mcpField
T
trigger-realtime

trigger-realtime

github.com/triggerdotdev开发352.9k 次安装1 周前更新
代码仓库

关于 trigger-realtime

面向 Trigger.dev 前端开发的实时数据与流处理指南,适用于构建 React、Next.js 或 Remix 界面。介绍运行状态订阅、浏览器触发任务及读写流等模式。

主要功能

  1. 01订阅实时运行状态
  2. 02渲染 AI 与文本流
  3. 03从浏览器触发任务
  4. 04向运行中的任务发送输入
  5. 05通过 React 完成等待令牌

适用场景

  1. 01在前端显示任务进度
  2. 02构建 AI 输出流界面
  3. 03从浏览器提交任务并跟踪运行
  4. 04通过界面处理任务审批

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/triggerdotdev/skills --skill trigger-realtime

安装后在 Claude Code 中输入/trigger-realtime确认已加载。

详细介绍

实时功能与前端

Trigger.dev 运行状态和流的消费端:读取运行的实时更新、渲染 AI/文本流,并从浏览器触发任务。钩子来自 @trigger.dev/react-hooks;令牌生成和后端订阅来自 @trigger.dev/sdk。

设置

npm add @trigger.dev/react-hooks   # frontend hooks (React/Next.js/Remix)
# @trigger.dev/sdk is already installed for the backend

流程始终是:在后端生成作用域受限的令牌,将其传递给前端,再使用钩子订阅。

// backend (API route / server action)
import { auth } from "@trigger.dev/sdk";

const publicAccessToken = await auth.createPublicToken({
  scopes: { read: { runs: ["run_1234"] } }, // a token with no scopes is useless
});
// frontend
"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";

