trigger-chat-agent-advanced

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

chat.agent: advanced and operational

chat.agent:高级可运行功能

chat.agent
is built on Sessions: a durable, task-bound, bi-directional I/O channel pair keyed on a stable
externalId
(e.g.
chatId
) that outlives any single run. This skill covers the layers beneath and around the everyday agent: the raw
sessions
API, server-side
AgentChat
, durable sub-agents, actions / background injection, fast starts, compaction and recovery, and the wire protocol for custom transports.
Two
chat
namespaces are easy to confuse: the agent definition imports
chat
from
@trigger.dev/sdk/ai
; Head Start / Node-listener server entries import
chat
from
@trigger.dev/sdk/chat-server
.
chat.agent
基于 Sessions 构建:这是一组持久化、绑定任务的双向I/O通道,以稳定的
externalId
(例如
chatId
)作为键,生命周期超过单次运行。本技能涵盖了日常代理之下及周边的层级:原始
sessions
API、服务端
AgentChat
、持久化子代理、动作/后台注入、快速启动、压缩与恢复,以及自定义传输层的有线协议。
有两个
chat
命名空间容易混淆:代理定义从
@trigger.dev/sdk/ai
导入
chat
;Head Start/Node监听器服务入口从
@trigger.dev/sdk/chat-server
导入
chat

Setup

配置步骤

Happy path: drive an agent from server-side code (task, webhook, or script) with
AgentChat
.
ts
import { AgentChat } from "@trigger.dev/sdk/chat";
import type { myAgent } from "./trigger/my-agent";

const chat = new AgentChat<typeof myAgent>({ agent: "my-chat", clientData: { userId: "user_123" } });
const stream = await chat.sendMessage("Review PR #42");
const text = await stream.text();
await chat.close();
sendMessage()
triggers a run on the first call, then reuses it via input streams.
ChatStream
exposes
text()
,
result()
(
{ text, toolCalls, toolResults }
),
messages()
(UIMessage snapshots), and the raw
.stream
. Other methods:
steer(text)
,
stop()
,
sendRaw(uiMessages)
,
sendAction(action)
,
preload()
,
reconnect()
.
常规路径:通过服务端代码(任务、Webhook或脚本)使用
AgentChat
驱动代理。
ts
import { AgentChat } from "@trigger.dev/sdk/chat";
import type { myAgent } from "./trigger/my-agent";

const chat = new AgentChat<typeof myAgent>({ agent: "my-chat", clientData: { userId: "user_123" } });
const stream = await chat.sendMessage("Review PR #42");
const text = await stream.text();
await chat.close();
sendMessage()
在首次调用时触发运行,之后通过输入流复用该运行。
ChatStream
提供了
text()
result()
(返回
{ text, toolCalls, toolResults }
)、
messages()
(UIMessage快照)以及原始的
.stream
。其他方法包括:
steer(text)
stop()
sendRaw(uiMessages)
sendAction(action)
preload()
reconnect()

Core patterns

核心模式

1. Raw Sessions for non-chat, bi-directional I/O

1. 用于非聊天双向I/O的原始Sessions

Reach for
sessions
directly when the chat abstraction does not fit: agent inboxes, approval flows, server-to-server pipelines.
sessions.start
is idempotent on
(env, externalId)
;
externalId
cannot start with
session_
.
ts
import { sessions } from "@trigger.dev/sdk";

const { id, publicAccessToken } = await sessions.start({
  type: "chat.agent",
  externalId: chatId,
  taskIdentifier: "my-chat",
  triggerConfig: { tags: [`chat:${chatId}`], basePayload: { chatId, trigger: "preload" } },
});

