Files
Matt Aitken e2d3b8388c feat(sdk): return lastEventId from writeTurnComplete and typed capture result (#4304)
## Summary

Two ergonomic additions for custom chat-agent loops that own the turn
loop (`chat.customAgent`, `chat.createSession`, and the hand-rolled
primitives).

`chat.writeTurnComplete()` now resolves to `{ lastEventId }`, the resume
cursor for the start of the next turn. A custom loop can persist it
straight from the task instead of round-tripping it back from the client
after the turn ends. The value was already produced internally by the
turn-complete write; the public wrapper simply discarded it.

`chat.pipeAndCapture()` no longer throws when a stream is stopped or
fails. It now resolves to a `PipeAndCaptureResult` carrying any partial
`message` captured before the stop or failure, a typed `status`
(`"complete" | "aborted" | "error"`), and the `error` on failure.
Previously a failed stream threw and the partial was lost, and an abort
was captured only when the AI SDK happened to fire `onFinish` in time.

```ts
const { message, status, error } = await chat.pipeAndCapture(result, { signal });
if (message) conversation.addResponse(message);
if (status === "error") logger.error("turn failed", { error });

const { lastEventId } = await chat.writeTurnComplete();
await db.chats.update(chatId, { lastEventId });
```

## Design

`pipeAndCapture` wraps the pipe in a `try/catch` and classifies the
outcome from the abort signal (a stop drains the source stream cleanly
rather than throwing) versus a thrown error. It also races the
`onFinish` capture against a timeout so a hard stop that prevents
`onFinish` from firing can't hang the caller. This mirrors the capture
path `chat.agent` already uses internally.

The `finishReason` from `onFinish` is surfaced too, since it was already
captured on the built-in path.

The internal `turn.complete()` helper keeps its existing contract: it
still returns `UIMessage | undefined`, still throws on a genuine stream
failure, and still discards output on a full run cancel.

## Breaking change

`chat.pipeAndCapture` previously resolved to `UIMessage | undefined`.
Call sites now read `.message` off the result. This is a young,
low-level API; the docs examples are updated in this PR.
2026-07-21 17:08:30 +02:00
..
2023-08-02 15:09:35 +01:00
2023-08-02 15:09:35 +01:00
2025-08-18 12:34:55 +01:00
2024-08-07 11:13:45 +01:00
2025-08-18 12:34:55 +01:00
2024-08-07 11:13:45 +01:00
2024-08-07 11:13:45 +01:00
2024-08-07 11:13:45 +01:00
2024-08-07 11:13:45 +01:00
2023-10-02 14:26:54 +01:00
2024-08-07 11:13:45 +01:00
2025-08-18 12:34:55 +01:00
2026-03-04 09:55:54 +00:00

Development

Install and initial setup

pnpm install

Running the app

pnpm run dev --filter docs

View the app locally

It runs locally here: http://localhost:3050