createToolResultFork

createToolResultFork builds the input and the send options a client needs to answer a suspended tool call. With the Vercel codec a client tool result opens a new reply run rather than re-entering the suspended run, so two clients answering the same call land on segregated sibling branches instead of colliding on one shared run.

You need it on the generic hooks path, where you drive view.send yourself. The ChatTransport performs the same construction internally, so an application using useChat never calls this.

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

// Client code.
import { createToolResultFork } from '@ably/ai-transport/vercel';

const { input, sendOptions } = createToolResultFork({
  runMessages,
  parentCodecMessageId,
  toolCallId,
  result: { output },
  supersedesRunId,
});

const forked = await view.send([input], sendOptions);

Build a tool-result fork

createToolResultFork(params: ToolResultForkParams): { input: VercelInput, sendOptions: SendOptions }

The fork's parameters all come from the tree, because message order alone does not identify the suspended run. view.runOf(codecMessageId) gives the run that owns the assistant message holding the tool call, and tree.getRunNode(runId) gives the node whose parentCodecMessageId and projection the fork needs.

The returned input addresses a fresh assistant codec-message-id and carries a copy of the suspended run's whole message list, each entry re-minted under a new id. Seeding the whole run rather than only the current assistant message keeps context across sequential client tool calls, so a run that already resolved two tool calls carries both into the fork.

Parameters

runMessagesrequiredCodecMessage<UIMessage>[]
The suspended run's full message list, read as codec.getMessages(node.projection). The entry carrying toolCallId becomes the result's target.
parentCodecMessageIdrequiredString
The fork's structural parent, which is the suspended run's own input node from node.parentCodecMessageId. It roots the fork as a same-parent sibling of the suspended run instead of a detached root.
toolCallIdrequiredString
The tool call being resolved.
resultrequiredToolCallResolution
The resolution.
supersedesRunIdrequiredString
The runId of the suspended run this fork resolves, from node.runId. The tree then hides that run from branch selection.

Exactly one of output and errorMessage is set.

Returns

{ input: VercelInput, sendOptions: SendOptions }. Pass both to view.send([input], sendOptions).

The send options carry parent, role: 'assistant', and supersedes, and deliberately carry no runId. The agent mints the fork's run id when it publishes ai-run-start, and the tree reconciles the client's optimistic reply run onto it by the tool result's codec-message-id. The assistant role marks the run-less input as a reconstructed assistant turn, so the tree reads it as a reply run rather than a user prompt.

Throws an ErrorInfo with code 40003 when no entry in runMessages carries toolCallId, since the run does not own that call.

Example

Resolve a pending client tool call, then wake the agent so it picks the result up off the session:

JavaScript

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// Client code.
import { createToolResultFork, createUIMessageCodec } from '@ably/ai-transport/vercel';

const codec = createUIMessageCodec();

async function resolveToolCall(view, tree, pending, toolCallId, output) {
  const node = tree.getRunNode(view.runOf(pending.codecMessageId).runId);
  if (!node) return;

  const { input, sendOptions } = createToolResultFork({
    runMessages: codec.getMessages(node.projection),
    parentCodecMessageId: node.parentCodecMessageId,
    toolCallId,
    result: { output },
    supersedesRunId: node.runId,
  });

  const forked = await view.send([input], sendOptions);

  await fetch('/api/chat', {
    method: 'POST',
    body: JSON.stringify(forked.toInvocation().toJSON()),
  });
}