const session = sessions.open(chatId); // no network call; methods are lazy
await session.out.append({ kind: "message", text: "hello" });
const next = await session.in.once<MyEvent>({ timeoutMs: 30_000 });
sessions.open(id).in
also has
send
,
on(handler)
,
peek
,
wait
(suspends the run, only inside
task.run()
), and
waitWithIdleTimeout
.
.out
has
append
,
pipe
,
writer
,
read
,
writeControl
, and
trimTo
. List with
sessions.list({ type, tag, status, ... })
(
for await
), mutate with
sessions.update
, end with
sessions.close
(terminal, idempotent).
当聊天抽象不适用时,直接使用
sessions
:代理收件箱、审批流程、服务端到服务端流水线。
sessions.start
(env, externalId)
上是幂等的;
externalId
不能以
session_
开头。
ts
import { sessions } from "@trigger.dev/sdk";

const { id, publicAccessToken } = await sessions.start({
  type: "chat.agent",
  externalId: chatId,
  taskIdentifier: "my-chat",
  triggerConfig: { tags: [`chat:${chatId}`], basePayload: { chatId, trigger: "preload" } },
});

const session = sessions.open(chatId); // 无网络调用;方法为惰性执行
await session.out.append({ kind: "message", text: "hello" });
const next = await session.in.once<MyEvent>({ timeoutMs: 30_000 });
sessions.open(id).in
还包含
send
on(handler)
peek
wait
(仅在
task.run()
内暂停运行)和
waitWithIdleTimeout
方法。
.out
包含
append
pipe
writer
read
writeControl
trimTo
方法。使用
sessions.list({ type, tag, status, ... })
(支持
for await
)列出会话,使用
sessions.update
修改会话,使用
sessions.close
结束会话(终端操作,幂等)。

2. Durable sub-agent as a streaming tool

2. 作为流工具的持久化子代理

AgentChat
inside an AI SDK
tool()
delegates to a durable sub-agent; its response streams as preliminary tool results. Give the tool a
toModelOutput
so the model sees a compact summary.
ts
import { tool } from "ai";
import { AgentChat } from "@trigger.dev/sdk/chat";
import { z } from "zod";

const researchTool = tool({
  description: "Delegate research to a specialist agent.",
  inputSchema: z.object({ topic: z.string() }),
  execute: async function* ({ topic }, { abortSignal }) {
    const chat = new AgentChat({ agent: "research-agent" });
    const stream = await chat.sendMessage(topic, { abortSignal });
    yield* stream.messages(); // UIMessage snapshots become preliminary tool results
    await chat.close();
  },
  toModelOutput: ({ output: message }) => {
    const lastText = message?.parts?.findLast((p: { type: string }) => p.type === "text") as
      | { text?: string }
      | undefined;
    return { type: "text", value: lastText?.text ?? "Done." };
  },
});
For a subtask exposed via
execute: ai.toolExecute(task)
, stream progress to the agent's run with
chat.stream.writer({ target: "root" })
.
target
accepts
"self" | "parent" | "root" | <runId>
. Inside the subtask, read context with
ai.toolCallId()
and
ai.chatContextOrThrow<typeof myChat>()
(
{ chatId, turn, continuation, clientData }
).
ts
import { chat, ai } from "@trigger.dev/sdk/ai";

const { waitUntilComplete } = chat.stream.writer({
  target: "root",
  execute: ({ write }) =>
    write({ type: "data-research-status", id: partId, data: { query, status: "in-progress" } }),
});
await waitUntilComplete();
AI SDK的
tool()
内的
AgentChat
会委托给持久化子代理;其响应会作为初步工具结果流式返回。为工具设置
toModelOutput
,让模型看到简洁的摘要。
ts
import { tool } from "ai";
import { AgentChat } from "@trigger.dev/sdk/chat";
import { z } from "zod";

const researchTool = tool({
  description: "Delegate research to a specialist agent.",
  inputSchema: z.object({ topic: z.string() }),
  execute: async function* ({ topic }, { abortSignal }) {
    const chat = new AgentChat({ agent: "research-agent" });
    const stream = await chat.sendMessage(topic, { abortSignal });
    yield* stream.messages(); // UIMessage快照成为初步工具结果
    await chat.close();
  },
  toModelOutput: ({ output: message }) => {
    const lastText = message?.parts?.findLast((p: { type: string }) => p.type === "text") as
      | { text?: string }
      | undefined;
    return { type: "text", value: lastText?.text ?? "Done." };
  },
});
对于通过
execute: ai.toolExecute(task)
暴露的子任务,使用
chat.stream.writer({ target: "root" })
将进度流式传输到代理的运行中。
target
接受
"self" | "parent" | "root" | <runId>
。在子任务内部,使用
ai.toolCallId()
ai.chatContextOrThrow<typeof myChat>()
读取上下文(返回
{ chatId, turn, continuation, clientData }
)。
ts
import { chat, ai } from "@trigger.dev/sdk/ai";

