-
-
Notifications
You must be signed in to change notification settings - Fork 876
feat(react-hooks): add TriggerChatTransport for AI SDK useChat #2644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(react-hooks): add TriggerChatTransport for AI SDK useChat #2644
Conversation
🦋 Changeset detectedLatest commit: b94f4f2 The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
WalkthroughAdds a new TriggerChatTransport class and accompanying useTriggerChat React hook that provide Trigger.dev–backed streaming chat integration for the AI SDK. Introduces types, helpers for SSE/stream subscription and metadata parsing, reconnect logic, access-token handling, and runtime abort/cleanup behavior. Exports the new hook from the package index, updates packages/react-hooks/package.json to declare four optional peer dependencies ( Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| * @remarks | ||
| * **CRITICAL:** Your Trigger.dev task MUST call `metadata.stream()` with the AI SDK stream. | ||
| * The stream key used in `metadata.stream()` must match the `streamKey` option (default: "chat"). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there may be a better place to put this piece of documentation
the tricky bit here is there are 2 external files required to enable this hook
- the trigger.dev task definition itself, which must call a
stream()method fromaiand forward the stream tometadata.stream() - the server action which invoked the trigger.dev task server-side
| * @example Trigger.dev task that streams AI responses: | ||
| * ```ts | ||
| * import { metadata, task } from "@trigger.dev/sdk/v3"; | ||
| * import { streamText } from "ai"; | ||
| * import { openai } from "@ai-sdk/openai"; | ||
| * | ||
| * export const chatTask = task({ | ||
| * id: "chat", | ||
| * run: async ({ messages }) => { | ||
| * const result = streamText({ | ||
| * model: openai("gpt-4"), | ||
| * messages, | ||
| * }); | ||
| * | ||
| * // CRITICAL: Stream to client using metadata.stream() | ||
| * await metadata.stream("chat", result.toUIMessageStream()); | ||
| * | ||
| * return { text: await result.text }; | ||
| * }, | ||
| * }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar to the above, it's a bit awkward adding this example here, because the task parameter actually just needs the string which is the id of the task ("chat" in the above example)
still unsure if this is the best way to document this
| function parseMetadata( | ||
| metadata: Record<string, unknown> | string | undefined | ||
| ): ParsedMetadata | undefined { | ||
| if (!metadata) return undefined; | ||
|
|
||
| if (typeof metadata === "string") { | ||
| try { | ||
| return JSON.parse(metadata) as ParsedMetadata; | ||
| } catch { | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
| if (typeof metadata === "object") { | ||
| return metadata as ParsedMetadata; | ||
| } | ||
|
|
||
| return undefined; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
normally I would use zod for this, but I think this matches patterns in other hooks
| * @remarks | ||
| * **CRITICAL SETUP REQUIREMENTS:** | ||
| * | ||
| * 1. Your Trigger.dev task MUST call `metadata.stream()` to stream responses: | ||
| * ```ts | ||
| * await metadata.stream("chat", result.toUIMessageStream()); | ||
| * ``` | ||
| * | ||
| * 2. You must provide a server action that calls `tasks.trigger()`: | ||
| * ```ts | ||
| * "use server"; | ||
| * export async function triggerChat(task: string, payload: unknown) { | ||
| * const handle = await tasks.trigger(task, payload); | ||
| * return { success: true, runId: handle.id, publicAccessToken: handle.publicAccessToken }; | ||
| * } | ||
| * ``` | ||
| * | ||
| * @example Complete setup with three files: | ||
| * | ||
| * **1. Trigger.dev task (src/trigger/chat.ts):** | ||
| * ```ts | ||
| * import { metadata, task } from "@trigger.dev/sdk/v3"; | ||
| * import { streamText } from "ai"; | ||
| * import { openai } from "@ai-sdk/openai"; | ||
| * | ||
| * export const chatTask = task({ | ||
| * id: "chat", | ||
| * run: async ({ messages }) => { | ||
| * const result = streamText({ model: openai("gpt-4"), messages }); | ||
| * // CRITICAL: Stream to client | ||
| * await metadata.stream("chat", result.toUIMessageStream()); | ||
| * return { text: await result.text }; | ||
| * }, | ||
| * }); | ||
| * ``` | ||
| * | ||
| * **2. Server action (src/actions.ts):** | ||
| * ```ts | ||
| * "use server"; | ||
| * import { tasks } from "@trigger.dev/sdk/v3"; | ||
| * | ||
| * export async function triggerChat(task: string, payload: unknown) { | ||
| * const handle = await tasks.trigger(task, payload); | ||
| * return { success: true, runId: handle.id, publicAccessToken: handle.publicAccessToken }; | ||
| * } | ||
| * ``` | ||
| * | ||
| * **3. Client component (src/components/Chat.tsx):** | ||
| * ```ts | ||
| * "use client"; | ||
| * import { useTriggerChat } from "@trigger.dev/react-hooks"; | ||
| * import { triggerChat } from "../actions"; | ||
| * | ||
| * export function Chat() { | ||
| * const { messages, input, handleInputChange, handleSubmit } = useTriggerChat({ | ||
| * transportOptions: { triggerTask: triggerChat } | ||
| * }); | ||
| * | ||
| * return ( | ||
| * <form onSubmit={handleSubmit}> | ||
| * {messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)} | ||
| * <input value={input} onChange={handleInputChange} /> | ||
| * <button type="submit">Send</button> | ||
| * </form> | ||
| * ); | ||
| * } | ||
| * ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the above comments apply here as well
…ok with a custom transport for trigger.tdev tasks
c4f800f to
ba97cb0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
.changeset/thirty-olives-confess.md(1 hunks)packages/react-hooks/package.json(1 hunks)packages/react-hooks/src/hooks/useTriggerChat.ts(1 hunks)packages/react-hooks/src/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations
Files:
packages/react-hooks/src/index.tspackages/react-hooks/src/hooks/useTriggerChat.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Applied to files:
packages/react-hooks/src/index.ts.changeset/thirty-olives-confess.mdpackages/react-hooks/package.jsonpackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-29T10:06:49.293Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-08-29T10:06:49.293Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from trigger.dev/core’s package.json
Applied to files:
packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling
Applied to files:
packages/react-hooks/src/index.ts.changeset/thirty-olives-confess.mdpackages/react-hooks/package.jsonpackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export every task (including subtasks) defined with task(), schedules.task(), or schemaTask()
Applied to files:
packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic
Applied to files:
packages/react-hooks/src/index.tspackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project
Applied to files:
packages/react-hooks/src/index.ts.changeset/thirty-olives-confess.mdpackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required
Applied to files:
packages/react-hooks/src/index.tspackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks
Applied to files:
packages/react-hooks/src/index.ts.changeset/thirty-olives-confess.mdpackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks
Applied to files:
packages/react-hooks/src/index.tspackages/react-hooks/package.jsonpackages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
Applied to files:
packages/react-hooks/src/index.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
🧬 Code graph analysis (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (2)
packages/core/src/v3/apiClientManager/index.ts (2)
accessToken(37-45)baseURL(32-35)references/d3-openai-agents/src/components/main-app.tsx (1)
useChat(20-86)
b3cb0cc to
6409d28
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (1)
268-269: Consider making API versions configurable.The hardcoded version headers may need updates as the Trigger.dev API evolves. Consider adding optional configuration for these versions in
TriggerChatTransportOptions.export interface TriggerChatTransportOptions< TPayload extends TriggerChatTaskPayload = TriggerChatTaskPayload, > { // ... existing options ... + /** + * Override Electric SQL version header + * @default "1.0.0-beta.1" + */ + electricVersion?: string; + + /** + * Override Trigger.dev API version header + * @default "2024-11-28" + */ + apiVersion?: string; }Then use them in the ShapeStream constructor:
const runStream = new ShapeStream({ url: runStreamUrl, headers: { Authorization: `Bearer ${accessToken}`, - "x-trigger-electric-version": "1.0.0-beta.1", - "x-trigger-api-version": "2024-11-28", + "x-trigger-electric-version": this.electricVersion ?? "1.0.0-beta.1", + "x-trigger-api-version": this.apiVersion ?? "2024-11-28", }, signal: runAbortController.signal, });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (2)
packages/react-hooks/package.json(1 hunks)packages/react-hooks/src/hooks/useTriggerChat.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/react-hooks/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations
Files:
packages/react-hooks/src/hooks/useTriggerChat.ts
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
🧬 Code graph analysis (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (1)
packages/core/src/v3/apiClientManager/index.ts (2)
accessToken(37-45)baseURL(32-35)
🔇 Additional comments (7)
packages/react-hooks/src/hooks/useTriggerChat.ts (7)
1-20: LGTM: Clean imports and sensible defaults.The imports are well-organized and the default constants provide good fallback values for common use cases.
33-132: LGTM: Comprehensive interface with excellent documentation.The inline JSDoc examples are appropriately placed and very helpful for users. Documenting critical setup requirements directly in the interface definition is a best practice, especially given the complexity of the three-file setup (task, server action, client component).
159-337: LGTM: Well-structured transport implementation.The class properly manages resources with cleanup handlers, handles errors appropriately, and provides both initial connection and reconnection capabilities. The token resolution logic correctly prioritizes stored tokens while providing fallback options.
339-361: LGTM: Pragmatic metadata parsing.The silent error handling and type casting are acceptable here given that
subscribeToDataStreamsdefensively validates the metadata structure. This approach matches existing patterns in the codebase and avoids adding zod as a dependency.
401-411: LGTM: Promise rejection now properly handled.The addition of
.catch()successfully addresses the previous review concern about unhandled promise rejections. The error is properly propagated to the controller and the run is aborted to ensure clean resource cleanup.
415-491: LGTM: Robust SSE streaming implementation.The stream handling is well-implemented with appropriate error handling. The decision to continue processing despite individual parse errors adds resilience, and the intentional reliance on
run.finishedAtfor cleanup (rather than stream end) ensures proper lifecycle management.
530-620: LGTM: Excellent documentation and clean implementation.The extensive three-file example is exactly what users need to understand the setup requirements. The documentation placement is ideal—comprehensive examples at the public API level guide developers through the entire integration. The hook implementation properly validates dependencies and delegates to
useChat.
…check Remove require.resolve() runtime check for optional peer dependencies. With optional peerDependencies in package.json, static ES imports provide better compile-time validation: - Build fails with clear errors only if useTriggerChat is imported - TypeScript/LSP shows missing dependencies immediately in IDE - Tree-shaking eliminates code when hook is unused - No Node.js-specific APIs in client-side "use client" code The previous runtime check using require.resolve() was: 1. Not isomorphic (fails in browser/bundler environments) 2. Redundant (static imports already enforce dependencies) 3. Worse UX (runtime errors instead of build-time errors)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/react-hooks/src/hooks/useTriggerChat.ts (2)
210-225: Verify silent error handling in reconnection.The
catchblock silently suppresses errors when reconnection fails, which appears intentional (returningnullsignals failure to the caller). However, this may hide useful debugging information from developers.Consider logging the error before suppressing it:
try { return this.subscribeToRun(runId, undefined); - } catch { + } catch (error) { + console.warn('Failed to reconnect to stream:', error); this.activeRuns.delete(options.chatId); return null; }This preserves the reconnection UX while aiding troubleshooting.
276-282: Document the type assertion for clarity.Line 281 uses a double type assertion (
as unknown as AnyRealtimeRun) to work around ShapeStream's generic message typing.Add a brief comment explaining why the cast is necessary:
(messages) => { for (const message of messages) { if (!("value" in message) || !message.value) continue; + // ShapeStream returns generic messages; cast to expected run type const run = message.value as unknown as AnyRealtimeRun;This helps future maintainers understand the type safety trade-off.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/react-hooks/src/hooks/useTriggerChat.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
For TypeScript, we usually use types over interfaces
Avoid enums
No default exports, use function declarations
Files:
packages/react-hooks/src/hooks/useTriggerChat.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Import Trigger.dev APIs from "trigger.dev/sdk/v3" when writing tasks or related utilities
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata API (metadata.current/get/set/append/stream, etc.) only inside run functions or lifecycle hooks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to trigger.config.ts : Configure global task lifecycle hooks (onStart/onSuccess/onFailure) only within trigger.config.ts if needed, not within arbitrary files
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Define tasks using task({ id, run, ... }) with a unique id per project
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use triggerAndWait() only from within a task context (not from generic app code) and handle result.ok or use unwrap() with error handling
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Do not use client.defineJob or any deprecated v2 patterns (e.g., eventTrigger) when defining tasks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schedules.task(...) for scheduled (cron) tasks; do not implement schedules as plain task() with external cron logic
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use schemaTask({ schema, run, ... }) to validate payloads when input validation is required
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : For idempotent child-task invocations, create and pass idempotencyKey (and optional TTL) when calling trigger()/batchTrigger() from tasks
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : When triggering a task multiple times in a loop from inside another task, use batchTrigger()/batchTriggerAndWait() instead of per-item trigger() calls
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to {packages/core,apps/webapp}/**/*.{ts,tsx} : We use zod a lot in packages/core and in the webapp
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-18T10:07:17.368Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-08-18T10:07:17.368Z
Learning: For Realtime subscriptions or React hooks, provide a Public Access Token and scope it appropriately (e.g., via TriggerAuthContext)
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-07-18T17:49:24.468Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-18T17:49:24.468Z
Learning: Applies to **/*.{ts,tsx} : Always prefer using isomorphic code like fetch, ReadableStream, etc. instead of Node.js specific code
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
📚 Learning: 2025-08-29T10:06:49.293Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-08-29T10:06:49.293Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from trigger.dev/core in the webapp, never import the root package path; always use one of the documented subpath exports from trigger.dev/core’s package.json
Applied to files:
packages/react-hooks/src/hooks/useTriggerChat.ts
🧬 Code graph analysis (1)
packages/react-hooks/src/hooks/useTriggerChat.ts (2)
packages/core/src/v3/apiClientManager/index.ts (2)
accessToken(37-45)baseURL(32-35)references/d3-openai-agents/src/components/main-app.tsx (1)
useChat(20-86)
🔇 Additional comments (9)
packages/react-hooks/src/hooks/useTriggerChat.ts (9)
1-16: LGTM!The imports follow the isomorphic code guideline, using client-safe libraries and types. The "use client" directive is correctly placed for this React hook.
18-20: LGTM!The default constants are sensible and align with the documented usage patterns.
22-138: LGTM!The type definitions are well-structured with comprehensive documentation and helpful examples. The use of generics provides good flexibility while maintaining type safety. Follows the coding guideline to prefer types over interfaces.
159-179: LGTM!The class structure and constructor are clean, with appropriate use of private fields and sensible defaults. The Map-based state management for activeRuns and runTokens is appropriate for tracking multiple concurrent conversations.
227-336: LGTM!The
buildPayloadandresolveAccessTokenmethods are clean and handle their respective concerns well. The access token resolution follows the learned pattern of supporting both static and dynamic tokens (string or function). Good error messaging when tokens are missing.Based on learnings
343-361: LGTM!The metadata parsing handles both string and object formats safely with appropriate error recovery. The simple approach is sufficient for internal parsing of trusted Trigger.dev metadata.
367-413: LGTM!The stream subscription logic correctly checks for available streams and subscribes to matching keys. The error handling for the async
streamDataFromTriggercall properly catches rejections and propagates them to the controller, addressing the previous review concern about unhandled promise rejections.
419-491: LGTM!The SSE streaming implementation is robust with good error handling:
- Properly uses EventSourceParserStream for parsing
- Warns on unparseable chunks without breaking the stream
- Ignores AbortError to allow graceful cancellation
- Correctly delegates stream closure to the run lifecycle (line 464 comment)
The fetch-based approach follows the isomorphic code guideline.
493-586: LGTM!The
useTriggerChathook is clean and well-documented:
- Simple composition pattern delegating to AI SDK's
useChat- Comprehensive three-file example covers task definition, server action, and client usage
- Follows coding guidelines (no default export, function declaration, isomorphic code)
- Type-safe integration with AI SDK types
The extensive documentation addresses setup complexity effectively.
| const runStreamUrl = `${baseURL}/realtime/v1/runs/${runId}`; | ||
| const runStream = new ShapeStream({ | ||
| url: runStreamUrl, | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| "x-trigger-electric-version": "1.0.0-beta.1", | ||
| "x-trigger-api-version": "2024-11-28", | ||
| }, | ||
| signal: runAbortController.signal, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded API version headers may require maintenance.
Lines 268-269 contain hardcoded version strings that may need updates as the Trigger.dev API evolves. If these versions fall out of sync with the SDK, stream subscriptions could fail.
Consider one of these approaches:
Option 1: Import from a shared constants file
import { ELECTRIC_VERSION, API_VERSION } from '@trigger.dev/core/v3/constants';Option 2: Make versions configurable via TriggerChatTransportOptions
export interface TriggerChatTransportOptions<...> {
baseURL?: string;
+ electricVersion?: string;
+ apiVersion?: string;
// ...
}Then use:
headers: {
Authorization: `Bearer ${accessToken}`,
- "x-trigger-electric-version": "1.0.0-beta.1",
- "x-trigger-api-version": "2024-11-28",
+ "x-trigger-electric-version": this.electricVersion ?? "1.0.0-beta.1",
+ "x-trigger-api-version": this.apiVersion ?? "2024-11-28",
},🤖 Prompt for AI Agents
In packages/react-hooks/src/hooks/useTriggerChat.ts around lines 263 to 272, the
ShapeStream headers currently hardcode "x-trigger-electric-version" and
"x-trigger-api-version", which will break when versions change; replace the
hardcoded strings by sourcing versions from a shared constant or configuration:
either import ELECTRIC_VERSION and API_VERSION from the SDK/shared constants
module and use those variables in the headers, or add them to
TriggerChatTransportOptions (with sensible defaults) and read from options when
constructing ShapeStream; ensure any new import path is added to package
dependencies and update the hook signature/defaults accordingly.
Closes #2634
✅ Checklist
Summary
Adds a custom transport implementation for AI SDK's
useChathook that integrates with Trigger.dev background tasks. This enables long-running AI conversations by triggering tasks, subscribing to realtime run updates via Electric SQL, and streaming AI responses via Server-Sent Events (SSE).Changes
useTriggerChat- A drop-in replacement for AI SDK'suseChatthat works with Trigger.dev tasksTriggerChatTransport- Custom transport implementation following AI SDK's transport patternai(^5.0.82),@ai-sdk/react(^2.0.14), andeventsource-parser(^3.0.0)Technical Details
Architecture
EventSourceParserStreamfor streaming AI responsesuseRealtimehookDeveloper Requirements
Important: Developers must create their own server action to trigger tasks. Since
useTriggerChatis a client-side hook, it cannot directly calltasks.trigger()(which requires server-side execution). ThetriggerTaskoption expects a server action that:tasks.trigger()on the server{ success: true, runId, publicAccessToken }Error Handling
Testing
Manually tested with a local project using
pnpm patchto verify:Usage Example
1. Define your Trigger.dev task (e.g.
src/trigger/chat.ts):2. Create a server action (e.g.
src/actions.ts):3. Use the hook in your component (e.g.
src/components/Chat.tsx):Changelog
Added
useTriggerChathook to@trigger.dev/react-hooksthat provides AI SDKuseChatintegration with Trigger.dev background tasks. Enables long-running AI conversations with realtime streaming via custom transport implementation.New exports:
useTriggerChat- Hook for AI chat integrationTriggerChatTransport- Custom transport classTriggerChatTransportOptions- Transport configuration typeTriggerChatTaskPayload- Task payload typeNew dependencies:
ai@^5.0.82@ai-sdk/react@^2.0.14eventsource-parser@^3.0.0Resources
Screenshots
N/A - This is a developer-facing hook with no UI
💯