-
-
Notifications
You must be signed in to change notification settings - Fork 873
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: 136085f 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 useTriggerChat React hook and TriggerChatTransport implementation to enable Trigger.dev–backed streaming chat for the AI SDK. Introduces types, helpers (metadata parsing, SSE stream subscription, reconnection), and runtime peer-dependency checks. Exports the hook from the package index and updates packages/react-hooks/package.json to declare optional peer dependencies for "@ai-sdk/react", "@electric-sql/client", "ai", and "eventsource-parser". Includes a changelog entry marking a minor release. No other public API declarations were removed or altered. Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (3 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.
| function checkRequiredPeerDependencies() { | ||
| const requiredPackages = [ | ||
| "@ai-sdk/react", | ||
| "ai", | ||
| "@electric-sql/client", | ||
| "eventsource-parser", | ||
| ]; | ||
|
|
||
| const missing = requiredPackages.filter((pkg) => { | ||
| try { | ||
| require.resolve(pkg); | ||
| return false; | ||
| } catch { | ||
| return true; | ||
| } | ||
| }); | ||
|
|
||
| if (!missing.length) return; | ||
|
|
||
| const packages = missing.join(" "); | ||
|
|
||
| throw new Error( | ||
| `useTriggerChat requires the following packages:\n${missing | ||
| .map((pkg) => ` - ${pkg}`) | ||
| .join("\n")}\n\n` + | ||
| `Install them with:\n` + | ||
| ` npm install ${packages}\n` + | ||
| ` yarn add ${packages}\n` + | ||
| ` pnpm add ${packages}\n` + | ||
| ` bun add ${packages}`, | ||
| ); | ||
| } |
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.
Replace Node.js-specific require.resolve with isomorphic approach.
The require.resolve() call violates the coding guideline to prefer isomorphic code over Node.js-specific APIs. Since this runs in a client-side hook ("use client"), it may fail in browser-only environments or bundlers without Node.js polyfills.
Consider one of these approaches:
Option 1: Remove runtime check entirely (recommended)
-function checkRequiredPeerDependencies() {
- const requiredPackages = [
- "@ai-sdk/react",
- "ai",
- "@electric-sql/client",
- "eventsource-parser",
- ];
-
- const missing = requiredPackages.filter((pkg) => {
- try {
- require.resolve(pkg);
- return false;
- } catch {
- return true;
- }
- });
-
- if (!missing.length) return;
-
- const packages = missing.join(" ");
-
- throw new Error(
- `useTriggerChat requires the following packages:\n${missing
- .map((pkg) => ` - ${pkg}`)
- .join("\n")}\n\n` +
- `Install them with:\n` +
- ` npm install ${packages}\n` +
- ` yarn add ${packages}\n` +
- ` pnpm add ${packages}\n` +
- ` bun add ${packages}`,
- );
-}Rely on TypeScript types and package.json peerDependencies for compile-time checks instead.
Option 2: Check for actual exports (if runtime check is needed)
function checkRequiredPeerDependencies() {
- const requiredPackages = [
- "@ai-sdk/react",
- "ai",
- "@electric-sql/client",
- "eventsource-parser",
- ];
-
- const missing = requiredPackages.filter((pkg) => {
- try {
- require.resolve(pkg);
- return false;
- } catch {
- return true;
- }
- });
+ const missing: string[] = [];
+
+ try {
+ if (typeof ShapeStream === 'undefined') missing.push('@electric-sql/client');
+ } catch {}
+
+ try {
+ if (typeof EventSourceParserStream === 'undefined') missing.push('eventsource-parser');
+ } catch {}
+
+ try {
+ if (typeof useChat === 'undefined') missing.push('@ai-sdk/react');
+ } catch {}
if (!missing.length) return;
const packages = missing.join(" ");
throw new Error(
`useTriggerChat requires the following packages:\n${missing
.map((pkg) => ` - ${pkg}`)
.join("\n")}\n\n` +
`Install them with:\n` +
` npm install ${packages}\n` +
` yarn add ${packages}\n` +
` pnpm add ${packages}\n` +
` bun add ${packages}`,
);
}As per coding guidelines.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In packages/react-hooks/src/hooks/useTriggerChat.ts around lines 493 to 524, the
runtime check using Node's require.resolve is not isomorphic and can break in
browser/bundler environments; remove the runtime package-resolution logic and
either delete this function entirely or replace it with a no-op that documents
reliance on TypeScript types and package.json peerDependencies for compile-time
validation; if you must keep a runtime guard, instead perform safe dynamic
import checks only in non-browser/Node environments behind a typeof window ===
"undefined" guard and handle failures gracefully, but the preferred fix is to
remove the runtime check and any calls to it so the hook remains client-safe.
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
💯