feat(sdk): AI SDK custom useChat transport & chat.task harness#3173
feat(sdk): AI SDK custom useChat transport & chat.task harness#3173
Conversation
New package that provides a custom AI SDK ChatTransport implementation bridging Vercel AI SDK's useChat hook with Trigger.dev's durable task execution and realtime streams. Key exports: - TriggerChatTransport class implementing ChatTransport<UIMessage> - createChatTransport() factory function - ChatTaskPayload type for task-side typing - TriggerChatTransportOptions type The transport triggers a Trigger.dev task with chat messages as payload, then subscribes to the task's realtime stream to receive UIMessageChunk data, which useChat processes natively. Co-authored-by: Eric Allam <eric@trigger.dev>
Tests cover: - Constructor with required and optional options - sendMessages triggering task and returning UIMessageChunk stream - Correct payload structure sent to trigger API - Custom streamKey in stream URL - Extra headers propagation - reconnectToStream with existing and non-existing sessions - createChatTransport factory function - Error handling for API failures - regenerate-message trigger type Co-authored-by: Eric Allam <eric@trigger.dev>
- Cache ApiClient instance instead of creating per-call - Add streamTimeoutSeconds option for customizable stream timeout - Clean up subscribeToStream method (remove unused variable) - Improve JSDoc with backend task example - Minor code cleanup Co-authored-by: Eric Allam <eric@trigger.dev>
Adds 3 additional test cases: - Abort signal gracefully closes the stream - Multiple independent chat sessions tracked correctly - ChatRequestOptions.body is merged into task payload Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: Eric Allam <eric@trigger.dev>
ChatSessionState is an implementation detail of the transport's session tracking. Users don't need to access it since the sessions map is private. Co-authored-by: Eric Allam <eric@trigger.dev>
The accessToken option now accepts either a string or a function
returning a string. This enables dynamic token refresh patterns:
new TriggerChatTransport({
taskId: 'my-task',
accessToken: () => getLatestToken(),
})
The function is called on each sendMessages() call, allowing fresh
tokens to be used for each task trigger.
Co-authored-by: Eric Allam <eric@trigger.dev>
Use the already-resolved token when creating ApiClient instead of calling resolveAccessToken() again through getApiClient(). Co-authored-by: Eric Allam <eric@trigger.dev>
Two new subpath exports: @trigger.dev/sdk/chat (frontend, browser-safe): - TriggerChatTransport — ChatTransport implementation for useChat - createChatTransport() — factory function - TriggerChatTransportOptions type @trigger.dev/sdk/ai (backend, adds to existing ai.tool/ai.currentToolOptions): - chatTask() — pre-typed task wrapper with auto-pipe - pipeChat() — pipe StreamTextResult to realtime stream - CHAT_STREAM_KEY constant - ChatTaskPayload type - ChatTaskOptions type - PipeChatOptions type Co-authored-by: Eric Allam <eric@trigger.dev>
Move and adapt tests from packages/ai to packages/trigger-sdk. - Import from ./chat.js instead of ./transport.js - Use 'task' option instead of 'taskId' - All 17 tests passing Co-authored-by: Eric Allam <eric@trigger.dev>
All functionality now lives in: - @trigger.dev/sdk/chat (frontend transport) - @trigger.dev/sdk/ai (backend chatTask, pipeChat) Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: Eric Allam <eric@trigger.dev>
1. Add null/object guard before enqueuing UIMessageChunk from SSE stream to handle heartbeat or malformed events safely 2. Use incrementing counter instead of Date.now() in test message factories to avoid duplicate IDs 3. Add test covering publicAccessToken from trigger response being used for stream subscription auth Co-authored-by: Eric Allam <eric@trigger.dev>
Comprehensive guide covering: - Quick start with chatTask + TriggerChatTransport - Backend patterns: simple (return streamText), complex (pipeChat), and manual (task + ChatTaskPayload) - Frontend options: dynamic tokens, extra data, self-hosting - ChatTaskPayload reference - Added to Writing tasks navigation near Streams Co-authored-by: Eric Allam <eric@trigger.dev>
Minimal example showcasing the new chatTask + TriggerChatTransport APIs: - Backend: chatTask with streamText auto-pipe (src/trigger/chat.ts) - Frontend: TriggerChatTransport with useChat (src/components/chat.tsx) - Token generation via auth.createTriggerPublicToken (src/app/page.tsx) - Tailwind v4 styling Co-authored-by: Eric Allam <eric@trigger.dev>
…delMessages @ai-sdk/openai v3 and @ai-sdk/react v3 are needed for ai v6 compatibility. convertToModelMessages is async in newer AI SDK versions. Co-authored-by: Eric Allam <eric@trigger.dev>
🦋 Changeset detectedLatest commit: cfe56ab The changes in this PR will be included in the next version bump. This PR includes changesets to release 29 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 |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a browser-safe chat transport and factory ( Estimated code review effort🎯 5 (Critical) | ⏱️ ~150 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/trigger-sdk/src/v3/chat.test.ts (1)
22-23: Consider resetting messageIdCounter in beforeEach.The
messageIdCounteris a module-level variable that accumulates across all tests. While this doesn't cause functional issues (IDs just need to be unique within each test), resetting it inbeforeEachwould make tests more deterministic and easier to debug.♻️ Suggested fix
beforeEach(() => { originalFetch = global.fetch; + messageIdCounter = 0; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/chat.test.ts` around lines 22 - 23, The module-level variable messageIdCounter in chat.test.ts accumulates across tests making IDs non-deterministic; add a beforeEach hook in the test file that resets messageIdCounter = 0 so each test starts with a fresh counter (locate the variable by name messageIdCounter and place the reset inside the existing or new beforeEach block in the test suite).packages/trigger-sdk/src/v3/chat-react.ts (1)
76-84: Good memoization pattern, but options are captured only on first render.The
useRefpattern correctly preserves the transport instance across re-renders. Note that ifbaseURL,headers,streamKey, or other options change after the initial render, the transport won't pick up those changes. This is likely intentional (and documented via "created once"), but worth keeping in mind if callers expect reactive updates to options other thanaccessToken.The
accessTokenfunction pattern works correctly for dynamic tokens since it's called per-request.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/chat-react.ts` around lines 76 - 84, The hook useTriggerChatTransport currently creates a single TriggerChatTransport with the options captured only on first render, so changes to baseURL, headers, streamKey, etc. after mount are ignored; modify the hook to either (A) expose or call an update method on the transport (e.g., ref.current.updateOptions or ref.current.setConfig) and update relevant fields when options.baseURL/headers/streamKey change, or (B) recreate the transport when those non-dynamic options change by tracking them in a useEffect and replacing ref.current = new TriggerChatTransport(options) (preserving any needed cleanup), while keeping the accessToken-as-function behavior unchanged so per-request tokens remain dynamic. Ensure you reference useTriggerChatTransport and TriggerChatTransport and only treat accessToken as dynamically invoked per request.packages/trigger-sdk/src/v3/chat.ts (1)
198-212: Consider logging the error for debugging.The silent catch when sending to an existing run's input stream makes debugging harder when things go wrong unexpectedly. While the fallthrough to trigger a new run is correct, a debug-level log could help diagnose issues.
🔧 Optional: Add debug logging
} catch { - // If sending fails (run died, etc.), fall through to trigger a new run. + // If sending fails (run died, etc.), fall through to trigger a new run. + // Note: Consider adding debug logging here if debugging becomes difficult this.sessions.delete(chatId); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/chat.ts` around lines 198 - 212, The catch block swallowing errors after ApiClient.sendInputStream makes failures hard to trace; update the catch to log the caught error (including context like session.runId and chatId) before deleting the session so you still fall through to start a new run — use the class logger (e.g., this.logger.debug) if available or console.debug as a fallback, referencing ApiClient.sendInputStream, subscribeToStream, and this.sessions.delete to locate the code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.scratch/plan-graceful-oversized-batch-items.md:
- Around line 216-238: The fenced code block that starts with "NDJSON bytes
arrive" is missing a language identifier which triggers markdownlint MD040;
update the opening fence to include a language (e.g., change ``` to ```text) so
the block is explicitly marked as plain text (ensure the closing fence remains
```), preserving the existing block contents and indentation.
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 560-563: The handler currently captures multiple messages into
pendingMessages via messagesInput.on (msgSub) but later only consumes the first
entry, discarding the rest; change the logic that extracts from pendingMessages
(where it currently uses a single element) to drain and preserve the entire
backlog — e.g., move all items from pendingMessages into the processing queue
(or append them to the existing messages array) before proceeding, then clean up
the subscription (msgSub) as before so no buffered messages are lost; update
references to pendingMessages, msgSub, and messagesInput.on to reflect this
full-drain behavior.
- Around line 268-269: The global _chatPipeCount is race-prone; make the pipe
counter scoped to each chat run/turn instead. Remove the module-global
_chatPipeCount and initialize a run-scoped counter (for example add a numeric
property like run.__chatPipeCount or turn.chatPipeCount on the ChatRun/ChatTurn
object when a run/turn is created or at the start of the function that currently
uses _chatPipeCount), then replace every reference to _chatPipeCount (including
uses around lines 360-361 and 548-583) with the run/turn-scoped property and
update increment/decrement logic to use that property so concurrent runs don’t
share state. Ensure the counter is initialized to 0 at run start and cleaned up
or left on the run object when finished.
---
Nitpick comments:
In `@packages/trigger-sdk/src/v3/chat-react.ts`:
- Around line 76-84: The hook useTriggerChatTransport currently creates a single
TriggerChatTransport with the options captured only on first render, so changes
to baseURL, headers, streamKey, etc. after mount are ignored; modify the hook to
either (A) expose or call an update method on the transport (e.g.,
ref.current.updateOptions or ref.current.setConfig) and update relevant fields
when options.baseURL/headers/streamKey change, or (B) recreate the transport
when those non-dynamic options change by tracking them in a useEffect and
replacing ref.current = new TriggerChatTransport(options) (preserving any needed
cleanup), while keeping the accessToken-as-function behavior unchanged so
per-request tokens remain dynamic. Ensure you reference useTriggerChatTransport
and TriggerChatTransport and only treat accessToken as dynamically invoked per
request.
In `@packages/trigger-sdk/src/v3/chat.test.ts`:
- Around line 22-23: The module-level variable messageIdCounter in chat.test.ts
accumulates across tests making IDs non-deterministic; add a beforeEach hook in
the test file that resets messageIdCounter = 0 so each test starts with a fresh
counter (locate the variable by name messageIdCounter and place the reset inside
the existing or new beforeEach block in the test suite).
In `@packages/trigger-sdk/src/v3/chat.ts`:
- Around line 198-212: The catch block swallowing errors after
ApiClient.sendInputStream makes failures hard to trace; update the catch to log
the caught error (including context like session.runId and chatId) before
deleting the session so you still fall through to start a new run — use the
class logger (e.g., this.logger.debug) if available or console.debug as a
fallback, referencing ApiClient.sendInputStream, subscribeToStream, and
this.sessions.delete to locate the code to change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ffd316a7-f5af-4416-88b4-2e7c865da47c
⛔ Files ignored due to path filters (13)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlreferences/ai-chat/next-env.d.tsis excluded by!references/**references/ai-chat/next.config.tsis excluded by!references/**references/ai-chat/package.jsonis excluded by!references/**references/ai-chat/postcss.config.mjsis excluded by!references/**references/ai-chat/src/app/actions.tsis excluded by!references/**references/ai-chat/src/app/globals.cssis excluded by!references/**references/ai-chat/src/app/layout.tsxis excluded by!references/**references/ai-chat/src/app/page.tsxis excluded by!references/**references/ai-chat/src/components/chat.tsxis excluded by!references/**references/ai-chat/src/trigger/chat.tsis excluded by!references/**references/ai-chat/trigger.config.tsis excluded by!references/**references/ai-chat/tsconfig.jsonis excluded by!references/**
📒 Files selected for processing (18)
.changeset/ai-sdk-chat-transport.md.claude/rules/package-installation.md.scratch/plan-graceful-oversized-batch-items.mdCLAUDE.mddocs/docs.jsondocs/guides/ai-chat.mdxpackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/noopManager.tspackages/core/src/v3/inputStreams/types.tspackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/package.jsonpackages/trigger-sdk/src/v3/ai.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/streams.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: sdk-compat / Bun Runtime
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: typecheck / typecheck
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: In TypeScript SDK usage, always import from@trigger.dev/sdk, never from@trigger.dev/sdk/v3or use deprecated client.defineJob
Import from@trigger.dev/coresubpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/inputStreams/types.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use zod for validation in packages/core and apps/webapp
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tspackages/core/src/v3/inputStreams/types.tspackages/core/src/v3/realtimeStreams/types.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/inputStreams/types.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/inputStreams/types.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tsdocs/docs.jsonpackages/trigger-sdk/src/v3/chat.tsCLAUDE.mdpackages/core/src/v3/inputStreams/types.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.jsonpackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.ts
packages/core/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/core/CLAUDE.md)
Never import the root package (
@trigger.dev/core). Always use subpath imports such as@trigger.dev/core/v3,@trigger.dev/core/v3/utils,@trigger.dev/core/logger, or@trigger.dev/core/schemas
Files:
packages/core/src/v3/inputStreams/manager.tspackages/core/src/v3/inputStreams/index.tspackages/core/src/v3/inputStreams/noopManager.tspackages/core/src/v3/inputStreams/types.tspackages/core/src/v3/realtimeStreams/types.ts
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
Docs in docs/ directory should use Mintlify MDX format following conventions in docs/CLAUDE.md
Files:
docs/guides/ai-chat.mdx
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.mdx: MDX documentation pages must include frontmatter with title (required), description (required), and sidebarTitle (optional) in YAML format
Use Mintlify components for structured content: , , , , , , /, /
Always import from@trigger.dev/sdkin code examples (never from@trigger.dev/sdk/v3)
Code examples must be complete and runnable where possible
Use language tags in code fences:typescript,bash,json
Files:
docs/guides/ai-chat.mdx
docs/**/docs.json
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/docs.json: Main documentation config must be defined indocs.jsonwhich includes navigation structure, theme, and metadata
Navigation structure indocs.jsonshould be organized usingnavigation.dropdownswith groups and pages
Files:
docs/docs.json
packages/trigger-sdk/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/src/v3/ai.ts
packages/trigger-sdk/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/trigger-sdk/CLAUDE.md)
Always import from
@trigger.dev/sdk. Never use@trigger.dev/sdk/v3(deprecated path alias)
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use vitest for all tests in the Trigger.dev repository
Files:
packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptivedescribeanditblocks
Tests should avoid mocks or stubs and use the helpers from@internal/testcontainerswhen Redis or Postgres are needed
Use vitest for running unit tests
Files:
packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx,js}: Use vitest exclusively for testing - never mock anything, use testcontainers instead
Place test files next to source files with .test.ts naming convention (e.g., MyService.ts -> MyService.test.ts)
Test files using Redis or PostgreSQL should use testcontainers helpers (redisTest, postgresTest, containerTest) instead of mocks
Files:
packages/trigger-sdk/src/v3/chat.test.ts
🧠 Learnings (51)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Keep SDK documentation in `rules/` and `.claude/skills/trigger-dev-tasks/` synchronized when features are added or changed
Applied to files:
docs/guides/ai-chat.mdxCLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.mdpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:43:37.906Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Exercise caution with changes to trigger.dev/core as they affect both the customer-facing SDK and server-side webapp - breaking changes can impact deployed user tasks and the platform simultaneously
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.ts.changeset/ai-sdk-chat-transport.mdpackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-react.ts.changeset/ai-sdk-chat-transport.mdpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.trigger()` to trigger a task from inside another task with specified payload
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.mdpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.mdpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `TriggerAuthContext` provider to supply Public Access Token to Trigger.dev React hooks
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat-react.ts
📚 Learning: 2026-02-25T17:28:20.456Z
Learnt from: isshaddad
Repo: triggerdotdev/trigger.dev PR: 3130
File: docs/v3-openapi.yaml:3134-3135
Timestamp: 2026-02-25T17:28:20.456Z
Learning: In the Trigger.dev codebase, the `publicAccessToken` returned by the SDK's `wait.createToken()` method is not part of the HTTP response body from `POST /api/v1/waitpoints/tokens`. The server returns only `{ id, isCached, url }`. The SDK's `prepareData` hook generates the JWT client-side from the `x-trigger-jwt-claims` response header after the HTTP call completes. The OpenAPI spec correctly documents only the HTTP response body, not SDK transformations.
<!-- [/add_learning]
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.md
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.batchTrigger()` to trigger multiple runs of a single task with different payloads
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.test.ts.scratch/plan-graceful-oversized-batch-items.md
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Organize documentation across appropriate directories: `documentation/` for core conceptual docs, `guides/` for how-to guides, `config/` for configuration reference, `deployment/` for deployment guides, `tasks/` for task documentation, `realtime/` for real-time features, `runs/` for run management, and `images/` for assets
Applied to files:
docs/docs.json
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.md
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-constants.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.mdpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.jsonpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : In TypeScript SDK usage, always import from trigger.dev/sdk, never from trigger.dev/sdk/v3 or use deprecated client.defineJob
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.md
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to packages/**/*.{ts,tsx,js},integrations/**/*.{ts,tsx,js} : When modifying public packages (packages/* or integrations/*), add a changeset via pnpm run changeset:add
Applied to files:
CLAUDE.md.claude/rules/package-installation.mdpackages/trigger-sdk/package.json
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Do NOT update `.claude/skills/trigger-dev-tasks/` directory files unless explicitly asked - these are maintained in separate dedicated passes
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/.claude/skills/trigger-dev-tasks/**/* : Update `.claude/skills/trigger-dev-tasks/` in parallel with `rules/` when SDK features change
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to docs/**/*.{md,mdx} : Docs in docs/ directory should use Mintlify MDX format following conventions in docs/CLAUDE.md
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/rules/**/* : Update `rules/` directory with versioned SDK documentation when SDK features change
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: This is a pnpm 10.23.0 monorepo using Turborepo - run commands from root with pnpm run
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2025-11-27T16:26:44.496Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/executing-commands.mdc:0-0
Timestamp: 2025-11-27T16:26:44.496Z
Learning: Execute most monorepo commands using `pnpm run` from the root directory, with `--filter` flag for specific packages (e.g., `pnpm run dev --filter webapp`)
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2025-11-27T16:26:44.496Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/executing-commands.mdc:0-0
Timestamp: 2025-11-27T16:26:44.496Z
Learning: For running tests, navigate into the package directory and run `pnpm run test --run` to enable single-file test execution (e.g., `pnpm run test ./src/engine/tests/ttl.test.ts --run`)
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2026-01-15T10:48:02.687Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-15T10:48:02.687Z
Learning: Use pnpm as the package manager (version 10.23.0 or later) and Node.js 20.20.0
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2025-11-27T16:26:47.602Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/repo.mdc:0-0
Timestamp: 2025-11-27T16:26:47.602Z
Learning: Refer to the monorepo structure documentation at repo.md before making changes or adding new files
Applied to files:
CLAUDE.md.claude/rules/package-installation.md
📚 Learning: 2026-03-02T12:43:17.177Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: internal-packages/database/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:17.177Z
Learning: Edit Prisma schema at `prisma/schema.prisma` and generate migrations using `pnpm run db:migrate:dev:create --name "descriptive_name"` from the `internal-packages/database` directory
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-02T12:43:37.906Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Applies to packages/core/**/*.{ts,tsx,js,jsx} : Never import the root package (trigger.dev/core). Always use subpath imports such as trigger.dev/core/v3, trigger.dev/core/v3/utils, trigger.dev/core/logger, or trigger.dev/core/schemas
Applied to files:
.claude/rules/package-installation.mdpackages/trigger-sdk/package.json
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `batch.triggerAndWait()` to batch trigger multiple different tasks and wait for results
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `batch.triggerByTaskAndWait()` to batch trigger tasks by passing task instances and wait for results
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.batchTrigger()` to trigger multiple runs of a task from inside another task
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.triggerAndWait()` to trigger a task and wait for its result from a parent task
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.batchTriggerAndWait()` to batch trigger tasks and wait for all results from a parent task
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components
Applied to files:
packages/trigger-sdk/src/v3/chat-react.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `useRun`, `useRealtimeRun` and other SWR/realtime hooks from `trigger.dev/react-hooks` for data fetching
Applied to files:
packages/trigger-sdk/src/v3/chat-react.ts
📚 Learning: 2026-03-03T13:07:33.177Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3166
File: internal-packages/run-engine/src/batch-queue/tests/index.test.ts:711-713
Timestamp: 2026-03-03T13:07:33.177Z
Learning: In `internal-packages/run-engine/src/batch-queue/tests/index.test.ts`, test assertions for rate limiter stubs can use `toBeGreaterThanOrEqual` rather than exact equality (`toBe`) because the consumer loop may call the rate limiter during empty pops in addition to actual item processing, and this over-calling is acceptable in integration tests.
Applied to files:
.scratch/plan-graceful-oversized-batch-items.md
📚 Learning: 2026-03-03T13:08:03.862Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3166
File: packages/redis-worker/src/fair-queue/index.ts:1114-1121
Timestamp: 2026-03-03T13:08:03.862Z
Learning: In packages/redis-worker/src/fair-queue/index.ts, it's acceptable for the worker queue depth cap check to allow overshooting by up to batchClaimSize messages per iteration, as the next iteration will recheck and prevent sustained growth beyond the limit.
Applied to files:
.scratch/plan-graceful-oversized-batch-items.md
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : When importing from `trigger.dev/core` in the webapp, use subpath exports from the package.json instead of importing from the root path
Applied to files:
packages/trigger-sdk/package.json.changeset/ai-sdk-chat-transport.md
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Applies to packages/cli-v3/src/build/**/* : Build system in `src/build/` should use configuration from `trigger.config.ts` in user projects to determine bundling, build extensions, and output structure
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : Import from trigger.dev/core subpaths only, never from the root
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Specify task locations in trigger.config.ts using the `dirs` array, with automatic exclusion of .test and .spec files
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2025-11-27T16:26:58.661Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/webapp.mdc:0-0
Timestamp: 2025-11-27T16:26:58.661Z
Learning: Applies to apps/webapp/**/*.{ts,tsx} : Follow the Remix 2.1.0 and Express server conventions when updating the main trigger.dev webapp
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger.config.ts : Use build extensions in trigger.config.ts (additionalFiles, additionalPackages, aptGet, prismaExtension, etc.) to customize the build
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2025-11-26T14:40:07.146Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 2710
File: packages/schema-to-json/package.json:0-0
Timestamp: 2025-11-26T14:40:07.146Z
Learning: Node.js 24+ has native TypeScript support and can execute .ts files directly without tsx or ts-node for scripts that use only erasable TypeScript syntax (type annotations, interfaces, etc.). The trigger.dev repository uses Node.js 24.11.1+ and scripts like updateVersion.ts can be run with `node` instead of `tsx`.
Applied to files:
packages/trigger-sdk/package.json
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
🪛 markdownlint-cli2 (0.21.0)
.scratch/plan-graceful-oversized-batch-items.md
[warning] 216-216: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (24)
.claude/rules/package-installation.md (1)
10-22: Clear and actionable monorepo dependency install workflow.The new guidance is explicit and practical for this repo setup.
CLAUDE.md (1)
9-10: Good top-level pointer to the package-installation rule.This keeps root instructions concise while linking to the detailed process.
packages/core/src/v3/realtimeStreams/types.ts (1)
74-77: Nice API surface extension for tracing customization.The new optional fields are additive and maintain compatibility.
Also applies to: 206-207, 244-245
packages/trigger-sdk/src/v3/chat-constants.ts (1)
1-13: Good centralization of chat stream identifiers.This avoids string duplication across the chat transport stack.
packages/trigger-sdk/src/v3/streams.ts (1)
142-143: Trace/span and input-wait lifecycle updates look consistent.The new options and waitpoint flow changes integrate cleanly with the stream abstractions.
Also applies to: 170-171, 644-645, 717-717, 754-804, 819-820
packages/trigger-sdk/package.json (1)
27-30: Export map and peer/type wiring for chat subpaths look solid.
./chatand./chat/reactare consistently mapped across build, runtime, and type resolution.Also applies to: 42-48, 77-77, 90-100, 139-161
docs/docs.json (1)
77-77: Navigation update is correctly placed.Adding
guides/ai-chatunder “Writing tasks” is consistent with the existing docs structure.packages/core/src/v3/inputStreams/manager.ts (2)
43-61: LGTM! New stream management methods are well-implemented.The
setLastSeqNumcorrectly guards against backward sequence number movement,shiftBufferproperly handles cleanup when the buffer becomes empty, and both methods align with the documented behavior intypes.ts.
181-188: LGTM! Clean stream disconnection implementation.The
disconnectStreammethod properly aborts the tail's AbortController and cleans up both the tail and buffer entries for the stream.packages/core/src/v3/inputStreams/index.ts (1)
54-64: LGTM! API wrappers follow established patterns.The new methods correctly delegate to the underlying manager and maintain consistency with the existing API surface.
packages/core/src/v3/inputStreams/noopManager.ts (1)
25-29: LGTM! Correct no-op implementations.The no-op methods properly implement the
InputStreamManagerinterface with appropriate default behavior:shiftBufferreturnsfalse(nothing to shift), and the others are silent no-ops.docs/guides/ai-chat.mdx (3)
1-5: LGTM! Well-structured documentation with proper Mintlify frontmatter.The documentation follows the MDX guidelines with required frontmatter fields (title, description) and optional sidebarTitle.
69-112: Frontend example is clear and practical.The React component example demonstrates the integration pattern well. The code is complete and follows React conventions.
19-21: Version requirement callout is clear and accurate. The@ai-sdk/reactimport path referenced in the file (line 72) is the correct path for AI SDK v5.0.0 and later. No issues found.packages/core/src/v3/inputStreams/types.ts (1)
73-93: LGTM! Excellent documentation for the new interface methods.The JSDoc comments clearly explain the purpose of each method in the context of waitpoint handling and SSE tail management. The documentation will help consumers understand when and why to use each method.
.changeset/ai-sdk-chat-transport.md (1)
1-42: LGTM! Well-documented changeset with clear examples.The minor version bump is appropriate for adding new features. The examples clearly demonstrate the usage of both frontend and backend exports.
packages/trigger-sdk/src/v3/chat.test.ts (3)
49-59: LGTM! Good test setup/teardown pattern.The
beforeEach/afterEachcorrectly saves and restoresglobal.fetch, andvi.restoreAllMocks()ensures clean state between tests.
919-1022: Excellent coverage of lastEventId tracking and SSE reconnection.The tests thoroughly verify that
Last-Event-IDis passed correctly on subsequent stream subscriptions, which is critical for proper SSE resumption behavior.
1264-1614: Comprehensive waitpoint/single-run mode test coverage.The tests cover important edge cases: storing waitpoint tokens, completing waitpoints on subsequent messages, fallback behavior when streams close unexpectedly, and fallback when waitpoint completion fails. This is crucial for the durable chat session feature.
packages/trigger-sdk/src/v3/chat.ts (5)
1-31: LGTM!Clear module documentation with a practical usage example. Imports are appropriate for a browser-safe module, using the
aipackage types and@trigger.dev/core/v3utilities that are isomorphic.
38-116: LGTM!Type definitions are well-structured with comprehensive JSDoc. The
accessTokenflexibility (supporting sync/async functions) is a nice pattern for dynamic token refresh and Next.js server actions.
308-389: Well-structured ReadableStream implementation.The streaming logic correctly handles:
- Turn completion via
__trigger_turn_completecontrol chunks- Skip-to-turn-complete state for abort/resume scenarios
lastEventIdtracking for stream resumption- Defensive cleanup with try-catch on
controller.close()The error handling properly distinguishes between AbortError (expected on stop) and other errors.
243-254: LGTM!The
reconnectToStreammethod correctly handles missing sessions by returningnull. ThecreateChatTransportfactory function provides a clean functional alternative with good documentation.Also applies to: 392-412
274-277:⚠️ Potential issue | 🔴 CriticalUpdate minimum Node.js engine requirement to match AbortSignal.any() availability.
The SDK declares
engines.node >= 18.20.0in package.json, but usesAbortSignal.any()(line 276) which requires Node 20.3.0+. This mismatch will cause runtime errors for Node 18.x and Node 20.0–20.2.x users.Either:
- Update
engines.nodeto>= 20.3.0in package.json, or- Replace
AbortSignal.any()with manual signal composition for backward compatibility⛔ Skipped due to learnings
Learnt from: nicktrn Repo: triggerdotdev/trigger.dev PR: 2593 File: packages/core/src/v3/workers/warmStartClient.ts:168-170 Timestamp: 2025-10-08T11:48:12.327Z Learning: The trigger.dev runners execute only in Node 21 and 22 environments, so modern Node.js APIs like AbortSignal.any (introduced in v20.3.0) are supported.Learnt from: CR Repo: triggerdotdev/trigger.dev PR: 0 File: .github/copilot-instructions.md:0-0 Timestamp: 2025-11-27T16:26:37.432Z Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific codeLearnt from: CR Repo: triggerdotdev/trigger.dev PR: 0 File: AGENTS.md:0-0 Timestamp: 2026-01-15T10:48:02.687Z Learning: Use pnpm as the package manager (version 10.23.0 or later) and Node.js 20.20.0
| ``` | ||
| NDJSON bytes arrive | ||
| | | ||
| createNdjsonParserStream | ||
| |-- Line <= limit --> parse JSON --> enqueue object | ||
| `-- Line > limit --> extractIndexAndTask(bytes) --> enqueue OversizedItemMarker | ||
| | | ||
| StreamBatchItemsService for-await loop | ||
| |-- OversizedItemMarker --> engine.enqueueBatchItem() with __error in options | ||
| `-- Normal item --> validate --> engine.enqueueBatchItem() | ||
| | | ||
| FairQueue consumer (#handleMessage) | ||
| |-- __error in options --> processItemCallback detects it | ||
| | --> TriggerFailedTaskService.call() | ||
| | --> Creates pre-failed TaskRun with SYSTEM_FAILURE status | ||
| | --> Proper waitpoint + TaskRunWaitpoint connections created | ||
| | --> Returns { success: true, runId: failedRunFriendlyId } | ||
| `-- Normal item --> TriggerTaskService.call() --> creates normal run | ||
| | | ||
| Batch sealing: enqueuedCount === runCount (all items go through enqueueBatchItem) | ||
| Batch completion: all items have runs (real or pre-failed), waitpoints resolve normally | ||
| Parent run: batchTriggerAndWait resolves with per-item results | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced block (Line 216).
This fence is missing a language tag, which trips markdownlint MD040.
📝 Proposed fix
-```
+```text
NDJSON bytes arrive
|
createNdjsonParserStream
|-- Line <= limit --> parse JSON --> enqueue object
`-- Line > limit --> extractIndexAndTask(bytes) --> enqueue OversizedItemMarker
|
StreamBatchItemsService for-await loop
|-- OversizedItemMarker --> engine.enqueueBatchItem() with __error in options
`-- Normal item --> validate --> engine.enqueueBatchItem()
|
FairQueue consumer (`#handleMessage`)
|-- __error in options --> processItemCallback detects it
| --> TriggerFailedTaskService.call()
| --> Creates pre-failed TaskRun with SYSTEM_FAILURE status
| --> Proper waitpoint + TaskRunWaitpoint connections created
| --> Returns { success: true, runId: failedRunFriendlyId }
`-- Normal item --> TriggerTaskService.call() --> creates normal run
|
Batch sealing: enqueuedCount === runCount (all items go through enqueueBatchItem)
Batch completion: all items have runs (real or pre-failed), waitpoints resolve normally
Parent run: batchTriggerAndWait resolves with per-item results</details>
<!-- suggestion_start -->
<details>
<summary>📝 Committable suggestion</summary>
> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
```suggestion
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)
[warning] 216-216: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.scratch/plan-graceful-oversized-batch-items.md around lines 216 - 238, The
fenced code block that starts with "NDJSON bytes arrive" is missing a language
identifier which triggers markdownlint MD040; update the opening fence to
include a language (e.g., change ``` to ```text) so the block is explicitly
marked as plain text (ensure the closing fence remains ```), preserving the
existing block contents and indentation.
| let _chatPipeCount = 0; | ||
|
|
There was a problem hiding this comment.
Scope manual-pipe tracking per run/turn; global _chatPipeCount is race-prone.
_chatPipeCount is module-global, so concurrent chat runs can mutate the same counter and incorrectly suppress or trigger auto-piping in other runs.
💡 Suggested fix (run-scoped counter)
-import {
+import {
accessoryAttributes,
AnyTask,
isSchemaZodEsque,
SemanticInternalAttributes,
Task,
+ taskContext,
type inferSchemaIn,
type PipeStreamOptions,
type TaskIdentifier,
type TaskOptions,
type TaskSchema,
type TaskWithSchema,
} from "@trigger.dev/core/v3";
-let _chatPipeCount = 0;
+const _chatPipeCountByRun = new Map<string, number>();
async function pipeChat(
source: UIMessageStreamable | AsyncIterable<unknown> | ReadableStream<unknown>,
options?: PipeChatOptions
): Promise<void> {
- _chatPipeCount++;
+ const runId = taskContext.ctx?.run.id;
+ if (runId) {
+ _chatPipeCountByRun.set(runId, (_chatPipeCountByRun.get(runId) ?? 0) + 1);
+ }
const streamKey = options?.streamKey ?? CHAT_STREAM_KEY;
// ...
}
// inside chatTask turn:
- _chatPipeCount = 0;
+ const activeRunId = taskContext.ctx?.run.id;
+ if (activeRunId) _chatPipeCountByRun.set(activeRunId, 0);
- if (_chatPipeCount === 0 && isUIMessageStreamable(result)) {
+ if ((activeRunId ? _chatPipeCountByRun.get(activeRunId) ?? 0 : 0) === 0 && isUIMessageStreamable(result)) {
await pipeChat(result, { signal: combinedSignal, spanName: "stream response" });
}Also applies to: 360-361, 548-583
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/trigger-sdk/src/v3/ai.ts` around lines 268 - 269, The global
_chatPipeCount is race-prone; make the pipe counter scoped to each chat run/turn
instead. Remove the module-global _chatPipeCount and initialize a run-scoped
counter (for example add a numeric property like run.__chatPipeCount or
turn.chatPipeCount on the ChatRun/ChatTurn object when a run/turn is created or
at the start of the function that currently uses _chatPipeCount), then replace
every reference to _chatPipeCount (including uses around lines 360-361 and
548-583) with the run/turn-scoped property and update increment/decrement logic
to use that property so concurrent runs don’t share state. Ensure the counter is
initialized to 0 at run start and cleaned up or left on the run object when
finished.
…r mesages from the transport
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
packages/trigger-sdk/src/v3/ai.ts (2)
541-545:⚠️ Potential issue | 🔴 CriticalDrain buffered message backlog; current code drops all but the first buffered item.
Lines 541-545 capture multiple messages during streaming, but Line 626 only uses the first and the rest are discarded after
msgSub.off(). This can lose user input.💡 Suggested fix (preserve queue across turns)
let currentWirePayload = payload; + const queuedMessages: ChatTaskWirePayload[] = []; // ... const pendingMessages: ChatTaskWirePayload[] = []; const msgSub = messagesInput.on((msg) => { pendingMessages.push(msg); }); // ... if (pendingMessages.length > 0) { - currentWirePayload = pendingMessages[0]!; + currentWirePayload = pendingMessages.shift()!; + if (pendingMessages.length > 0) { + queuedMessages.push(...pendingMessages); + } return "continue"; } + if (queuedMessages.length > 0) { + currentWirePayload = queuedMessages.shift()!; + return "continue"; + }Also applies to: 624-627
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` around lines 541 - 545, The buffered pendingMessages array (populated by messagesInput.on and unsubscribed via msgSub.off) is currently only consuming the first item and discarding the rest; update the logic that runs after msgSub.off() to drain the entire pendingMessages queue in FIFO order instead of taking only pendingMessages[0]: iterate through pendingMessages and push/emit each message back into the normal processing path (or append them to the messages queue used by the turn) before proceeding, and only then call msgSub.off(); reference pendingMessages, messagesInput.on, msgSub.off and the consumer that currently reads pendingMessages[0] to locate and replace the drop-with-first behavior.
245-245:⚠️ Potential issue | 🔴 CriticalScope pipe-call tracking per run/turn instead of module-global state.
Line 245 defines
_chatPipeCountas shared module state. Concurrent chat runs can affect each other and incorrectly suppress auto-piping at Line 579.💡 Suggested fix (run-scoped counter)
import { accessoryAttributes, AnyTask, isSchemaZodEsque, SemanticInternalAttributes, Task, + taskContext, type inferSchemaIn, type PipeStreamOptions, type TaskIdentifier, type TaskOptions, type TaskSchema, type TaskWithSchema, } from "@trigger.dev/core/v3"; -let _chatPipeCount = 0; +const _chatPipeCountByRun = new Map<string, number>(); async function pipeChat( source: UIMessageStreamable | AsyncIterable<unknown> | ReadableStream<unknown>, options?: PipeChatOptions ): Promise<void> { - _chatPipeCount++; + const runId = taskContext.ctx?.run.id; + if (runId) { + _chatPipeCountByRun.set(runId, (_chatPipeCountByRun.get(runId) ?? 0) + 1); + } const streamKey = options?.streamKey ?? CHAT_STREAM_KEY; // ... } // inside chatTask turn - _chatPipeCount = 0; + const activeRunId = taskContext.ctx?.run.id; + if (activeRunId) _chatPipeCountByRun.set(activeRunId, 0); - if (_chatPipeCount === 0 && isUIMessageStreamable(result)) { + const pipeCount = activeRunId ? (_chatPipeCountByRun.get(activeRunId) ?? 0) : 0; + if (pipeCount === 0 && isUIMessageStreamable(result)) { // ... }Also applies to: 337-338, 530-531, 579-579
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` at line 245, The module-global counter _chatPipeCount should be replaced with a run-scoped counter so concurrent chat runs don't interfere: remove the top-level _chatPipeCount and add a per-run property (e.g., set chatRun.chatPipeCount or add to the run/turn context object) initialized when a chat run/turn starts; update all increments, decrements and checks that currently reference _chatPipeCount (including the auto-piping check referenced as the “auto-piping” logic) to read/update this per-run property so each run tracks its own pipe count; ensure initialization, increment/decrement, and any conditional suppression logic use the new run-scoped symbol instead of _chatPipeCount.
🧹 Nitpick comments (1)
packages/trigger-sdk/src/v3/chat.ts (1)
27-31: Use sharedCHAT_STREAM_KEYconstant instead of duplicating"chat"literal.Line 29 duplicates a value already defined in
chat-constants.ts. Reusing the shared constant avoids drift between frontend and backend defaults.💡 Suggested refactor
-import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID } from "./chat-constants.js"; +import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID, CHAT_STREAM_KEY } from "./chat-constants.js"; -const DEFAULT_STREAM_KEY = "chat"; +const DEFAULT_STREAM_KEY = CHAT_STREAM_KEY;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/chat.ts` around lines 27 - 31, Replace the duplicated "chat" literal with the shared constant from chat-constants.ts: import CHAT_STREAM_KEY alongside CHAT_MESSAGES_STREAM_ID and CHAT_STOP_STREAM_ID and set DEFAULT_STREAM_KEY = CHAT_STREAM_KEY instead of the hard-coded "chat" string; this keeps frontend/backend defaults in sync and avoids drift (update any references to DEFAULT_STREAM_KEY in this module if needed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 382-425: chatTask currently hardcodes CHAT_STREAM_KEY for
auto-piping and turn-complete control messages which breaks non-default stream
keys; update the chat task plumbing so the run payload carries the effective
streamKey (threaded from TriggerChatTransport/transport config) and use that
payload.streamKey (or the task's configured streamKey) instead of
CHAT_STREAM_KEY in the auto-pipe logic and when emitting the turn-complete
control message (references: TriggerChatTransport, chatTask run
handler/auto-pipe, and the turn-complete control write locations that currently
use CHAT_STREAM_KEY). Ensure the ChatTaskRunPayload type includes streamKey,
pass the transport's streamKey into runs, and replace hardcoded CHAT_STREAM_KEY
usages with the dynamic payload.streamKey.
---
Duplicate comments:
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 541-545: The buffered pendingMessages array (populated by
messagesInput.on and unsubscribed via msgSub.off) is currently only consuming
the first item and discarding the rest; update the logic that runs after
msgSub.off() to drain the entire pendingMessages queue in FIFO order instead of
taking only pendingMessages[0]: iterate through pendingMessages and push/emit
each message back into the normal processing path (or append them to the
messages queue used by the turn) before proceeding, and only then call
msgSub.off(); reference pendingMessages, messagesInput.on, msgSub.off and the
consumer that currently reads pendingMessages[0] to locate and replace the
drop-with-first behavior.
- Line 245: The module-global counter _chatPipeCount should be replaced with a
run-scoped counter so concurrent chat runs don't interfere: remove the top-level
_chatPipeCount and add a per-run property (e.g., set chatRun.chatPipeCount or
add to the run/turn context object) initialized when a chat run/turn starts;
update all increments, decrements and checks that currently reference
_chatPipeCount (including the auto-piping check referenced as the “auto-piping”
logic) to read/update this per-run property so each run tracks its own pipe
count; ensure initialization, increment/decrement, and any conditional
suppression logic use the new run-scoped symbol instead of _chatPipeCount.
---
Nitpick comments:
In `@packages/trigger-sdk/src/v3/chat.ts`:
- Around line 27-31: Replace the duplicated "chat" literal with the shared
constant from chat-constants.ts: import CHAT_STREAM_KEY alongside
CHAT_MESSAGES_STREAM_ID and CHAT_STOP_STREAM_ID and set DEFAULT_STREAM_KEY =
CHAT_STREAM_KEY instead of the hard-coded "chat" string; this keeps
frontend/backend defaults in sync and avoids drift (update any references to
DEFAULT_STREAM_KEY in this module if needed).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0248001b-2508-4f06-980e-b32230fa359f
📒 Files selected for processing (3)
packages/trigger-sdk/src/v3/ai.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/trigger-sdk/src/v3/chat.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: typecheck / typecheck
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: sdk-compat / Bun Runtime
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (6)
packages/trigger-sdk/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: In TypeScript SDK usage, always import from@trigger.dev/sdk, never from@trigger.dev/sdk/v3or use deprecated client.defineJob
Import from@trigger.dev/coresubpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
packages/trigger-sdk/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/trigger-sdk/CLAUDE.md)
Always import from
@trigger.dev/sdk. Never use@trigger.dev/sdk/v3(deprecated path alias)
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
🧠 Learnings (15)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Exercise caution with changes to trigger.dev/core as they affect both the customer-facing SDK and server-side webapp - breaking changes can impact deployed user tasks and the platform simultaneously
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Applied to files:
packages/trigger-sdk/src/v3/chat.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : In TypeScript SDK usage, always import from trigger.dev/sdk, never from trigger.dev/sdk/v3 or use deprecated client.defineJob
Applied to files:
packages/trigger-sdk/src/v3/chat.ts
📚 Learning: 2026-03-03T13:08:03.862Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3166
File: packages/redis-worker/src/fair-queue/index.ts:1114-1121
Timestamp: 2026-03-03T13:08:03.862Z
Learning: In packages/redis-worker/src/fair-queue/index.ts, it's acceptable for the worker queue depth cap check to allow overshooting by up to batchClaimSize messages per iteration, as the next iteration will recheck and prevent sustained growth beyond the limit.
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
🔇 Additional comments (1)
packages/trigger-sdk/src/v3/chat.ts (1)
264-397: Solid stop/resume stream lifecycle handling.
lastEventIdtracking plusskipToTurnCompletehandling is robust and fits turn-based chat streaming well.
| export type ChatTaskOptions<TIdentifier extends string> = Omit< | ||
| TaskOptions<TIdentifier, ChatTaskWirePayload, unknown>, | ||
| "run" | ||
| > & { | ||
| /** | ||
| * The run function for the chat task. | ||
| * | ||
| * Receives a `ChatTaskRunPayload` with the conversation messages, chat session ID, | ||
| * trigger type, and abort signals (`signal`, `cancelSignal`, `stopSignal`). | ||
| * | ||
| * **Auto-piping:** If this function returns a value with `.toUIMessageStream()`, | ||
| * the stream is automatically piped to the frontend. | ||
| */ | ||
| run: (payload: ChatTaskRunPayload) => Promise<unknown>; | ||
|
|
||
| /** | ||
| * Maximum number of conversational turns (message round-trips) a single run | ||
| * will handle before ending. After this many turns the run completes | ||
| * normally and the next message will start a fresh run. | ||
| * | ||
| * @default 100 | ||
| */ | ||
| maxTurns?: number; | ||
|
|
||
| /** | ||
| * How long to wait for the next message before timing out and ending the run. | ||
| * Accepts any duration string (e.g. `"1h"`, `"30m"`). | ||
| * | ||
| * @default "1h" | ||
| */ | ||
| turnTimeout?: string; | ||
|
|
||
| /** | ||
| * How long (in seconds) to keep the run warm after each turn before suspending. | ||
| * During this window the run stays active and can respond instantly to the | ||
| * next message. After this timeout, the run suspends (frees compute) and waits | ||
| * via `inputStream.wait()`. | ||
| * | ||
| * Set to `0` to suspend immediately after each turn. | ||
| * | ||
| * @default 30 | ||
| */ | ||
| warmTimeoutInSeconds?: number; | ||
| }; |
There was a problem hiding this comment.
chatTask does not honor custom streamKey end-to-end.
TriggerChatTransport subscribes using its configured stream key (packages/trigger-sdk/src/v3/chat.ts Line 307), but chatTask auto-pipe (Lines 585-586) and turn-complete control write (Lines 710-718) are fixed to CHAT_STREAM_KEY. With non-default keys, turn completion can hang on the frontend.
💡 Suggested fix (thread streamKey through chatTask)
export type ChatTaskOptions<TIdentifier extends string> = Omit<
TaskOptions<TIdentifier, ChatTaskWirePayload, unknown>,
"run"
> & {
+ /**
+ * Stream key to use for chat output/control chunks.
+ * Must match TriggerChatTransport.streamKey on the frontend.
+ * `@default` "chat"
+ */
+ streamKey?: string;
run: (payload: ChatTaskRunPayload) => Promise<unknown>;
maxTurns?: number;
turnTimeout?: string;
warmTimeoutInSeconds?: number;
};
function chatTask<TIdentifier extends string>(
options: ChatTaskOptions<TIdentifier>
): Task<TIdentifier, ChatTaskWirePayload, unknown> {
const {
run: userRun,
+ streamKey = CHAT_STREAM_KEY,
maxTurns = 100,
turnTimeout = "1h",
warmTimeoutInSeconds = 30,
...restOptions
} = options;
// ...
- await pipeChat(uiStream, { signal: combinedSignal, spanName: "stream response" });
+ await pipeChat(uiStream, {
+ streamKey,
+ signal: combinedSignal,
+ spanName: "stream response",
+ });
- await writeTurnCompleteChunk(currentWirePayload.chatId);
+ await writeTurnCompleteChunk(streamKey);
}
-async function writeTurnCompleteChunk(chatId?: string): Promise<void> {
- const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, {
+async function writeTurnCompleteChunk(streamKey: string = CHAT_STREAM_KEY): Promise<void> {
+ const { waitUntilComplete } = streams.writer(streamKey, {Also applies to: 585-586, 710-718
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/trigger-sdk/src/v3/ai.ts` around lines 382 - 425, chatTask currently
hardcodes CHAT_STREAM_KEY for auto-piping and turn-complete control messages
which breaks non-default stream keys; update the chat task plumbing so the run
payload carries the effective streamKey (threaded from
TriggerChatTransport/transport config) and use that payload.streamKey (or the
task's configured streamKey) instead of CHAT_STREAM_KEY in the auto-pipe logic
and when emitting the turn-complete control message (references:
TriggerChatTransport, chatTask run handler/auto-pipe, and the turn-complete
control write locations that currently use CHAT_STREAM_KEY). Ensure the
ChatTaskRunPayload type includes streamKey, pass the transport's streamKey into
runs, and replace hardcoded CHAT_STREAM_KEY usages with the dynamic
payload.streamKey.
…and documenting it all
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (3)
packages/trigger-sdk/src/v3/ai.ts (3)
618-621:⚠️ Potential issue | 🔴 CriticalOnly the first buffered message is consumed; additional buffered messages are dropped.
When multiple messages arrive during a turn, everything after index 0 is lost.
💡 Proposed fix (preserve backlog)
let currentWirePayload = payload; + const queuedWirePayloads: ChatTaskWirePayload[] = []; @@ - if (pendingMessages.length > 0) { - currentWirePayload = pendingMessages[0]!; + if (pendingMessages.length > 0) { + queuedWirePayloads.push(...pendingMessages); + } + + if (queuedWirePayloads.length > 0) { + currentWirePayload = queuedWirePayloads.shift()!; return "continue"; }Also applies to: 764-767
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` around lines 618 - 621, The buffered message handler currently pushes incoming messages into pendingMessages but elsewhere only the first element is consumed, causing drops; change the consumption logic to drain the entire backlog instead of taking index 0 — after subscribing with messagesInput.on and collecting into pendingMessages, iterate (or repeatedly shift) through pendingMessages until empty and process each ChatTaskWirePayload, or swap/consume a copied array and clear pendingMessages before processing, ensuring msgSub is still properly unsubscribed when done; update all occurrences around pendingMessages/msgSub (including the similar block at lines ~764-767) to use this draining approach.
423-495:⚠️ Potential issue | 🟠 MajorCustom stream keys are not threaded through
chatTaskauto-pipe and turn-complete writes.
pipeChatandwriteTurnCompleteChunkstill default toCHAT_STREAM_KEY, so non-default frontend stream keys can hang/miss control chunks.💡 Proposed fix (thread `streamKey` through chatTask)
export type ChatTaskOptions<TIdentifier extends string> = Omit< TaskOptions<TIdentifier, ChatTaskWirePayload, unknown>, "run" > & { + streamKey?: string; run: (payload: ChatTaskRunPayload) => Promise<unknown>; @@ const { run: userRun, onChatStart, onTurnComplete, + streamKey = CHAT_STREAM_KEY, maxTurns = 100, @@ - await pipeChat(uiStream, { signal: combinedSignal, spanName: "stream response" }); + await pipeChat(uiStream, { + streamKey, + signal: combinedSignal, + spanName: "stream response", + }); @@ - await writeTurnCompleteChunk(currentWirePayload.chatId); + await writeTurnCompleteChunk(streamKey); @@ -async function writeTurnCompleteChunk(chatId?: string): Promise<void> { - const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, { +async function writeTurnCompleteChunk(streamKey: string = CHAT_STREAM_KEY): Promise<void> { + const { waitUntilComplete } = streams.writer(streamKey, {Also applies to: 698-699, 762-763, 930-935
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` around lines 423 - 495, The auto-pipe and turn-complete logic for chatTask currently always uses CHAT_STREAM_KEY causing custom frontend stream keys to be ignored; update the flow so the stream key from the run context is threaded and used instead. Concretely: add/ensure a streamKey property is available on ChatTaskRunPayload (or passed into the chat task runner), then replace usages of the hard-coded CHAT_STREAM_KEY in pipeChat and writeTurnCompleteChunk calls with that payload.streamKey (falling back to CHAT_STREAM_KEY only if undefined). Update chatTask invocation sites (the chatTask runner/dispatcher and any call sites that call pipeChat or writeTurnCompleteChunk — referenced symbols: chatTask, ChatTaskRunPayload, pipeChat, writeTurnCompleteChunk, CHAT_STREAM_KEY) so the streamKey is propagated through the lifecycle (auto-pipe and on-turn-complete) to fix missing control chunks for non-default stream keys.
245-246:⚠️ Potential issue | 🔴 Critical
_chatPipeCountis module-global and race-prone across concurrent runs.Cross-run mutations can suppress/trigger auto-piping in the wrong run.
💡 Proposed fix (run-scoped counter)
import { accessoryAttributes, AnyTask, isSchemaZodEsque, SemanticInternalAttributes, Task, + taskContext, type inferSchemaIn, type PipeStreamOptions, type TaskIdentifier, type TaskOptions, type TaskSchema, type TaskWithSchema, } from "@trigger.dev/core/v3"; @@ -let _chatPipeCount = 0; +const _chatPipeCountByRun = new Map<string, number>(); @@ - _chatPipeCount++; + const runId = taskContext.ctx?.run.id; + if (runId) { + _chatPipeCountByRun.set(runId, (_chatPipeCountByRun.get(runId) ?? 0) + 1); + } @@ - _chatPipeCount = 0; + const activeRunId = taskContext.ctx?.run.id; + if (activeRunId) _chatPipeCountByRun.set(activeRunId, 0); @@ - if (_chatPipeCount === 0 && isUIMessageStreamable(result)) { + const pipeCount = activeRunId ? (_chatPipeCountByRun.get(activeRunId) ?? 0) : 0; + if (pipeCount === 0 && isUIMessageStreamable(result)) { @@ - } finally { + } finally { stopSub.off(); + const activeRunId = taskContext.ctx?.run.id; + if (activeRunId) _chatPipeCountByRun.delete(activeRunId); }Also applies to: 337-338, 606-607, 692-693
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` around lines 245 - 246, _chatPipeCount is a module-global counter and races across concurrent runs; replace it with a run-scoped counter stored on the run/instance/context object used by the chat flow (e.g., add a chatPipeCount property on the ChatRun/AI instance or pass a runContext with chatPipeCount into the functions that reference _chatPipeCount). Update every reference to _chatPipeCount (including locations noted around 337-338, 606-607, 692-693) to use the run-scoped property (e.g., this.chatPipeCount or runContext.chatPipeCount), initialize it at run start, and perform increments/decrements against that property so each run has its own counter and avoids cross-run races. Ensure any helper functions like startChatPipe/createChatStream/autoPipe accept the run context or are methods on the run instance so they can access the run-scoped counter.
🧹 Nitpick comments (3)
packages/trigger-sdk/src/v3/chat.ts (1)
27-31: Use the sharedCHAT_STREAM_KEYconstant instead of"chat"literal.This avoids drift between frontend/backend defaults.
💡 Proposed refactor
-import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID } from "./chat-constants.js"; +import { CHAT_MESSAGES_STREAM_ID, CHAT_STOP_STREAM_ID, CHAT_STREAM_KEY } from "./chat-constants.js"; @@ -const DEFAULT_STREAM_KEY = "chat"; +const DEFAULT_STREAM_KEY = CHAT_STREAM_KEY;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/chat.ts` around lines 27 - 31, Replace the string literal default for DEFAULT_STREAM_KEY with the shared constant CHAT_STREAM_KEY: import CHAT_STREAM_KEY from the central constants module and set DEFAULT_STREAM_KEY = CHAT_STREAM_KEY (update the import section where CHAT_MESSAGES_STREAM_ID and CHAT_STOP_STREAM_ID are imported); this ensures DEFAULT_STREAM_KEY uses the shared CHAT_STREAM_KEY constant instead of the hard-coded "chat" value.docs/guides/ai-chat.mdx (1)
32-49: Usetypescriptfence labels instead ofts/tsxin MDX examples.As per coding guidelines, "Code examples must use language tags in code fences:
typescript,bash,json."Also applies to: 55-63, 69-119, 158-174, 180-205, 211-230, 243-255, 275-288, 311-338, 348-361, 367-423, 430-555, 575-585, 597-603, 610-614, 621-638, 647-651, 658-662, 688-698, 710-718, 768-774
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/guides/ai-chat.mdx` around lines 32 - 49, Change all MDX code fence language tags from the short forms "ts" or "tsx" to the canonical "typescript" per docs guidelines; for example, replace the opening fence "```ts trigger/chat.ts" (and any other occurrences listed) with "```typescript trigger/chat.ts" so examples like the chat.task export and streamText usage remain unchanged but use the required language tag. Search for "```ts" and "```tsx" in docs/guides/ai-chat.mdx (including the ranges called out in the review) and update each fence to "```typescript" while leaving file references and code content intact.packages/trigger-sdk/src/v3/ai.ts (1)
935-935: Extract the control-chunk type string to a shared constant.
"__trigger_turn_complete"is protocol-level and used in both backend (ai.ts) and frontend (chat.ts); centralizing it avoids drift.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` at line 935, Replace the hard-coded protocol string in write({ type: "__trigger_turn_complete" }) with a shared exported constant: create and export a constant (e.g. TRIGGER_TURN_COMPLETE = "__trigger_turn_complete") from a central module used by both backend and frontend, import that constant into packages/trigger-sdk/src/v3/ai.ts (where write(...) is called) and into the frontend chat code that currently uses the same literal, and update any related type unions or checks to reference the constant so both sides use the single canonical identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/ai-chat.mdx`:
- Around line 21-22: Update the ai package version requirement in the ai-chat
guide from "v5.0.0 or later" to "v6.0.0 or later" so it matches the reference
implementation; specifically edit the sentence containing "Requires
`@trigger.dev/sdk` version **4.4.0 or later** and the `ai` package **v5.0.0 or
later**" in docs/guides/ai-chat.mdx to use **v6.0.0 or later** instead.
In `@packages/trigger-sdk/src/v3/chat.test.ts`:
- Around line 103-104: The tests in packages/trigger-sdk/src/v3/chat.test.ts are
replacing global.fetch with vi.fn() mocks (e.g., the global.fetch =
vi.fn().mockImplementation(...) blocks) which violates the repo policy requiring
real integration-style tests via testcontainers and using vitest without
mocking; replace each mocked fetch usage with a testcontainers-backed HTTP
service (or a lightweight local server) that returns the desired responses and
update the tests to call that real endpoint via the existing client code,
removing vi.fn() mocks and any restore logic; keep test names and assertions
intact but point their HTTP base URL/host to the containerized server so the
suite exercises real network behavior under vitest.
In `@packages/trigger-sdk/src/v3/chat.ts`:
- Around line 277-279: TriggerChatTransport currently saves custom headers in
extraHeaders for SSE subscriptions but fails to forward them to triggerTask and
ApiClient.sendInputStream calls; update the calls inside TriggerChatTransport
(where triggerTask(...) is invoked and where new
ApiClient(...).sendInputStream(...) is invoked, including the stop/send stop
paths) to pass the stored extraHeaders as the headers option (e.g., pass {
headers: this.extraHeaders } or session.extraHeaders) so proxy/tenant routing
and custom auth headers are propagated to triggerTask and sendInputStream.
---
Duplicate comments:
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 618-621: The buffered message handler currently pushes incoming
messages into pendingMessages but elsewhere only the first element is consumed,
causing drops; change the consumption logic to drain the entire backlog instead
of taking index 0 — after subscribing with messagesInput.on and collecting into
pendingMessages, iterate (or repeatedly shift) through pendingMessages until
empty and process each ChatTaskWirePayload, or swap/consume a copied array and
clear pendingMessages before processing, ensuring msgSub is still properly
unsubscribed when done; update all occurrences around pendingMessages/msgSub
(including the similar block at lines ~764-767) to use this draining approach.
- Around line 423-495: The auto-pipe and turn-complete logic for chatTask
currently always uses CHAT_STREAM_KEY causing custom frontend stream keys to be
ignored; update the flow so the stream key from the run context is threaded and
used instead. Concretely: add/ensure a streamKey property is available on
ChatTaskRunPayload (or passed into the chat task runner), then replace usages of
the hard-coded CHAT_STREAM_KEY in pipeChat and writeTurnCompleteChunk calls with
that payload.streamKey (falling back to CHAT_STREAM_KEY only if undefined).
Update chatTask invocation sites (the chatTask runner/dispatcher and any call
sites that call pipeChat or writeTurnCompleteChunk — referenced symbols:
chatTask, ChatTaskRunPayload, pipeChat, writeTurnCompleteChunk, CHAT_STREAM_KEY)
so the streamKey is propagated through the lifecycle (auto-pipe and
on-turn-complete) to fix missing control chunks for non-default stream keys.
- Around line 245-246: _chatPipeCount is a module-global counter and races
across concurrent runs; replace it with a run-scoped counter stored on the
run/instance/context object used by the chat flow (e.g., add a chatPipeCount
property on the ChatRun/AI instance or pass a runContext with chatPipeCount into
the functions that reference _chatPipeCount). Update every reference to
_chatPipeCount (including locations noted around 337-338, 606-607, 692-693) to
use the run-scoped property (e.g., this.chatPipeCount or
runContext.chatPipeCount), initialize it at run start, and perform
increments/decrements against that property so each run has its own counter and
avoids cross-run races. Ensure any helper functions like
startChatPipe/createChatStream/autoPipe accept the run context or are methods on
the run instance so they can access the run-scoped counter.
---
Nitpick comments:
In `@docs/guides/ai-chat.mdx`:
- Around line 32-49: Change all MDX code fence language tags from the short
forms "ts" or "tsx" to the canonical "typescript" per docs guidelines; for
example, replace the opening fence "```ts trigger/chat.ts" (and any other
occurrences listed) with "```typescript trigger/chat.ts" so examples like the
chat.task export and streamText usage remain unchanged but use the required
language tag. Search for "```ts" and "```tsx" in docs/guides/ai-chat.mdx
(including the ranges called out in the review) and update each fence to
"```typescript" while leaving file references and code content intact.
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Line 935: Replace the hard-coded protocol string in write({ type:
"__trigger_turn_complete" }) with a shared exported constant: create and export
a constant (e.g. TRIGGER_TURN_COMPLETE = "__trigger_turn_complete") from a
central module used by both backend and frontend, import that constant into
packages/trigger-sdk/src/v3/ai.ts (where write(...) is called) and into the
frontend chat code that currently uses the same literal, and update any related
type unions or checks to reference the constant so both sides use the single
canonical identifier.
In `@packages/trigger-sdk/src/v3/chat.ts`:
- Around line 27-31: Replace the string literal default for DEFAULT_STREAM_KEY
with the shared constant CHAT_STREAM_KEY: import CHAT_STREAM_KEY from the
central constants module and set DEFAULT_STREAM_KEY = CHAT_STREAM_KEY (update
the import section where CHAT_MESSAGES_STREAM_ID and CHAT_STOP_STREAM_ID are
imported); this ensures DEFAULT_STREAM_KEY uses the shared CHAT_STREAM_KEY
constant instead of the hard-coded "chat" value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 0763b3e5-6e7e-4109-b919-c15533ed514e
⛔ Files ignored due to path filters (16)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlreferences/ai-chat/.gitignoreis excluded by!references/**references/ai-chat/package.jsonis excluded by!references/**references/ai-chat/prisma.config.tsis excluded by!references/**references/ai-chat/prisma/migrations/20260305112427_init/migration.sqlis excluded by!references/**references/ai-chat/prisma/migrations/migration_lock.tomlis excluded by!references/**references/ai-chat/prisma/schema.prismais excluded by!references/**references/ai-chat/src/app/actions.tsis excluded by!references/**references/ai-chat/src/app/page.tsxis excluded by!references/**references/ai-chat/src/components/chat-app.tsxis excluded by!references/**references/ai-chat/src/components/chat-sidebar.tsxis excluded by!references/**references/ai-chat/src/components/chat.tsxis excluded by!references/**references/ai-chat/src/lib/models.tsis excluded by!references/**references/ai-chat/src/lib/prisma.tsis excluded by!references/**references/ai-chat/src/trigger/chat.tsis excluded by!references/**references/ai-chat/trigger.config.tsis excluded by!references/**
📒 Files selected for processing (5)
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/ai.tspackages/trigger-sdk/src/v3/chat-react.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/chat.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/trigger-sdk/src/v3/chat-react.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (11)
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
Docs in docs/ directory should use Mintlify MDX format following conventions in docs/CLAUDE.md
Files:
docs/guides/ai-chat.mdx
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.mdx: MDX documentation pages must include frontmatter with title (required), description (required), and sidebarTitle (optional) in YAML format
Use Mintlify components for structured content: , , , , , , /, /
Always import from@trigger.dev/sdkin code examples (never from@trigger.dev/sdk/v3)
Code examples must be complete and runnable where possible
Use language tags in code fences:typescript,bash,json
Files:
docs/guides/ai-chat.mdx
packages/trigger-sdk/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: In TypeScript SDK usage, always import from@trigger.dev/sdk, never from@trigger.dev/sdk/v3or use deprecated client.defineJob
Import from@trigger.dev/coresubpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
packages/trigger-sdk/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/trigger-sdk/CLAUDE.md)
Always import from
@trigger.dev/sdk. Never use@trigger.dev/sdk/v3(deprecated path alias)
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{test,spec}.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use vitest for all tests in the Trigger.dev repository
Files:
packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.test.{ts,tsx,js,jsx}: Test files should live beside the files under test and use descriptivedescribeanditblocks
Tests should avoid mocks or stubs and use the helpers from@internal/testcontainerswhen Redis or Postgres are needed
Use vitest for running unit tests
Files:
packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx,js}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.test.{ts,tsx,js}: Use vitest exclusively for testing - never mock anything, use testcontainers instead
Place test files next to source files with .test.ts naming convention (e.g., MyService.ts -> MyService.test.ts)
Test files using Redis or PostgreSQL should use testcontainers helpers (redisTest, postgresTest, containerTest) instead of mocks
Files:
packages/trigger-sdk/src/v3/chat.test.ts
🧠 Learnings (20)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Keep SDK documentation in `rules/` and `.claude/skills/trigger-dev-tasks/` synchronized when features are added or changed
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2026-03-02T12:43:37.906Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Exercise caution with changes to trigger.dev/core as they affect both the customer-facing SDK and server-side webapp - breaking changes can impact deployed user tasks and the platform simultaneously
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.trigger()` to trigger a task from inside another task with specified payload
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Use vitest for all tests in the Trigger.dev repository
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Subscribe to run updates using `runs.subscribeToRun()` for realtime monitoring of task execution
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `runs.subscribeToBatch()` to subscribe to changes for all runs in a batch
Applied to files:
packages/trigger-sdk/src/v3/chat.test.ts
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-03T13:08:03.862Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3166
File: packages/redis-worker/src/fair-queue/index.ts:1114-1121
Timestamp: 2026-03-03T13:08:03.862Z
Learning: In packages/redis-worker/src/fair-queue/index.ts, it's acceptable for the worker queue depth cap check to allow overshooting by up to batchClaimSize messages per iteration, as the next iteration will recheck and prevent sustained growth beyond the limit.
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
🔇 Additional comments (2)
packages/trigger-sdk/src/v3/chat.ts (1)
415-507: Stop + turn-complete stream lifecycle handling looks robust.The abort listener,
skipToTurnCompletebehavior, and control-chunk interception are implemented cleanly.packages/trigger-sdk/src/v3/chat.test.ts (1)
49-1913: Excellent behavioral coverage across transport lifecycle and edge cases.The suite meaningfully exercises streaming, resume, fallback, abort, token, and session-change paths.
| Requires `@trigger.dev/sdk` version **4.4.0 or later** and the `ai` package **v5.0.0 or later**. | ||
| </Note> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Compare declared AI package versions in repo vs docs note.
rg -n '"ai"\s*:' --glob '**/package.json'
echo "---"
rg -n 'Requires `@trigger.dev/sdk` version|`ai` package' docs/guides/ai-chat.mdxRepository: triggerdotdev/trigger.dev
Length of output: 800
Update ai package version requirement to match reference implementation.
The documentation states the ai package v5.0.0 or later, but the ai-chat reference example at references/ai-chat/package.json requires v6.0.0. Update the version requirement in the guide to reflect the actual minimum version needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/guides/ai-chat.mdx` around lines 21 - 22, Update the ai package version
requirement in the ai-chat guide from "v5.0.0 or later" to "v6.0.0 or later" so
it matches the reference implementation; specifically edit the sentence
containing "Requires `@trigger.dev/sdk` version **4.4.0 or later** and the `ai`
package **v5.0.0 or later**" in docs/guides/ai-chat.mdx to use **v6.0.0 or
later** instead.
| global.fetch = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => { | ||
| const urlStr = typeof url === "string" ? url : url.toString(); |
There was a problem hiding this comment.
Test suite currently violates repo policy against mocks/stubs in *.test.ts.
This file relies extensively on mocked fetch. Please align with the repository’s test strategy for these tests.
As per coding guidelines, "**/*.test.{ts,tsx,js}: Use vitest exclusively for testing - never mock anything, use testcontainers instead."
Also applies to: 170-171, 236-237, 293-294, 368-369, 463-464, 537-538, 578-579, 657-658, 731-732, 801-802, 865-866, 934-935, 1031-1032, 1117-1118, 1174-1175, 1246-1247, 1307-1308, 1402-1403, 1471-1472, 1564-1565, 1641-1642, 1737-1738, 1860-1861
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/trigger-sdk/src/v3/chat.test.ts` around lines 103 - 104, The tests
in packages/trigger-sdk/src/v3/chat.test.ts are replacing global.fetch with
vi.fn() mocks (e.g., the global.fetch = vi.fn().mockImplementation(...) blocks)
which violates the repo policy requiring real integration-style tests via
testcontainers and using vitest without mocking; replace each mocked fetch usage
with a testcontainers-backed HTTP service (or a lightweight local server) that
returns the desired responses and update the tests to call that real endpoint
via the existing client code, removing vi.fn() mocks and any restore logic; keep
test names and assertions intact but point their HTTP base URL/host to the
containerized server so the suite exercises real network behavior under vitest.
| const apiClient = new ApiClient(this.baseURL, session.publicAccessToken); | ||
| await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, minimalPayload); | ||
| return this.subscribeToStream( |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "index.ts" | grep -E "apiClient|v3" | head -20Repository: triggerdotdev/trigger.dev
Length of output: 930
🏁 Script executed:
rg -l "class ApiClient" packages/coreRepository: triggerdotdev/trigger.dev
Length of output: 155
🏁 Script executed:
# Check the ApiClient implementation
rg -A 20 "triggerTask\(" packages/core/src/v3/apiClient/index.ts | head -50Repository: triggerdotdev/trigger.dev
Length of output: 755
🏁 Script executed:
# Check sendInputStream method
rg -A 20 "sendInputStream\(" packages/core/src/v3/apiClient/index.ts | head -50Repository: triggerdotdev/trigger.dev
Length of output: 641
🏁 Script executed:
wc -l packages/trigger-sdk/src/v3/chat.tsRepository: triggerdotdev/trigger.dev
Length of output: 108
🏁 Script executed:
sed -n '270,310p' packages/trigger-sdk/src/v3/chat.tsRepository: triggerdotdev/trigger.dev
Length of output: 1541
🏁 Script executed:
sed -n '420,430p' packages/trigger-sdk/src/v3/chat.tsRepository: triggerdotdev/trigger.dev
Length of output: 461
🏁 Script executed:
# Check the TriggerChatTransport class definition and header handling
rg -B 10 "extraHeaders|headers" packages/trigger-sdk/src/v3/chat.ts | head -80Repository: triggerdotdev/trigger.dev
Length of output: 1930
Pass headers option to triggerTask and sendInputStream API calls.
TriggerChatTransport stores custom headers in extraHeaders and uses them for SSE subscriptions, but omits them from triggerTask, sendInputStream (message), and sendInputStream (stop) calls. This breaks proxy/tenant routing or custom auth flows that depend on these headers.
Proposed fix
- await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, minimalPayload);
+ await apiClient.sendInputStream(
+ session.runId,
+ CHAT_MESSAGES_STREAM_ID,
+ minimalPayload,
+ { headers: this.extraHeaders }
+ );- const triggerResponse = await apiClient.triggerTask(this.taskId, {
- payload,
- options: {
- payloadType: "application/json",
- },
- });
+ const triggerResponse = await apiClient.triggerTask(
+ this.taskId,
+ {
+ payload,
+ options: {
+ payloadType: "application/json",
+ },
+ },
+ undefined,
+ { headers: this.extraHeaders }
+ );- api
- .sendInputStream(session.runId, CHAT_STOP_STREAM_ID, { stop: true })
+ api
+ .sendInputStream(session.runId, CHAT_STOP_STREAM_ID, { stop: true }, { headers: this.extraHeaders })
.catch(() => {}); // Best-effortApplies to lines 277–279, 296–301, and 425–426.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/trigger-sdk/src/v3/chat.ts` around lines 277 - 279,
TriggerChatTransport currently saves custom headers in extraHeaders for SSE
subscriptions but fails to forward them to triggerTask and
ApiClient.sendInputStream calls; update the calls inside TriggerChatTransport
(where triggerTask(...) is invoked and where new
ApiClient(...).sendInputStream(...) is invoked, including the stop/send stop
paths) to pass the stored extraHeaders as the headers option (e.g., pass {
headers: this.extraHeaders } or session.extraHeaders) so proxy/tenant routing
and custom auth headers are propagated to triggerTask and sendInputStream.
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/trigger-sdk/src/v3/streams.ts (1)
780-807:⚠️ Potential issue | 🔴 CriticalPotential input event loss when advancing sequence after disconnect.
disconnectStream()aborts tailing asynchronously, so new tail events can still advance seq before shutdown. ThensetLastSeqNum(prev + 1)at Line 806 can skip unseen events on reconnect.🛠️ Safer local mitigation
- inputStreams.disconnectStream(opts.id); + const seqBeforeDisconnect = inputStreams.lastSeqNum(opts.id); + inputStreams.disconnectStream(opts.id); // 3. Suspend the task const waitResult = await runtime.waitUntil(response.waitpointId); @@ - const prevSeq = inputStreams.lastSeqNum(opts.id); - inputStreams.setLastSeqNum(opts.id, (prevSeq ?? -1) + 1); + const seqAfterWait = inputStreams.lastSeqNum(opts.id); + if (seqAfterWait === seqBeforeDisconnect) { + inputStreams.setLastSeqNum(opts.id, (seqBeforeDisconnect ?? -1) + 1); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/streams.ts` around lines 780 - 807, disconnectStream aborts tailing asynchronously so advancing seq using the stale prevSeq can skip events that arrived during shutdown; modify the logic around inputStreams.disconnectStream(opts.id) + inputStreams.setLastSeqNum(...) to first await/ensure the stream is fully stopped (e.g., await the disconnect promise or wait for a closed/aborted signal for opts.id), then read the latest sequence via inputStreams.lastSeqNum(opts.id) and setLastSeqNum to (latestSeq ?? -1) + 1 (or otherwise max(prev+1, latest+1)) so you never advance past events that arrived during the asynchronous disconnect; reference disconnectStream, waitUntil, inputStreams.lastSeqNum, inputStreams.setLastSeqNum and opts.id when locating the change.
🧹 Nitpick comments (3)
packages/trigger-sdk/src/v3/ai.ts (1)
718-731: Consider logging a warning when token creation fails.If token creation fails,
turnAccessTokenremains an empty string and is passed to lifecycle hooks and the turn-complete chunk. The frontend may fail to reconnect. Consider logging a warning to aid debugging:} catch { - // Token creation failed + console.warn(`[chatTask] Failed to create access token for run ${currentRunId}`); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/trigger-sdk/src/v3/ai.ts` around lines 718 - 731, The token creation catch block swallows errors, leaving turnAccessToken empty and making frontend reconnection debugging hard; update the catch in the currentRunId branch around auth.createPublicToken (using chatAccessTokenTTL) to log a warning via the existing logger (or processLogger) that includes the caught error and context (e.g., currentRunId and that turnAccessToken will be empty) so lifecycle hooks and the turn-complete chunk consumers can be diagnosed easily.docs/guides/ai-chat.mdx (1)
218-231: Manual mode example missing message accumulation caveat.The manual mode example uses a regular
task()withChatTaskPayload, but this won't get automatic message accumulation across turns. The warning at lines 233-235 mentions this, but the example itself doesn't show how to handle multi-turn conversations manually.Consider adding a note that manual mode is single-turn only, or show how to implement accumulation manually:
export const manualChat = task({ id: "manual-chat", retry: { maxAttempts: 3 }, queue: { concurrencyLimit: 10 }, - run: async (payload: ChatTaskPayload) => { + // Note: This is single-turn only. For multi-turn, use chat.task() + run: async (payload: ChatTaskPayload) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/guides/ai-chat.mdx` around lines 218 - 231, The manualChat task example uses task() with ChatTaskPayload and calls streamText(...) then await chat.pipe(result) but doesn't show that this is single-turn only; update the example or add a short note: either state that manual mode (manualChat) does not auto-accumulate messages across turns, or modify the task to explicitly accumulate prior turns by merging stored conversation history with payload.messages before calling streamText (i.e., gather previousMessages + payload.messages into the messages argument), then pipe the result as shown; reference manualChat, ChatTaskPayload, streamText, and chat.pipe so readers can locate and implement the accumulation if they want multi-turn behavior.packages/core/src/v3/realtimeStreams/types.ts (1)
33-40: Prefertypealiases for the updated stream contracts.Since these signatures were touched, please migrate these interfaces to type aliases to match repo conventions.
As per coding guidelines:
**/*.{ts,tsx}: Use types over interfaces for TypeScript.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/src/v3/realtimeStreams/types.ts` around lines 33 - 40, Replace the two exported interfaces with exported type aliases: change RealtimeStreamInstance and StreamsWriter from interface declarations to type aliases while preserving their shapes and member names (ensure RealtimeStreamInstance still exposes wait(): Promise<StreamWriteResult> and a getter stream: AsyncIterableStream<T>, and StreamsWriter still exposes wait(): Promise<StreamWriteResult>); keep the same generic parameter T and export names so public API is unchanged and update any references if needed to the new type aliases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/guides/ai-chat.mdx`:
- Around line 804-817: The docs table for ChatTaskOptions is missing the
chatAccessTokenTTL entry; update the ChatTaskOptions table in
docs/guides/ai-chat.mdx to include a row for `chatAccessTokenTTL` (property on
the ChatTaskOptions type) with Type `string`, Default `"1h"`, and a short
Description like "TTL for generated chat access tokens" so it matches the
`chatAccessTokenTTL` field defined in the ChatTaskOptions type in ai.ts.
---
Outside diff comments:
In `@packages/trigger-sdk/src/v3/streams.ts`:
- Around line 780-807: disconnectStream aborts tailing asynchronously so
advancing seq using the stale prevSeq can skip events that arrived during
shutdown; modify the logic around inputStreams.disconnectStream(opts.id) +
inputStreams.setLastSeqNum(...) to first await/ensure the stream is fully
stopped (e.g., await the disconnect promise or wait for a closed/aborted signal
for opts.id), then read the latest sequence via inputStreams.lastSeqNum(opts.id)
and setLastSeqNum to (latestSeq ?? -1) + 1 (or otherwise max(prev+1, latest+1))
so you never advance past events that arrived during the asynchronous
disconnect; reference disconnectStream, waitUntil, inputStreams.lastSeqNum,
inputStreams.setLastSeqNum and opts.id when locating the change.
---
Nitpick comments:
In `@docs/guides/ai-chat.mdx`:
- Around line 218-231: The manualChat task example uses task() with
ChatTaskPayload and calls streamText(...) then await chat.pipe(result) but
doesn't show that this is single-turn only; update the example or add a short
note: either state that manual mode (manualChat) does not auto-accumulate
messages across turns, or modify the task to explicitly accumulate prior turns
by merging stored conversation history with payload.messages before calling
streamText (i.e., gather previousMessages + payload.messages into the messages
argument), then pipe the result as shown; reference manualChat, ChatTaskPayload,
streamText, and chat.pipe so readers can locate and implement the accumulation
if they want multi-turn behavior.
In `@packages/core/src/v3/realtimeStreams/types.ts`:
- Around line 33-40: Replace the two exported interfaces with exported type
aliases: change RealtimeStreamInstance and StreamsWriter from interface
declarations to type aliases while preserving their shapes and member names
(ensure RealtimeStreamInstance still exposes wait(): Promise<StreamWriteResult>
and a getter stream: AsyncIterableStream<T>, and StreamsWriter still exposes
wait(): Promise<StreamWriteResult>); keep the same generic parameter T and
export names so public API is unchanged and update any references if needed to
the new type aliases.
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 718-731: The token creation catch block swallows errors, leaving
turnAccessToken empty and making frontend reconnection debugging hard; update
the catch in the currentRunId branch around auth.createPublicToken (using
chatAccessTokenTTL) to log a warning via the existing logger (or processLogger)
that includes the caught error and context (e.g., currentRunId and that
turnAccessToken will be empty) so lifecycle hooks and the turn-complete chunk
consumers can be diagnosed easily.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: ebda7b1b-2998-4f72-a46e-4207104ed513
⛔ Files ignored due to path filters (4)
references/ai-chat/src/app/actions.tsis excluded by!references/**references/ai-chat/src/components/chat-app.tsxis excluded by!references/**references/ai-chat/src/components/chat.tsxis excluded by!references/**references/ai-chat/src/trigger/chat.tsis excluded by!references/**
📒 Files selected for processing (10)
docs/guides/ai-chat.mdxpackages/core/src/v3/realtimeStreams/manager.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/core/src/v3/realtimeStreams/types.tspackages/trigger-sdk/src/v3/ai.tspackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/streams.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (29)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
- GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
- GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
- GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
- GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
- GitHub Check: sdk-compat / Cloudflare Workers
- GitHub Check: sdk-compat / Deno Runtime
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
- GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
- GitHub Check: typecheck / typecheck
- GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
- GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
- GitHub Check: sdk-compat / Bun Runtime
- GitHub Check: Analyze (javascript-typescript)
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.{ts,tsx}: Use types over interfaces for TypeScript
Avoid using enums; prefer string unions or const objects instead
**/*.{ts,tsx}: In TypeScript SDK usage, always import from@trigger.dev/sdk, never from@trigger.dev/sdk/v3or use deprecated client.defineJob
Import from@trigger.dev/coresubpaths only, never from the root
Use the Run Engine 2.0 (@internal/run-engine) and redis-worker for all new work, not legacy V1 MarQS queue or deprecated V1 functions
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/trigger-sdk/src/v3/streams.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.tspackages/trigger-sdk/src/v3/ai.ts
{packages/core,apps/webapp}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use zod for validation in packages/core and apps/webapp
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Use function declarations instead of default exports
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/trigger-sdk/src/v3/streams.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.tspackages/trigger-sdk/src/v3/ai.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/otel-metrics.mdc)
**/*.ts: When creating or editing OTEL metrics (counters, histograms, gauges), ensure metric attributes have low cardinality by using only enums, booleans, bounded error codes, or bounded shard IDs
Do not use high-cardinality attributes in OTEL metrics such as UUIDs/IDs (envId, userId, runId, projectId, organizationId), unbounded integers (itemCount, batchSize, retryCount), timestamps (createdAt, startTime), or free-form strings (errorMessage, taskName, queueName)
When exporting OTEL metrics via OTLP to Prometheus, be aware that the exporter automatically adds unit suffixes to metric names (e.g., 'my_duration_ms' becomes 'my_duration_ms_milliseconds', 'my_counter' becomes 'my_counter_total'). Account for these transformations when writing Grafana dashboards or Prometheus queries
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/trigger-sdk/src/v3/streams.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.tspackages/trigger-sdk/src/v3/ai.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}
📄 CodeRabbit inference engine (AGENTS.md)
Format code using Prettier before committing
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/trigger-sdk/src/v3/streams.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.tspackages/trigger-sdk/src/v3/ai.ts
packages/core/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/core/CLAUDE.md)
Never import the root package (
@trigger.dev/core). Always use subpath imports such as@trigger.dev/core/v3,@trigger.dev/core/v3/utils,@trigger.dev/core/logger, or@trigger.dev/core/schemas
Files:
packages/core/src/v3/realtimeStreams/manager.tspackages/core/src/v3/realtimeStreams/streamInstance.tspackages/core/src/v3/realtimeStreams/noopManager.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/core/src/v3/realtimeStreams/streamsWriterV1.ts
docs/**/*.{md,mdx}
📄 CodeRabbit inference engine (CLAUDE.md)
Docs in docs/ directory should use Mintlify MDX format following conventions in docs/CLAUDE.md
Files:
docs/guides/ai-chat.mdx
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/CLAUDE.md)
docs/**/*.mdx: MDX documentation pages must include frontmatter with title (required), description (required), and sidebarTitle (optional) in YAML format
Use Mintlify components for structured content: , , , , , , /, /
Always import from@trigger.dev/sdkin code examples (never from@trigger.dev/sdk/v3)
Code examples must be complete and runnable where possible
Use language tags in code fences:typescript,bash,json
Files:
docs/guides/ai-chat.mdx
packages/trigger-sdk/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/ai.ts
packages/trigger-sdk/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (packages/trigger-sdk/CLAUDE.md)
Always import from
@trigger.dev/sdk. Never use@trigger.dev/sdk/v3(deprecated path alias)
Files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/ai.ts
🧠 Learnings (19)
📓 Common learnings
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Use `trigger.dev/react-hooks` package for realtime subscriptions in React components
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes
Applied to files:
packages/core/src/v3/realtimeStreams/manager.tsdocs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/core/src/v3/realtimeStreams/types.tspackages/core/src/v3/realtimeStreams/streamsWriterV2.tspackages/trigger-sdk/src/v3/streams.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:43:34.140Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/cli-v3/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:34.140Z
Learning: Keep SDK documentation in `rules/` and `.claude/skills/trigger-dev-tasks/` synchronized when features are added or changed
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2026-03-02T12:43:37.906Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/core/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:37.906Z
Learning: Exercise caution with changes to trigger.dev/core as they affect both the customer-facing SDK and server-side webapp - breaking changes can impact deployed user tasks and the platform simultaneously
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2026-03-02T12:43:48.124Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: packages/trigger-sdk/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:48.124Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx,js,jsx} : Always import from `trigger.dev/sdk`. Never use `trigger.dev/sdk/v3` (deprecated path alias)
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:43:02.539Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: docs/CLAUDE.md:0-0
Timestamp: 2026-03-02T12:43:02.539Z
Learning: Applies to docs/**/*.mdx : Always import from `trigger.dev/sdk` in code examples (never from `trigger.dev/sdk/v3`)
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
Applied to files:
docs/guides/ai-chat.mdxpackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `yourTask.trigger()` to trigger a task from inside another task with specified payload
Applied to files:
docs/guides/ai-chat.mdx
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK
Applied to files:
packages/trigger-sdk/src/v3/chat.ts
📚 Learning: 2025-11-27T16:26:37.432Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-27T16:26:37.432Z
Learning: Applies to packages/trigger-sdk/**/*.{ts,tsx} : In the Trigger.dev SDK (packages/trigger-sdk), prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code
Applied to files:
packages/trigger-sdk/src/v3/chat.tspackages/trigger-sdk/src/v3/streams.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-02T12:42:41.110Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-02T12:42:41.110Z
Learning: Applies to **/*.{ts,tsx} : In TypeScript SDK usage, always import from trigger.dev/sdk, never from trigger.dev/sdk/v3 or use deprecated client.defineJob
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2026-03-03T13:08:03.862Z
Learnt from: ericallam
Repo: triggerdotdev/trigger.dev PR: 3166
File: packages/redis-worker/src/fair-queue/index.ts:1114-1121
Timestamp: 2026-03-03T13:08:03.862Z
Learning: In packages/redis-worker/src/fair-queue/index.ts, it's acceptable for the worker queue depth cap check to allow overshooting by up to batchClaimSize messages per iteration, as the next iteration will recheck and prevent sustained growth beyond the limit.
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Attach metadata to task runs using the metadata option when triggering, and access/update it inside runs using metadata functions
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use metadata methods (set, del, replace, append, remove, increment, decrement, stream, flush) to update metadata during task execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Export tasks with unique IDs within the project to enable proper task discovery and execution
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
📚 Learning: 2025-11-27T16:27:35.304Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: .cursor/rules/writing-tasks.mdc:0-0
Timestamp: 2025-11-27T16:27:35.304Z
Learning: Applies to **/trigger/**/*.{ts,tsx,js,jsx} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation
Applied to files:
packages/trigger-sdk/src/v3/ai.ts
🔇 Additional comments (19)
packages/trigger-sdk/src/v3/chat.ts (4)
278-279: Passheadersoption tosendInputStreamAPI call.The
extraHeadersstored on the transport are used for SSE subscriptions (Line 415) but not passed to thesendInputStreamcall here. This breaks proxy/tenant routing or custom auth flows that depend on these headers.Proposed fix
const apiClient = new ApiClient(this.baseURL, session.publicAccessToken); - await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, minimalPayload); + await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, minimalPayload, { + headers: this.extraHeaders, + });
297-302: Passheadersoption totriggerTaskAPI call.Same issue as
sendInputStream— theextraHeadersare not forwarded to thetriggerTaskcall, breaking custom auth or proxy routing.Proposed fix
- const triggerResponse = await apiClient.triggerTask(this.taskId, { - payload, - options: { - payloadType: "application/json", - }, - }); + const triggerResponse = await apiClient.triggerTask( + this.taskId, + { + payload, + options: { + payloadType: "application/json", + }, + }, + undefined, + { headers: this.extraHeaders } + );
438-441: Passheadersoption to stop signalsendInputStreamcall.The stop signal path also omits
extraHeaders.Proposed fix
const api = new ApiClient(this.baseURL, session.publicAccessToken); api - .sendInputStream(session.runId, CHAT_STOP_STREAM_ID, { stop: true }) + .sendInputStream(session.runId, CHAT_STOP_STREAM_ID, { stop: true }, { + headers: this.extraHeaders, + }) .catch(() => {}); // Best-effort
459-550: LGTM — Stream subscription and chunk processing logic.The SSE stream subscription correctly:
- Handles
__trigger_turn_completecontrol chunks and token refresh- Tracks
lastEventIdfor stream resumption- Manages abort signals and graceful shutdown
- Skips leftover chunks after stop via
skipToTurnCompletepackages/trigger-sdk/src/v3/ai.ts (6)
247-248: Global_chatPipeCountis race-prone with concurrent chat runs.
_chatPipeCountis module-global, so concurrent chat runs can mutate the same counter and incorrectly suppress or trigger auto-piping in other runs.Suggested fix — scope counter per run
-let _chatPipeCount = 0; +const _chatPipeCountByRun = new Map<string, number>(); async function pipeChat( source: UIMessageStreamable | AsyncIterable<unknown> | ReadableStream<unknown>, options?: PipeChatOptions ): Promise<void> { - _chatPipeCount++; + const runId = taskContext.ctx?.run.id; + if (runId) { + _chatPipeCountByRun.set(runId, (_chatPipeCountByRun.get(runId) ?? 0) + 1); + } // ... }Then in the chatTask turn loop, reset and check the run-scoped counter instead of the global.
453-552:chatTaskdoes not support customstreamKeyend-to-end.
TriggerChatTransportallows configuring a customstreamKey, butchatTaskhardcodesCHAT_STREAM_KEYfor auto-piping (Line 801) and the turn-complete control chunk (Line 1047). If a user configures a non-default stream key on the frontend, the turn completion will hang because the frontend subscribes to a different stream than where the backend writes.Suggested fix — add `streamKey` option to `chatTask`
Add a
streamKeyoption toChatTaskOptionsand thread it through topipeChatandwriteTurnCompleteChunk:export type ChatTaskOptions<TIdentifier extends string> = Omit<...> & { + /** Stream key for output. Must match TriggerChatTransport.streamKey. `@default` "chat" */ + streamKey?: string; run: (payload: ChatTaskRunPayload) => Promise<unknown>; // ... }; function chatTask<TIdentifier extends string>(...) { const { run: userRun, + streamKey = CHAT_STREAM_KEY, // ... } = options; // In auto-pipe: - await pipeChat(uiStream, { signal: combinedSignal, spanName: "stream response" }); + await pipeChat(uiStream, { streamKey, signal: combinedSignal, spanName: "stream response" }); // In turn complete: - const turnCompleteResult = await writeTurnCompleteChunk(...); + const turnCompleteResult = await writeTurnCompleteChunk(streamKey, ...); } -async function writeTurnCompleteChunk(chatId?: string, publicAccessToken?: string) { - const { waitUntilComplete } = streams.writer(CHAT_STREAM_KEY, { +async function writeTurnCompleteChunk(streamKey: string = CHAT_STREAM_KEY, chatId?: string, publicAccessToken?: string) { + const { waitUntilComplete } = streams.writer(streamKey, {
880-884: Verify handling of multiple buffered messages.When messages arrive during streaming, they're buffered in
pendingMessages. However, only the first message is used (Line 882) — any additional messages are discarded. If multiple messages arrive during a streaming turn, subsequent ones will be lost.Consider preserving the backlog:
+ const queuedMessages: ChatTaskWirePayload[] = []; + // ... inside turn loop after processing ... if (pendingMessages.length > 0) { - currentWirePayload = pendingMessages[0]!; + currentWirePayload = pendingMessages.shift()!; + queuedMessages.push(...pendingMessages); return "continue"; } + + // Check queued messages before waiting + if (queuedMessages.length > 0) { + currentWirePayload = queuedMessages.shift()!; + return "continue"; + }The past review indicated this was addressed. Please verify the intended behavior — is dropping extra messages intentional (e.g., expecting only one message per turn)?
734-777: LGTM — Lifecycle hooks implementation.The
onChatStartandonTurnStarthooks are correctly sequenced and wrapped in traced spans. The scoped access token is minted per-turn and passed to callbacks for persistence.
691-713: LGTM — Message accumulation logic.The accumulation correctly handles:
- Turn 0: Full history from frontend initializes the accumulator
- Regenerate: Full history resets the accumulator (removing last assistant message)
- Submit (turn 1+): Only new messages appended to existing accumulator
This aligns with the frontend transport sending minimal payloads after the first turn.
1096-1115: LGTM —stripProviderMetadatahelper.This correctly strips ephemeral OpenAI
itemIdfields that would cause 404 errors when sent back in subsequentstreamTextcalls. The implementation preserves other provider metadata while removing only the problematic fields.docs/guides/ai-chat.mdx (2)
20-22: Verify theaipackage version requirement.The documentation states the
aipackage v5.0.0 or later is required. A past review indicated the reference implementation uses v6.0.0. Please verify and update the version requirement if needed.#!/bin/bash # Check ai package version in reference implementations and package.json files rg -n '"ai"\s*:' --glob '**/package.json' | head -20
344-406: LGTM — Persistence documentation.The persistence section clearly explains:
- What needs to be persisted (messages + sessions)
- Server-side persistence via
onTurnStart(before streaming) andonTurnComplete(after)- The purpose of
lastEventIdfor stream resumption- Complete code examples with database operations
packages/core/src/v3/realtimeStreams/types.ts (1)
29-31:StreamWriteResultis a clean API extension.This shape keeps backward compatibility while enabling resume metadata.
packages/core/src/v3/realtimeStreams/manager.ts (1)
19-21: Type propagation for active stream waits is consistent.Good alignment with the new
wait(): Promise<StreamWriteResult>contract.packages/core/src/v3/realtimeStreams/noopManager.ts (1)
18-18: Noop manager now matches the wait contract.Returning an object here correctly aligns with
StreamWriteResult.packages/core/src/v3/realtimeStreams/streamsWriterV1.ts (1)
261-264:wait()return type update is correctly wired.This keeps V1 aligned with the shared writer contract.
packages/core/src/v3/realtimeStreams/streamInstance.ts (1)
66-69:StreamInstance.wait()now correctly preserves writer output.Returning
writer.wait()is the right propagation point forStreamWriteResult.packages/core/src/v3/realtimeStreams/streamsWriterV2.ts (1)
173-176: CapturinglastSeqNumand exposing it fromwait()is a solid improvement.This makes V2 waits materially more useful for resume flows.
Also applies to: 188-191
packages/trigger-sdk/src/v3/streams.ts (1)
143-201: Span customization and waitpoint entity attribution are well integrated.
spanName/collapsedwiring andwaitpointIdattribution improve trace clarity.Also applies to: 757-775, 822-823
| ### ChatTaskOptions | ||
|
|
||
| | Option | Type | Default | Description | | ||
| |--------|------|---------|-------------| | ||
| | `id` | `string` | required | Task identifier | | ||
| | `run` | `(payload: ChatTaskRunPayload) => Promise<unknown>` | required | Handler for each turn | | ||
| | `onChatStart` | `(event: ChatStartEvent) => Promise<void> \| void` | — | Fires on turn 0 before `run()` | | ||
| | `onTurnStart` | `(event: TurnStartEvent) => Promise<void> \| void` | — | Fires every turn before `run()` | | ||
| | `onTurnComplete` | `(event: TurnCompleteEvent) => Promise<void> \| void` | — | Fires after each turn completes | | ||
| | `maxTurns` | `number` | `100` | Max conversational turns per run | | ||
| | `turnTimeout` | `string` | `"1h"` | How long to wait for next message | | ||
| | `warmTimeoutInSeconds` | `number` | `30` | Seconds to stay warm before suspending | | ||
|
|
||
| Plus all standard [TaskOptions](/tasks/overview) — `retry`, `queue`, `machine`, `maxDuration`, etc. |
There was a problem hiding this comment.
Add chatAccessTokenTTL to ChatTaskOptions table.
The ChatTaskOptions type in ai.ts (lines 542-552) includes chatAccessTokenTTL with default "1h", but it's missing from the documentation table.
| `turnTimeout` | `string` | `"1h"` | How long to wait for next message |
| `warmTimeoutInSeconds` | `number` | `30` | Seconds to stay warm before suspending |
+| `chatAccessTokenTTL` | `string` | `"1h"` | How long the scoped access token remains valid |📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### ChatTaskOptions | |
| | Option | Type | Default | Description | | |
| |--------|------|---------|-------------| | |
| | `id` | `string` | required | Task identifier | | |
| | `run` | `(payload: ChatTaskRunPayload) => Promise<unknown>` | required | Handler for each turn | | |
| | `onChatStart` | `(event: ChatStartEvent) => Promise<void> \| void` | — | Fires on turn 0 before `run()` | | |
| | `onTurnStart` | `(event: TurnStartEvent) => Promise<void> \| void` | — | Fires every turn before `run()` | | |
| | `onTurnComplete` | `(event: TurnCompleteEvent) => Promise<void> \| void` | — | Fires after each turn completes | | |
| | `maxTurns` | `number` | `100` | Max conversational turns per run | | |
| | `turnTimeout` | `string` | `"1h"` | How long to wait for next message | | |
| | `warmTimeoutInSeconds` | `number` | `30` | Seconds to stay warm before suspending | | |
| Plus all standard [TaskOptions](/tasks/overview) — `retry`, `queue`, `machine`, `maxDuration`, etc. | |
| ### ChatTaskOptions | |
| | Option | Type | Default | Description | | |
| |--------|------|---------|-------------| | |
| | `id` | `string` | required | Task identifier | | |
| | `run` | `(payload: ChatTaskRunPayload) => Promise<unknown>` | required | Handler for each turn | | |
| | `onChatStart` | `(event: ChatStartEvent) => Promise<void> \| void` | — | Fires on turn 0 before `run()` | | |
| | `onTurnStart` | `(event: TurnStartEvent) => Promise<void> \| void` | — | Fires every turn before `run()` | | |
| | `onTurnComplete` | `(event: TurnCompleteEvent) => Promise<void> \| void` | — | Fires after each turn completes | | |
| | `maxTurns` | `number` | `100` | Max conversational turns per run | | |
| | `turnTimeout` | `string` | `"1h"` | How long to wait for next message | | |
| | `warmTimeoutInSeconds` | `number` | `30` | Seconds to stay warm before suspending | | |
| | `chatAccessTokenTTL` | `string` | `"1h"` | How long the scoped access token remains valid | | |
| Plus all standard [TaskOptions](/tasks/overview) — `retry`, `queue`, `machine`, `maxDuration`, etc. |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/guides/ai-chat.mdx` around lines 804 - 817, The docs table for
ChatTaskOptions is missing the chatAccessTokenTTL entry; update the
ChatTaskOptions table in docs/guides/ai-chat.mdx to include a row for
`chatAccessTokenTTL` (property on the ChatTaskOptions type) with Type `string`,
Default `"1h"`, and a short Description like "TTL for generated chat access
tokens" so it matches the `chatAccessTokenTTL` field defined in the
ChatTaskOptions type in ai.ts.
No description provided.