Pending Actions
How typed wait points, client tools, and manual outputs resume work.
Pending actions unify named suspension, human approval, client tools, and tools whose output must be supplied externally.
await action.submit(data);
await action.cancel({ reason });
await action.fail(error);submit() validates the payload before changing durable state. Invalid client
tool output or resume data leaves the action pending so the caller can correct
and retry it.
cancel() and fail() resolve the native tool part as output-error. They do
not invent a persisted cancelled tool state. PAI may derive cancelled as a
a renderer's action sidecar from run and suspension facts.
Finding An Action
Actions are sidecars bound to an exact projected message and native tool part.
They are not another transcript and do not live on ThreadState as a public
collection.
import { isPaiToolPart } from "@pai/client";
const state = await run.waitUntilBlocked();
for (const message of state.messages) {
for (const part of message.parts) {
if (!isPaiToolPart(part)) continue;
const action = thread.getPendingAction(message, part);
if (action?.name === "requestApproval.approval") {
await action.submit({
approved: true,
comment: "Looks good.",
});
}
}
}The sidecar carries its durable reference internally, so applications do not
pass message, run, or tool-call identifiers back to submit().
React tool renderers receive the narrowed action directly:
const renderers = {
requestApproval({ part, runStatus, action }) {
if (!action) return <ApprovalStatus part={part} state={part.state} />;
return <ApprovalForm input={action.input} onSubmit={action.submit} />;
},
};Native Tool State
The serializable tool part retains the AI SDK lifecycle:
input-streaminginput-availableapproval-requestedapproval-respondedoutput-availableoutput-erroroutput-denied
Named suspension history and the current suspension are projected onto the PAI
tool part, while the native part.state remains unchanged. PAI command adoption
for native SDK approval responses is intentionally deferred.
Submit, Cancel, And Fail
For client-executed tools and backend tools without execute, submit() accepts
the final tool output. For named suspension, it accepts the declared resume
payload and re-enters the tool.
await action.submit({ approved: false, comment: "Revise this first." });Use cancel() only when no valid domain response exists:
await action.cancel({ reason: "The approval panel was dismissed." });Use fail() for an execution failure:
await action.fail({
message: "The browser capability disconnected.",
code: "BROWSER_DISCONNECTED",
});Application-executed tool failures replay through the AI SDK's native
error-text model message. Safe structured detail remains available to the UI
through the PAI error sidecar; raw exceptions are never projected.
Durable Flow
This keeps one native message authority while giving applications one command shape for every PAI-managed interruption.