# vercelRunOutcome `vercelRunOutcome` resolves the outcome of a Vercel `streamText` response that was piped through `Run.pipe`. It returns a [`VercelRunOutcome`](#vercel-run-outcome-returns) object discriminated on `reason`: when `reason` is `'suspend'`, call `Run.suspend`; otherwise pass the whole outcome to `Run.end` (the non-`'suspend'` arms are assignable to `RunEndParams`). It preserves transport-level outcomes (`'cancelled'`, `'error'`) from the pipe result. When the pipe completed naturally, it awaits Vercel's `finishReason` and returns `{ reason: 'suspend' }` for `'tool-calls'` (the LLM requested tools the SDK did not auto-execute, so the run should pause for the next client-published tool result), or `{ reason: 'complete' }` otherwise. An `'error'` outcome carries the wrapped `error`. Use it at the end of every Vercel route handler so the lifecycle event you publish matches what actually happened on the LLM side. #### Javascript ``` import { streamText } from 'ai'; import { vercelRunOutcome } from '@ably/ai-transport/vercel'; const result = streamText({ model, messages, tools }); const pipeResult = await run.pipe(result.toUIMessageStream()); const outcome = await vercelRunOutcome(pipeResult, result.finishReason); if (outcome.reason === 'suspend') { await run.suspend(); } else { await run.end(outcome); } ``` ## Resolve the run outcome `vercelRunOutcome(pipeResult: StreamResult, finishReason: PromiseLike): Promise` The helper applies two rules: 1. If `pipeResult.reason` is `'cancelled'` or `'error'`, return it as-is. The transport already knows the run ended for a non-completion reason. An `'error'` outcome carries `pipeResult.error` wrapped as an `Ably.ErrorInfo`. 2. If `pipeResult.reason` is `'complete'`, await `finishReason` and translate: - `'tool-calls'` returns `{ reason: 'suspend' }`. The LLM requested tools the SDK did not auto-execute, so the run should pause for the next client-published tool result. - Any other Vercel finish reason returns `{ reason: 'complete' }`. - If `finishReason` rejects, classify the rejection: abort-shaped errors return `{ reason: 'cancelled' }`; anything else (for example `NoOutputGeneratedError`) returns `{ reason: 'error', error }` with the rejection wrapped as an `Ably.ErrorInfo`. The rejection guard matters: Vercel AI SDK v6 rejects `streamText().finishReason` with the abort signal's reason when the stream is aborted before any step completes, and with `NoOutputGeneratedError` when the model produced nothing at all. Without the guard the rejection would bubble out of the route handler, skip `Run.end`, and leave the run with no `ai-run-end` event on the session. ### Parameters | Parameter | Required | Description | Type | | --- | --- | --- | --- | | pipeResult | required | The result returned by [`Run.pipe`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md#pipe). |
| | finishReason | required | The `finishReason` promise from a Vercel `streamText` result. | `PromiseLike` |
| Property | Description | Type | | --- | --- | --- | | reason | Why the stream ended. | `'complete' \| 'cancelled' \| 'error'` | | error | The original error when `reason` is `'error'`. | `Error` |
### Returns `Promise`. A `VercelRunOutcome` is an object discriminated on `reason` with three arms: - `{ reason: 'suspend' }`: the LLM requested tools the SDK did not auto-execute. Call [`Run.suspend`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md#run-suspend) so a continuation invocation can resume the run. - `{ reason: 'complete' | 'cancelled' }`: the run terminated normally. Pass the outcome to [`Run.end`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md#run-end). - `{ reason: 'error', error }`: the run failed. `error` is an `Ably.ErrorInfo`. Pass the outcome to [`Run.end`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md#run-end). `error` is present only on the `'error'` arm. The non-`'suspend'` arms are assignable to [`RunEndParams`](https://ably.com/docs/ai-transport/api/javascript/core/agent-session.md#run-end-params), so after a `'suspend'` guard the whole object passes straight to `Run.end(outcome)`.
| Property | Description | Type | | --- | --- | --- | | reason | The terminal reason, or `'suspend'` to pause the run. | `'suspend' \| 'complete' \| 'cancelled' \| 'error'` | | error | The terminal error, present only when `reason` is `'error'`. | `Ably.ErrorInfo` |
## Example A Vercel route handler that runs a tool-capable model, pipes the stream, and either suspends (for tool resolution) or ends the run with the correct reason. ### Javascript ``` import * as Ably from 'ably'; import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; import { Invocation } from '@ably/ai-transport'; import { createAgentSession, vercelRunOutcome } from '@ably/ai-transport/vercel'; const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY }); export async function POST(req: Request) { const invocation = Invocation.fromJSON(await req.json()); const session = createAgentSession({ client: ably, channelName: invocation.sessionName, }); await session.connect(); const run = session.createRun(invocation, {}, { signal: req.signal }); try { // Rebuild the conversation from run.view before run.start(): draining pages // in this run's triggering input (otherwise run.start() awaits it live). while (run.view.hasOlder()) { await run.view.loadOlder(); } const conversation = run.view.getMessages().map(({ message }) => message); await run.start(); const result = streamText({ model: openai('gpt-4'), messages: conversation, tools: myTools, }); const pipeResult = await run.pipe(result.toUIMessageStream()); const outcome = await vercelRunOutcome(pipeResult, result.finishReason); if (outcome.reason === 'suspend') { await run.suspend(); } else { await run.end(outcome); } } finally { await session.end(); } return Response.json({ invocationId: run.invocationId }); } ``` ## Related Topics - [Chat transport](https://ably.com/docs/ai-transport/api/javascript/vercel/chat-transport.md): API reference for the AI Transport Vercel ChatTransport adapter and the Vercel-bound createClientSession and createAgentSession factories. - [Codec](https://ably.com/docs/ai-transport/api/javascript/vercel/codec.md): API reference for createUIMessageCodec, the factory for the pre-built AI Transport codec for the Vercel AI SDK. ## Documentation Index To discover additional Ably documentation: 1. Fetch [llms.txt](https://ably.com/llms.txt) for the canonical list of available pages. 2. Identify relevant URLs from that index. 3. Fetch target pages as needed. Avoid using assumed or outdated documentation paths.