const { waitUntilComplete } = chat.stream.writer({
  target: "root",
  execute: ({ write }) =>
    write({ type: "data-research-status", id: partId, data: { query, status: "in-progress" } }),
});
await waitUntilComplete();

3. Background injection: defer + inject

3. 后台注入:defer + inject

chat.defer(promise)
runs work in parallel with streaming (all deferred promises are awaited, with a 5s timeout, before
onTurnComplete
).
chat.inject(messages)
queues
ModelMessage[]
that drain at the next turn start or
prepareStep
boundary.
ts
export const myChat = chat.agent({
  id: "my-chat",
  onTurnComplete: async ({ messages }) => {
    chat.defer(
      (async () => {
        const analysis = await analyzeConversation(messages);
        chat.inject([{ role: "system", content: `[Analysis]\n\n${analysis}` }]);
      })()
    );
  },
  run: async ({ messages, signal }) =>
    streamText({ ...chat.toStreamTextOptions({ registry }), messages, abortSignal: signal, stopWhen: stepCountIs(15) }),
});
chat.defer(promise)
会在流式传输的同时并行运行任务(所有延迟的Promise会在
onTurnComplete
前等待,超时时间为5秒)。
chat.inject(messages)
会将
ModelMessage[]
加入队列,在下一轮开始或
prepareStep
边界处处理。
ts
export const myChat = chat.agent({
  id: "my-chat",
  onTurnComplete: async ({ messages }) => {
    chat.defer(
      (async () => {
        const analysis = await analyzeConversation(messages);
        chat.inject([{ role: "system", content: `[Analysis]\n\n${analysis}` }]);
      })()
    );
  },
  run: async ({ messages, signal }) =>
    streamText({ ...chat.toStreamTextOptions({ registry }), messages, abortSignal: signal, stopWhen: stepCountIs(15) }),
});

4. Compaction (threshold-based)

4. 压缩(基于阈值)

compaction.shouldCompact
decides when,
summarize
produces the summary that replaces the model messages. UI messages are preserved by default (customize via
compactUIMessages
). The
prepareStep
that performs inner-loop compaction is auto-injected by
chat.toStreamTextOptions()
; a
prepareStep
you pass after the spread wins.
ts
compaction: {
  shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
  summarize: async ({ messages }) =>
    (await generateText({
      model: anthropic("claude-haiku-4-5"),
      messages: [...messages, { role: "user", content: "Summarize concisely." }],
    })).text,
},
compaction.shouldCompact
决定何时进行压缩,
summarize
生成替换模型消息的摘要。默认保留UI消息(可通过
compactUIMessages
自定义)。执行内部循环压缩的
prepareStep
chat.toStreamTextOptions()
自动注入;你在扩展后传入的
prepareStep
会优先生效。
ts
compaction: {
  shouldCompact: ({ totalTokens }) => (totalTokens ?? 0) > 80_000,
  summarize: async ({ messages }) =>
    (await generateText({
      model: anthropic("claude-haiku-4-5"),
      messages: [...messages, { role: "user", content: "Summarize concisely." }],
    })).text,
},

5. Actions: mutate state without a turn

5. 动作:无需轮次即可修改状态

