You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Think: public applyToolResult() / applyToolApproval() for answering a parked tool call from the server side #2636
Versions:@cloudflare/think 0.20.2. Unchanged on 0.21.0 and on the inspected main at fc7f59f, where both methods are private.
Use case
Our app speaks its own protocol to its clients, so it doesn't use Think's chat WebSocket. Two cases park a turn:
a tool with no execute (a client-style tool, such as "ask the user", whose answer arrives later over our protocol, possibly after hibernation);
a needsApproval tool (Allow or Deny arrives over our protocol).
Think already applies both answers. Its WebSocket handler takes tool-result and tool-approval frames and calls _applyToolResult(toolCallId, output, state?, errorText?) and _applyToolApproval(toolCallId, approved) through _enqueueInteractionApply. Then continueLastTurn() finishes the turn. But those methods are private, and there is no public way to do the same from server code: an RPC method, a webhook, or a queue consumer.
Reproduction
getTools(){return{ask_user: tool({inputSchema: z.object({question: z.string()})}),// no executedangerous: tool({inputSchema: z.object({what: z.string()}),needsApproval: true,execute: async({ what })=>({ran: what})})};}asyncanswer(kind: "result"|"approval"){awaitthis.runTurn({input: kind==="result" ? "ask" : "danger"});constpart=lastToolPart(this.messages);// state: "input-available" / "approval-requested"// Nothing public: applyToolResult, addToolOutput, addToolResult, applyToolApproval,// addToolApprovalResponse are all undefined on the instance.constpriv=thisasunknownas{_applyToolResult(id: string,o: unknown): Promise<void>;_applyToolApproval(id: string,ok: boolean): Promise<void>};if(kind==="result")awaitpriv._applyToolResult(part.toolCallId,{answer: "blue"});elseawaitpriv._applyToolApproval(part.toolCallId,true);awaitthis.continueLastTurn();// completes: the model gets {answer:"blue"} / the tool runs}
Why not the existing public paths
action({ kind: "durable-pause" }) with approveExecution() and rejectExecution() is public and durable. It models a human approving an action, though. The parked output tells the model that it is awaiting approval, the model keeps writing after the pause, and the continuation runs outside the app's own turn runner. It doesn't fit "deliver this answer to that tool call", and it doesn't cover AI SDK needsApproval tools or tools with no execute.
/** Apply a result to a pending client-style tool call (as the `tool-result` frame does). */applyToolResult(toolCallId: string,output: unknown,options?: {state?: "output-error";errorText?: string;continue?: boolean }): Promise<void>;/** Approve or deny a pending `needsApproval` call (as the `tool-approval` frame does). */applyToolApproval(toolCallId: string,approved: boolean,options?: {continue?: boolean}): Promise<void>;
Both should go through _enqueueInteractionApply, so they are ordered with any frame-driven applies. continue: true would call continueLastTurn(), or schedule the same auto-continuation the frames use. Being public also makes the ordering contract part of the API: calling the private methods directly, as we do, skips the queue.
Activity