export function RunStatus({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { run, error } = useRealtimeRun(runId, { accessToken: publicAccessToken });
  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;
  return <div>Run: {run.status}</div>;
}

令牌分为两种:Public Access Tokens(读取/订阅,通过 auth.createPublicToken 生成)和 Trigger Tokens(从浏览器触发,单次使用,通过 auth.createTriggerPublicToken 生成)。两者的默认有效期均为 15 分钟。

核心模式

1. 订阅运行并渲染元数据进度

metadata 的类型为 Record<string, DeserializedJson>,因此嵌套值需要进行类型转换。

"use client";
import { useRealtimeRun } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function Progress({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { run, error } = useRealtimeRun<typeof myTask>(runId, { accessToken: publicAccessToken });
  if (error) return <div>Error: {error.message}</div>;
  if (!run) return <div>Loading...</div>;
  const progress = run.metadata?.progress as { percentage?: number } | undefined;
  return <div>{run.status}: {progress?.percentage ?? 0}%</div>;
}

传入 onComplete: (run, error) => {},即可在运行结束时作出响应。

2. 使用 skipColumns 仅订阅状态

对于徽标或进度条,无需使用 payload/output。跳过这些字段可减小传输数据量,并避免出现“Large HTTP Payload”警告。

const { run } = useRealtimeRun(runId, {
  accessToken: publicAccessToken,
  skipColumns: ["payload", "output"],
});

你可以跳过以下任意字段:payload、output、metadata、startedAt、delayUntil、queuedAt、expiredAt、completedAt、number、isTest、usageDurationMs、costInCents、baseCostInCents、ttl、payloadType、outputType、runTags、error。

3. 使用 Trigger Token 从浏览器触发任务

此处的 accessToken 是 Trigger Token(通过 auth.createTriggerPublicToken 生成),而不是 Public Access Token。

"use client";
import { useTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function TriggerButton({ triggerToken }: { triggerToken: string }) {
  const { submit, handle, isLoading } = useTaskTrigger<typeof myTask>("my-task", {
    accessToken: triggerToken,
  });
  if (handle) return <div>Run ID: {handle.id}</div>;
  return (
    <button onClick={() => submit({ foo: "bar" }, { tags: ["user:123"] })} disabled={isLoading}>
      {isLoading ? "Triggering..." : "Run"}
    </button>
  );
}

submit(payload, options?) 接受与后端 trigger 调用相同的选项。

4. 在一个钩子中触发任务并订阅
"use client";
import { useRealtimeTaskTrigger } from "@trigger.dev/react-hooks";
import type { myTask } from "@/trigger/myTask";

export function Runner({ publicAccessToken }: { publicAccessToken: string }) {
  const { submit, run, isLoading } = useRealtimeTaskTrigger<typeof myTask>("my-task", {
    accessToken: publicAccessToken,
  });
  if (run) return <div>{run.status}</div>;
  return <button onClick={() => submit({ foo: "bar" })} disabled={isLoading}>Run</button>;
}

如果还需要任务的流,请使用 useRealtimeTaskTriggerWithStreams<typeof myTask, STREAMS>(它会返回 { submit, run, streams, error, isLoading })。

5. 消费 AI/文本流(SDK 4.1.0+,推荐)

useRealtimeStream 接受一个已定义的流以获得完整的类型安全性,也可以接受 runId 和可选的流键。返回 { parts, error }。

"use client";
import { useRealtimeStream } from "@trigger.dev/react-hooks";
import { aiStream } from "@/trigger/streams"; // a defined stream -> typed parts

export function StreamView({ runId, publicAccessToken }: { runId: string; publicAccessToken: string }) {
  const { parts, error } = useRealtimeStream(aiStream, runId, {
    accessToken: publicAccessToken,
    timeoutInSeconds: 300, // default 60
    onData: (chunk) => console.log(chunk),
  });
  if (error) return <div>Error: {error.message}</div>;
  if (!parts) return <div>Loading...</div>;
  return <div>{parts.join("")}</div>;
}

不使用已定义的流时:useRealtimeStream<string>(runId, "ai-output", { accessToken }),也可以省略键以使用默认流。其他选项:baseURL、startIndex、throttleInMs(默认值为 16)。如果需要同时获取运行和所有流,仍可使用旧版 useRealtimeRunWithStreams(runId, options) 钩子。

6. 向正在运行的任务发送输入
"use client";
import { useInputStreamSend } from "@trigger.dev/react-hooks";
import { approval } from "@/trigger/streams";

export function ApprovalForm({ runId, accessToken }: { runId: string; accessToken: string }) {
  const { send, isLoading, isReady } = useInputStreamSend(approval.id, runId, { accessToken });
  return (
    <button disabled={!isReady || isLoading} onClick={() => send({ approved: true })}>
      Approve
    </button>
  );
}
7. 从 React 完成等待令牌
// backend: create the token, return id + publicAccessToken to the frontend
import { wait } from "@trigger.dev/sdk";
const token = await wait.createToken({ timeout: "10m" });
return { tokenId: token.id, publicToken: token.publicAccessToken };
"use client";
import { useWaitToken } from "@trigger.dev/react-hooks";

export function Approve({ tokenId, publicToken }: { tokenId: string; publicToken: string }) {
  const { complete } = useWaitToken(tokenId, { accessToken: publicToken });
  return <button onClick={() => complete({ approved: true })}>Approve</button>;
}
8. 从后端订阅(异步迭代器)
import { runs, tasks } from "@trigger.dev/sdk";
import type { myTask } from "./trigger/my-task";

const handle = await tasks.trigger("my-task", { some: "data" });
for await (const run of runs.subscribeToRun<typeof myTask>(handle.id)) {
  console.log(run.payload.some, run.output?.some); // typed
}

runs.subscribeToRun 会在运行结束时完成,因此循环会自动退出。

常见错误

  1. 严重:使用公共访问令牌从浏览器触发任务。 createPublicToken 返回的读取令牌无法触发任务。

    • 错误:useTaskTrigger("my-task", { accessToken: publicAccessTokenFromCreatePublicToken })
    • 正确:使用 auth.createTriggerPublicToken("my-task") 创建一次性触发令牌,并将其传入。
  2. 令牌没有作用域。 不带作用域的令牌无法授权任何操作,因此每次订阅都会返回 403。

    • 错误:await auth.createPublicToken()
    • 正确:await auth.createPublicToken({ scopes: { read: { runs: ["run_1234"] } } })
  3. 使用 useRun/SWR 轮询实时更新。 useRun 是基于 SWR 的管理 API Hook(不建议用于实时状态);如果确实要用,请将 refreshInterval: 0 设为停止轮询。

    • 错误:使用 useRun(runId, { refreshInterval: 1000 }) 跟踪进度
    • 正确:useRealtimeRun(runId, { accessToken })(无需轮询,也无需设置 WebSocket)
  4. 忘记添加 "use client"。 实时/触发 Hook 无法在服务端组件中运行。

    • 错误:在 Next.js App Router 服务端组件中使用 useRealtimeRun
    • 正确:在使用这些 Hook 的任意组件顶部添加 "use client";。
  5. 传输不会呈现的 payload/output。

    • 错误:为了显示状态徽标而使用 useRealtimeRun(runId, { accessToken })(会通过网络传输大型负载)
    • 正确:useRealtimeRun(runId, { accessToken, skipColumns: ["payload", "output"] })
  6. 在句柄尚不存在时订阅。

    • 错误:不添加条件保护,直接使用 useRealtimeRun(handle, { accessToken: handle?.publicAccessToken })
    • 正确:添加 enabled: !!handle,确保仅在触发器返回句柄后才订阅。

参考资料

相关技能:

  • trigger-tasks,用于任务端:streams.define()、metadata.set() 和 wait.createToken。
  • trigger-authoring-chat-agent 和 trigger-chat-agent-advanced,用于聊天 Agent,它们基于这些实时流构建。

参考文档与此技能一同包含在同一软件包中,请在本地读取(无需联网),并使用与你安装版本匹配的文档。上方的 sources: frontmatter 列出了此技能所依据的所有文档,均位于 @trigger.dev/sdk/docs/ 下。请从以下文档开始:

  • @trigger.dev/sdk/docs/realtime/react-hooks/subscribe.mdx
  • @trigger.dev/sdk/docs/realtime/react-hooks/streams.mdx
  • @trigger.dev/sdk/docs/realtime/auth.mdx
  • @trigger.dev/sdk/docs/realtime/run-object.mdx(实时运行对象与 useRun 返回的管理 API 对象不同)

版本

此技能包含在 @trigger.dev/sdk 中,并直接从 node_modules 读取,因此始终与你安装的 SDK 版本匹配(请参见旁边的 package.json)。这些 API 的完整文档也随其一同包含在 @trigger.dev/sdk/docs/ 下。

评论

支持 Markdown