actionSchema
validates;
onAction
mutates via
chat.history
(
slice
,
replace
,
rollbackTo
,
remove
,
getPendingToolCalls
,
extractNewToolResults
). Actions fire
hydrateMessages
and
onAction
only, never
run()
or the turn hooks. Return a
StreamTextResult
, string, or
UIMessage
to also emit a model response.
ts
export const myChat = chat.agent({
  id: "my-chat",
  actionSchema: z.discriminatedUnion("type", [
    z.object({ type: z.literal("undo") }),
    z.object({ type: z.literal("rollback"), targetMessageId: z.string() }),
  ]),
  onAction: async ({ action }) => {
    if (action.type === "undo") chat.history.slice(0, -2);
    if (action.type === "rollback") chat.history.rollbackTo(action.targetMessageId);
  },
  run: async ({ messages, signal }) => streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal }),
});
Send from the browser with
transport.sendAction(chatId, { type: "undo" })
, or server-side with
agentChat.sendAction({ type: "rollback", targetMessageId: "msg-3" })
.
actionSchema
负责验证;
onAction
通过
chat.history
修改状态(包含
slice
replace
rollbackTo
remove
getPendingToolCalls
extractNewToolResults
方法)。动作仅触发
hydrateMessages
onAction
,绝不会触发
run()
或轮次钩子。返回
StreamTextResult
、字符串或
UIMessage
也会发出模型响应。
ts
export const myChat = chat.agent({
  id: "my-chat",
  actionSchema: z.discriminatedUnion("type", [
    z.object({ type: z.literal("undo") }),
    z.object({ type: z.literal("rollback"), targetMessageId: z.string() }),
  ]),
  onAction: async ({ action }) => {
    if (action.type === "undo") chat.history.slice(0, -2);
    if (action.type === "rollback") chat.history.rollbackTo(action.targetMessageId);
  },
  run: async ({ messages, signal }) => streamText({ model: anthropic("claude-sonnet-4-5"), messages, abortSignal: signal }),
});
在浏览器中通过
transport.sendAction(chatId, { type: "undo" })
发送动作,或在服务端通过
agentChat.sendAction({ type: "rollback", targetMessageId: "msg-3" })
发送。

6. Fast starts: Head Start

6. 快速启动:Head Start

chat.headStart
(from
@trigger.dev/sdk/chat-server
, NOT
/ai
) returns a Web Fetch handler that serves turn 1 from your own warm process, then hands off to the agent on turn 2+. Tools passed here must be schema-only (a module importing
ai
+
zod
only); heavy executes stay in the task.
ts
import { chat } from "@trigger.dev/sdk/chat-server";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { headStartTools } from "@/lib/chat-tools/schemas";

export const chatHandler = chat.headStart({
  agentId: "my-chat",
  run: async ({ chat: helper }) =>
    streamText({
      ...helper.toStreamTextOptions({ tools: headStartTools }),
      model: anthropic("claude-sonnet-4-6"),
      system: "You are helpful.",
      stopWhen: stepCountIs(15),
    }),
});
// Next.js: export const POST = chatHandler;  Transport: headStart: "/api/chat"
Node-only frameworks wrap a Web Fetch handler with
chat.toNodeListener(handler)
. Use the same model on both sides to avoid a tone shift between turn 1 and turn 2+.
chat.headStart
(来自
@trigger.dev/sdk/chat-server
,而非
/ai
)返回一个Web Fetch处理器,从你自己的预热进程中提供第一轮响应,然后在第二轮及以后将控制权交给代理。此处传入的工具必须是仅包含Schema的(仅导入
ai
+
zod
的模块);复杂的执行逻辑留在任务中。
ts
import { chat } from "@trigger.dev/sdk/chat-server";
import { streamText, stepCountIs } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { headStartTools } from "@/lib/chat-tools/schemas";

export const chatHandler = chat.headStart({
  agentId: "my-chat",
  run: async ({ chat: helper }) =>
    streamText({
      ...helper.toStreamTextOptions({ tools: headStartTools }),
      model: anthropic("claude-sonnet-4-6"),
      system: "You are helpful.",
      stopWhen: stepCountIs(15),
    }),
});
// Next.js: export const POST = chatHandler;  Transport: headStart: "/api/chat"
仅支持Node的框架使用
chat.toNodeListener(handler)
包装Web Fetch处理器。在两侧使用相同的模型,以避免第一轮和第二轮及以后的语气差异。

7. chat.local: init in onBoot, not onChatStart

