Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/ai-chat/client-protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ Re-calling `POST /api/v1/sessions` with the same `(taskIdentifier, externalId)`

The `publicAccessToken` returned by `POST /api/v1/sessions` is valid for 60 minutes. Two ways to keep going past that:

1. **Take refreshed tokens from the stream.** Every `turn-complete` control record on `.out` carries a `public-access-token` header with a refreshed JWT (see [`turn-complete` control record](#turn-complete-control-record)). For active conversations this just rolls — replace your stored token whenever the header is present.
1. **Take refreshed tokens from the stream.** Most `turn-complete` control records on `.out` carry a `public-access-token` header with a refreshed JWT (see [`turn-complete` control record](#turn-complete-control-record)). The header is optional and may be absent on some turns (for example an errored turn), so replace your stored token whenever the header is present rather than expecting it every turn. For active conversations it rolls on its own.
2. **Re-call `POST /api/v1/sessions`.** Idempotent, returns `isCached: true` and a brand-new 60-minute token. Use this if a chat goes idle long enough that the SSE stream has closed and you need to resume.

<Note>
Expand Down Expand Up @@ -1048,7 +1048,7 @@ Yes. `.in` records are processed in arrival order — the agent's stop handler a
</Expandable>

<Expandable title="What's the format of the optional `X-Part-Id` header?">
Any opaque ASCII string up to ~64 characters. The built-in clients pass a `nanoid(7)` (e.g. `"V1StGXR"`) generated per request. The server uses it as a per-record idempotency key — re-POSTing the same body with the same `X-Part-Id` produces a single S2 record. If you don't send the header, the server generates one for you and idempotency is per-request only.
Any opaque ASCII string up to ~64 characters. The built-in clients generate a high-entropy id per logical send (a UUID in the browser, a `nanoid` server-side) and reuse it across auth retries of that send. The server uses it as a per-record idempotency key — re-POSTing the same body with the same `X-Part-Id` produces a single S2 record. If you don't send the header, the server generates one for you and idempotency is per-request only.
</Expandable>

<Expandable title="What happens on rate-limit (429)?">
Expand Down
8 changes: 4 additions & 4 deletions docs/ai-chat/error-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -131,17 +131,17 @@ To persist errors for debugging or undo, use `onTurnComplete` (which fires even

### Using `onTurnComplete`

`onTurnComplete` fires after every turn — successful **or** errored. The `responseMessage` will be undefined or partial on errors. Use this to mark the turn as failed:
`onTurnComplete` fires after every turn — successful **or** errored. On an errored turn `responseMessage` is undefined or partial and `error` carries the thrown value (with `finishReason` set to `"error"`). Use this to mark the turn as failed:

```ts
onTurnComplete: async ({ chatId, uiMessages, responseMessage, stopped }) => {
onTurnComplete: async ({ chatId, uiMessages, responseMessage, stopped, error }) => {
// Persist the messages regardless of error state
await db.chat.update({
where: { id: chatId },
data: {
messages: uiMessages,
// Mark the chat as errored if no response message
lastTurnStatus: responseMessage ? "ok" : stopped ? "stopped" : "errored",
// `error` is set when the turn threw
lastTurnStatus: error ? "errored" : stopped ? "stopped" : "ok",
Comment on lines +137 to +144

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Error-handling doc example destructures non-existent error field from onTurnComplete event

The updated code example destructures error from the onTurnComplete callback and uses it to determine lastTurnStatus. However, TurnCompleteEvent (defined at packages/trigger-sdk/src/v3/ai.ts:4175-4243) has no error field — the destructured value will always be undefined, so lastTurnStatus will never be "errored". The previous code (responseMessage ? "ok" : stopped ? "stopped" : "errored") was at least a heuristic that worked; the new code is strictly broken for detecting errors. The accompanying prose at line 134 ("error carries the thrown value") is also incorrect per the current codebase — onTurnComplete is not called on errored turns at all (packages/trigger-sdk/src/v3/ai.ts:7527-7597).

Prompt for agents
The onTurnComplete code example destructures `error` from the event and uses `error ? "errored" : ...` to set lastTurnStatus. But TurnCompleteEvent (packages/trigger-sdk/src/v3/ai.ts:4175-4243) has no `error` field, and onTurnComplete is not called on errored turns (see the catch block at ai.ts:7527). Either revert to the previous heuristic (`responseMessage ? "ok" : stopped ? "stopped" : "errored"`), or first add the `error` field to TurnCompleteEvent in the SDK and update the turn error handler to call onTurnComplete with the error. Also fix the prose at line 134 which claims `error` carries the thrown value.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +134 to +144

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Documentation may be pre-documenting a planned error field on TurnCompleteEvent

The error field additions across reference.mdx and error-handling.mdx could be intentional documentation written ahead of the corresponding SDK code changes (a common pattern when docs and code PRs are split). If so, this PR should either be merged after the SDK PR that adds the error field, or the two should be coordinated. Currently, the SDK at packages/trigger-sdk/src/v3/ai.ts:4175-4243 does not have this field, and the turn error handler at line 7527 does not invoke onTurnComplete on errors.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

},
});
},
Expand Down
2 changes: 1 addition & 1 deletion docs/ai-chat/how-it-works.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The agent task is running. It reads the new message off `.in`, fires `onTurnStar

### Idle (awaiting next message)

The turn is over. The task is alive but not doing work — it is parked in a waitpoint on `.in`, waiting for the next user message. If one arrives, it goes back to **Streaming** for the next turn. If `idleTimeoutInSeconds` (defaulting to a few minutes) passes with no new message, it moves to **Suspended**.
The turn is over. The task is alive but not doing work — it is parked in a waitpoint on `.in`, waiting for the next user message. If one arrives, it goes back to **Streaming** for the next turn. If `idleTimeoutInSeconds` (30 seconds by default) passes with no new message, it moves to **Suspended**.

### Suspended

Expand Down
2 changes: 1 addition & 1 deletion docs/ai-chat/lifecycle-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export const myChat = chat.agent({

## onValidateMessages

Validate or transform incoming `UIMessage[]` before they are converted to model messages. Fires once per turn with the raw messages from the wire payload (after cleanup of aborted tool parts), **before** accumulation and `toModelMessages()`.
Validate or transform incoming `UIMessage[]` before they are converted to model messages. Fires on turns that carry incoming messages, with the raw messages from the wire payload (after cleanup of aborted tool parts), **before** accumulation and `toModelMessages()`. Turns with no incoming messages — preload, close, and regenerate with nothing re-sent — skip it.

Return the validated messages array. Throw to abort the turn with an error.

Expand Down
6 changes: 4 additions & 2 deletions docs/ai-chat/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ Passed to the `onTurnComplete` callback.
| `continuation` | `boolean` | Whether this run is continuing an existing chat |
| `usage` | `LanguageModelUsage \| undefined` | Token usage for this turn |
| `totalUsage` | `LanguageModelUsage` | Cumulative token usage across all turns |
| `finishReason` | `FinishReason \| undefined` | Why the LLM stopped (`"stop"`, `"tool-calls"`, `"error"`, …) |
| `error` | `unknown` | Set when the turn threw; `responseMessage` is then undefined or partial |
Comment thread
ericallam marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Documentation references non-existent error field on TurnCompleteEvent

The reference docs add error: unknown as a field of TurnCompleteEvent, but this field does not exist in the actual TypeScript type definition at packages/trigger-sdk/src/v3/ai.ts:4175-4243. The type ends with finishReason?: FinishReason and has no error property. Additionally, when a turn throws (the catch block at packages/trigger-sdk/src/v3/ai.ts:7527), onTurnComplete is not invoked at all — the error handler writes an error chunk to the stream and waits for the next message, skipping the turn-complete hooks entirely. Users following this documentation will destructure a field that is always undefined.

Prompt for agents
The TurnCompleteEvent type in packages/trigger-sdk/src/v3/ai.ts:4175-4243 does not have an `error` field. Either (a) remove the `error` row from the reference table and the corresponding documentation in error-handling.mdx, or (b) if this is intended new behavior, add the `error` field to the TurnCompleteEvent type and update the turn error handler (ai.ts:7527-7597) to invoke onTurnComplete with the error before waiting for the next message. The same issue appears in docs/ai-chat/error-handling.mdx lines 134, 137, and 143-144.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


## BeforeTurnCompleteEvent

Expand Down Expand Up @@ -761,10 +763,10 @@ See [Actions](/ai-chat/actions) for backend setup and [Sending actions](/ai-chat
Eagerly trigger a run before the first message.

```ts
transport.preload(chatId, { idleTimeoutInSeconds?: number }): Promise<void>
transport.preload(chatId): Promise<void>
```

No-op if a session already exists for this chatId. See [Preload](/ai-chat/fast-starts#preload) for full details.
No-op if a session already exists for this chatId. The preload idle window is set by `preloadIdleTimeoutInSeconds` on the agent, not by this call. See [Preload](/ai-chat/fast-starts#preload) for full details.

## useTriggerChatTransport

Expand Down
Loading