7. chat.local:在onBoot中初始化,而非onChatStart

chat.local<T>({ id })
is module-level, shallow-proxy, run-scoped state. Initialize it in
onBoot
(fires on every fresh worker, including continuation runs), never
onChatStart
.
ts
const userContext = chat.local<{ name: string; plan: "free" | "pro" }>({ id: "userContext" });

export const myChat = chat.agent({
  id: "my-chat",
  onBoot: async ({ clientData }) => userContext.init({ name: "Alice", plan: "pro" }),
  run: async ({ messages, signal }) => streamText({ /* ... */ }),
});
chat.local<T>({ id })
是模块级、浅代理、运行作用域的状态。在
onBoot
中初始化(在每个新工作进程上触发,包括续运行),绝不要在
onChatStart
中初始化。
ts
const userContext = chat.local<{ name: string; plan: "free" | "pro" }>({ id: "userContext" });

export const myChat = chat.agent({
  id: "my-chat",
  onBoot: async ({ clientData }) => userContext.init({ name: "Alice", plan: "pro" }),
  run: async ({ messages, signal }) => streamText({ /* ... */ }),
});

8. Pending messages (mid-stream user input)

8. 待处理消息(流中用户输入)

A message sent while a turn is streaming should NOT cancel the stream. Configure
pendingMessages
(
shouldInject
,
prepare
,
onReceived
,
onInjected
) on the agent so the SDK's auto-injected
prepareStep
folds them in at the next boundary. On the frontend,
usePendingMessages
returns
pending
,
steer(text)
,
queue(text)
, and
promoteToSteering(id)
; send via
transport.sendPendingMessage(chatId, uiMessage, metadata?)
.
在轮次流式传输时发送的消息不应取消流。在代理上配置
pendingMessages
(包含
shouldInject
prepare
onReceived
onInjected
),以便SDK自动注入的
prepareStep
在下一个边界处将其合并。在前端,
usePendingMessages
返回
pending
steer(text)
queue(text)
promoteToSteering(id)
;通过
transport.sendPendingMessage(chatId, uiMessage, metadata?)
发送。

9. Recovery and version upgrades

9. 恢复与版本升级

onRecoveryBoot
fires only when a partial assistant message exists on the tail (interrupted deploy, crash, OOM retry). It does NOT fire on
chat.requestUpgrade()
, which is a graceful exit with no partial.
chat.requestUpgrade()
(called in
onTurnStart
/
onValidateMessages
to skip
run()
, or in
run()
/
chat.defer()
to exit after the turn) rotates the Session's
currentRunId
to a run on the latest deployment without a client reconnect. Pair it with a contract version on
clientData
.
ts
const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
onTurnStart: async ({ clientData }) => {
  if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
    chat.requestUpgrade();
  }
},
For OOM resilience, set
oomMachine
(and
machine
) on the agent so retries land on a larger preset.
onRecoveryBoot
仅在尾部存在部分助手消息时触发(部署中断、崩溃、OOM重试)。在
chat.requestUpgrade()
时不会触发,这是一种无部分内容的优雅退出。
chat.requestUpgrade()
(在
onTurnStart
/
onValidateMessages
中调用以跳过
run()
,或在
run()
/
chat.defer()
中调用以在轮次后退出)会将会话的
currentRunId
切换到最新部署的运行,无需客户端重新连接。将其与
clientData
上的合约版本配对使用。
ts
const SUPPORTED_VERSIONS = new Set(["v2", "v3"]);
onTurnStart: async ({ clientData }) => {
  if (clientData?.protocolVersion && !SUPPORTED_VERSIONS.has(clientData.protocolVersion)) {
    chat.requestUpgrade();
  }
},
为了提升OOM恢复能力,在代理上设置
oomMachine
(和
machine
),以便重试时使用更大的预设配置。

10. Offline testing with mockChatAgent

10. 使用mockChatAgent进行离线测试

@trigger.dev/sdk/ai/test
runs the real turn loop in-memory. Import it before the agent module so the resource catalog is installed. Drive with
sendMessage
,
sendRegenerate
,
sendAction
,
sendStop
,
sendHeadStart
,
sendHandover
; seed state with
seedSnapshot
/
seedSessionOutTail
/
seedSessionOutPartial
/
seedSessionInTail
; assert against
turn.chunks
and
harness.allChunks
.
ts
import { mockChatAgent } from "@trigger.dev/sdk/ai/test"; // BEFORE the agent module
import { myChatAgent } from "./my-chat.js";

const harness = mockChatAgent(myChatAgent, { chatId: "test-1", clientData: { model } });
try {
  const turn = await harness.sendMessage({ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] });
  // assert against turn.chunks
} finally {
  await harness.close();
}
Options include
mode
(
"preload" | "submit-message" | "handover-prepare" | "continuation"
),
preload
,
continuation
,
previousRunId
,
snapshot
,
taskContext
, and
setupLocals
. Set
taskContext.ctx.attempt.number > 1
to simulate an OOM-retry attempt.
runInMockTaskContext
drives a non-chat task offline.
@trigger.dev/sdk/ai/test
在内存中运行真实的轮次循环。在代理模块之前导入它,以便安装资源目录。使用
sendMessage
sendRegenerate
sendAction
sendStop
sendHeadStart
sendHandover
驱动测试;使用
seedSnapshot
/
seedSessionOutTail
/
seedSessionOutPartial
/
seedSessionInTail
初始化状态;断言
turn.chunks
harness.allChunks
ts
import { mockChatAgent } from "@trigger.dev/sdk/ai/test"; // 必须在代理模块之前导入
import { myChatAgent } from "./my-chat.js";

const harness = mockChatAgent(myChatAgent, { chatId: "test-1", clientData: { model } });
try {
  const turn = await harness.sendMessage({ id: "u1", role: "user", parts: [{ type: "text", text: "hi" }] });
  // 断言turn.chunks
} finally {
  await harness.close();
}
选项包括
mode
"preload" | "submit-message" | "handover-prepare" | "continuation"
)、
preload
continuation
previousRunId
snapshot
taskContext
setupLocals
。设置
taskContext.ctx.attempt.number > 1
以模拟OOM重试尝试。
runInMockTaskContext
驱动非聊天任务的离线测试。

11. Custom transport: the wire protocol

11. 自定义传输层:有线协议

Endpoints:
POST /api/v1/sessions
(create),
GET /realtime/v1/sessions/{id}/out
(SSE),
POST /realtime/v1/sessions/{id}/in/append
,
POST /api/v1/sessions/{id}/close
.
ChatInputChunk
is
{ kind: "message"; payload: ChatTaskWirePayload } | { kind: "stop"; message? }
. The
ChatTaskWirePayload
carries
chatId
,
trigger
(
submit-message | regenerate-message | preload | close | action | handover-prepare
),
message?
,
metadata?
,
action?
,
continuation?
,
previousRunId?
, and more. Control records are header-form:
trigger-control: turn-complete
(with optional
public-access-token
,
session-in-event-id
) and
trigger-control: upgrade-required
. The TS helpers
SSEStreamSubscription
and
controlSubtype(headers)
(documented in
docs/ai-chat/client-protocol.mdx
) handle batch decoding and control-record filtering for you.
端点:
POST /api/v1/sessions
(创建)、
GET /realtime/v1/sessions/{id}/out
(SSE)、
POST /realtime/v1/sessions/{id}/in/append
POST /api/v1/sessions/{id}/close
ChatInputChunk
的格式为
{ kind: "message"; payload: ChatTaskWirePayload } | { kind: "stop"; message? }
ChatTaskWirePayload
包含
chatId
trigger
submit-message | regenerate-message | preload | close | action | handover-prepare
)、
message?
metadata?
action?
continuation?
previousRunId?
等。控制记录为头部格式:
trigger-control: turn-complete
(可选包含
public-access-token
session-in-event-id
)和
trigger-control: upgrade-required
。TS辅助工具
SSEStreamSubscription
controlSubtype(headers)
(在
docs/ai-chat/client-protocol.mdx
中有文档)为你处理批量解码和控制记录过滤。

Common mistakes

常见错误

  • CRITICAL: sending a follow-up by re-POSTing
    POST /api/v1/sessions
    .
    ts
    // Wrong - a cached re-POST silently drops basePayload.message; basePayload is trigger config, not a channel
    await fetch("/api/v1/sessions", { method: "POST", body: JSON.stringify({ ...createBody }) });
    // Correct - append to the session's input channel
    await fetch(`/realtime/v1/sessions/${id}/in/append`, { method: "POST", body: JSON.stringify({ kind: "message", payload }) });
  • Using the wrong token for
    .in
    /
    .out
    .
    Use
    publicAccessToken
    from the create response body (session-scoped). The
    x-trigger-jwt
    response header is run-scoped and cannot subscribe.
  • Initializing
    chat.local
    in
    onChatStart
    .
    It is skipped on continuation runs, so
    run()
    crashes with
    chat.local can only be modified after initialization
    . Init in
    onBoot
    .
  • chat.defer
    for the message-history write.
    A mid-stream refresh would read
    []
    .
    await
    that write inline before the model streams; reserve
    chat.defer
    for analytics, audit, cache warming.
  • Giving the HITL tool an
    execute
    .
    streamText
    calls it immediately. Leave it execute-less; the frontend supplies the answer via
    addToolOutput
    +
    sendAutomaticallyWhen
    .
  • Declaring sub-agent / heavy tools only on
    streamText
    .
    Also declare them on
    chat.agent({ tools })
    (or pass to
    convertToModelMessages(uiMessages, { tools })
    in a custom agent) so
    toModelOutput
    re-applies on every turn.
  • Importing heavy-execute tools into the Head Start route module. This is a build-time import chain problem; runtime strip helpers do not fix it. Keep schemas in an
    ai
    +
    zod
    -only module.
  • Returning a megabyte tool output on the stream. One
    tool-output-available
    record over ~1 MiB throws
    ChatChunkTooLargeError
    . Persist to your store, write the row first, then emit only an id.
  • Setting
    X-Peek-Settled: 1
    on the active-send path.
    It races the new turn's first chunk and closes the stream early. Use it only on reconnect-on-reload paths.
Note on docs vocabulary: agent-side examples in some docs still use the legacy
trigger:turn-complete
chunk type. That is the agent-emit vocabulary. A custom reader must filter on the
trigger-control
header, not on
chunk.type
.
MCP-driven agent chats (
list_agents
,
start_agent_chat
,
send_agent_message
,
close_agent_chat
) are MCP server tools used from Claude Code / Cursor, not importable SDK functions. See
/mcp-tools#agent-chat-tools
.
  • 严重错误:通过重新POST
    POST /api/v1/sessions
    发送后续消息。
    ts
    // 错误 - 缓存的重新POST会静默丢弃basePayload.message;basePayload是触发器配置,而非通道
    await fetch("/api/v1/sessions", { method: "POST", body: JSON.stringify({ ...createBody }) });
    // 正确 - 追加到会话的输入通道
    await fetch(`/realtime/v1/sessions/${id}/in/append`, { method: "POST", body: JSON.stringify({ kind: "message", payload }) });
  • .in
    /
    .out
    使用错误的令牌。
    使用创建响应体中的
    publicAccessToken
    (会话作用域)。
    x-trigger-jwt
    响应头是运行作用域的,无法用于订阅。
  • onChatStart
    中初始化
    chat.local
    续运行会跳过此步骤,导致
    run()
    chat.local can only be modified after initialization
    崩溃。请在
    onBoot
    中初始化。
  • 使用
    chat.defer
    处理消息历史写入。
    流中刷新会读取到
    []
    。在模型流式传输前内联
    await
    该写入操作;将
    chat.defer
    保留用于分析、审计、缓存预热。
  • 为HITL工具设置
    execute
    streamText
    会立即调用它。不要设置execute;前端通过
    addToolOutput
    +
    sendAutomaticallyWhen
    提供答案。
  • 仅在
    streamText
    上声明子代理/重型工具。
    还需在
    chat.agent({ tools })
    上声明(或在自定义代理中传递给
    convertToModelMessages(uiMessages, { tools })
    ),以便
    toModelOutput
    在每一轮都重新生效。
  • 将重型执行工具导入Head Start路由模块。 这会导致构建时导入链问题;运行时剥离工具无法解决。请将Schema放在仅包含
    ai
    +
    zod
    的模块中。
  • 在流上返回兆字节级的工具输出。 超过~1 MiB的
    tool-output-available
    记录会抛出
    ChatChunkTooLargeError
    。将其持久化到你的存储中,先写入行,然后仅发出ID。
  • 在主动发送路径上设置
    X-Peek-Settled: 1
    这会与新轮次的第一个块竞争,导致流提前关闭。仅在重新加载时重新连接的路径上使用。
文档词汇说明:部分文档中的代理端示例仍使用旧版
trigger:turn-complete
块类型。这是代理端的术语。自定义读取器必须过滤
trigger-control
头部,而非
chunk.type
MCP驱动的代理聊天(
list_agents
start_agent_chat
send_agent_message
close_agent_chat
)是用于Claude Code/Cursor的MCP服务工具,并非可导入的SDK函数。请查看
/mcp-tools#agent-chat-tools

References

参考资料

  • trigger-authoring-chat-agent
    skill - the everyday
    chat.agent({...})
    definition, lifecycle hooks, and the
    useTriggerChatTransport
    happy path. Start there before reaching for this skill.
  • trigger-realtime
    skill - Realtime hooks and frontend streaming beyond the chat transport.
  • trigger-tasks
    skill - base
    task()
    semantics,
    ctx
    , and standard lifecycle hooks.
Reference docs ship beside this skill in the same package, read them locally (no network), pinned to your installed version. The
sources:
frontmatter above lists every doc this skill draws from, all under
@trigger.dev/sdk/docs/ai-chat/
(including
patterns/
). For HITL, sessions, and sub-agents start with
sessions.mdx
,
server-chat.mdx
,
client-protocol.mdx
,
patterns/human-in-the-loop.mdx
,
patterns/sub-agents.mdx
.
For
trigger.config.ts
and build extensions a chat-agent task may need (Prisma, Playwright, Python, etc.), read the bundled config docs under
@trigger.dev/sdk/docs/config/
(
config/extensions/
for the per-extension setup).
  • trigger-authoring-chat-agent
    技能 - 日常
    chat.agent({...})
    定义、生命周期钩子以及
    useTriggerChatTransport
    的常规使用路径。在使用本技能前,请先从该技能开始。
  • trigger-realtime
    技能 - 聊天传输层之外的实时钩子和前端流式传输。
  • trigger-tasks
    技能 - 基础
    task()
    语义、
    ctx
    以及标准生命周期钩子。
参考文档与本技能捆绑在同一个包中,可本地阅读(无需网络),并与你安装的版本保持一致。上方的
sources:
前置元数据列出了本技能引用的所有文档,均位于
@trigger.dev/sdk/docs/ai-chat/
下(包括
patterns/
)。对于人机协同、会话和子代理,请从
sessions.mdx
server-chat.mdx
client-protocol.mdx
patterns/human-in-the-loop.mdx
patterns/sub-agents.mdx
开始阅读。
关于聊天代理任务可能需要的
trigger.config.ts
和构建扩展(Prisma、Playwright、Python等),请阅读
@trigger.dev/sdk/docs/config/
下的捆绑配置文档(每个扩展的设置请查看
config/extensions/
)。

Version

版本

This skill is bundled inside
@trigger.dev/sdk
and read directly from
node_modules
, so it always matches your installed SDK version (see the adjacent
package.json
). The full documentation for these APIs ships alongside it under
@trigger.dev/sdk/docs/
.
本技能捆绑在
@trigger.dev/sdk
中,直接从
node_modules
读取,因此始终与你安装的SDK版本匹配(请查看相邻的
package.json
)。这些API的完整文档与本技能一起位于
@trigger.dev/sdk/docs/
下。