Skip to content

feat(sdk): AI SDK custom useChat transport & chat.task harness#3173

Draft
ericallam wants to merge 32 commits intomainfrom
feature/tri-7532-ai-sdk-chat-transport-and-chat-task-system
Draft

feat(sdk): AI SDK custom useChat transport & chat.task harness#3173
ericallam wants to merge 32 commits intomainfrom
feature/tri-7532-ai-sdk-chat-transport-and-chat-task-system

Conversation

@ericallam
Copy link
Member

No description provided.

cursoragent and others added 27 commits March 4, 2026 13:48
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-bot
Copy link

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: cfe56ab

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 29 packages
Name Type
@trigger.dev/sdk Minor
@trigger.dev/python Minor
@internal/sdk-compat-tests Patch
references-ai-chat Patch
d3-chat Patch
references-d3-openai-agents Patch
references-nextjs-realtime Patch
references-realtime-hooks-test Patch
references-realtime-streams Patch
references-telemetry Patch
@trigger.dev/build Minor
@trigger.dev/core Minor
@trigger.dev/react-hooks Minor
@trigger.dev/redis-worker Minor
@trigger.dev/rsc Minor
@trigger.dev/schema-to-json Minor
@trigger.dev/database Minor
@trigger.dev/otlp-importer Minor
trigger.dev Minor
@internal/cache Patch
@internal/clickhouse Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 4, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a browser-safe chat transport and factory (TriggerChatTransport, createChatTransport) and a React hook (useTriggerChatTransport) under @trigger.dev/sdk/chat. Extends the backend AI SDK (@trigger.dev/sdk/ai) with chat primitives (chatTask, pipeChat, createChatAccessToken, CHAT_STREAM_KEY), many chat-related types, and runtime helpers. Implements per-item oversized NDJSON handling (OversizedItemMarker, extractIndexAndTask) and removes BatchItemTooLargeError/related size checks. Adds InputStreamManager methods (setLastSeqNum, shiftBuffer, disconnectStream) and introduces StreamWriteResult and new realtime options (spanName, collapsed). Updates package exports, docs, tests, and package-installation guidance.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely missing. The author provided no description content, violating the template requirement for testing details, changelog, and confirmation of following contributing guidelines. Add a detailed PR description including testing steps, a changelog summary, and confirmation that contributing guidelines were followed per the provided template.
Docstring Coverage ⚠️ Warning Docstring coverage is 68.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly summarizes the main change: introducing AI SDK custom useChat transport and chat.task harness, which aligns with the extensive additions across chat transport, backend task handling, and React integration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/tri-7532-ai-sdk-chat-transport-and-chat-task-system

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ericallam ericallam changed the title feature/tri-7532-ai-sdk-chat-transport-and-chat-task-system feat(sdk): AI SDK custom useChat transport & chat.task harness Mar 4, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/trigger-sdk/src/v3/chat.test.ts (1)

22-23: Consider resetting messageIdCounter in beforeEach.

The messageIdCounter is 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 in beforeEach would 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 useRef pattern correctly preserves the transport instance across re-renders. Note that if baseURL, 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 than accessToken.

The accessToken function 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

📥 Commits

Reviewing files that changed from the base of the PR and between c013322 and d7817e0.

⛔ Files ignored due to path filters (13)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • references/ai-chat/next-env.d.ts is excluded by !references/**
  • references/ai-chat/next.config.ts is excluded by !references/**
  • references/ai-chat/package.json is excluded by !references/**
  • references/ai-chat/postcss.config.mjs is excluded by !references/**
  • references/ai-chat/src/app/actions.ts is excluded by !references/**
  • references/ai-chat/src/app/globals.css is excluded by !references/**
  • references/ai-chat/src/app/layout.tsx is excluded by !references/**
  • references/ai-chat/src/app/page.tsx is excluded by !references/**
  • references/ai-chat/src/components/chat.tsx is excluded by !references/**
  • references/ai-chat/src/trigger/chat.ts is excluded by !references/**
  • references/ai-chat/trigger.config.ts is excluded by !references/**
  • references/ai-chat/tsconfig.json is 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.md
  • CLAUDE.md
  • docs/docs.json
  • docs/guides/ai-chat.mdx
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/manager.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/trigger-sdk/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/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/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths 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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • docs/docs.json
  • packages/trigger-sdk/src/v3/chat.ts
  • CLAUDE.md
  • packages/core/src/v3/inputStreams/types.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/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.ts
  • packages/core/src/v3/inputStreams/index.ts
  • packages/core/src/v3/inputStreams/noopManager.ts
  • packages/core/src/v3/inputStreams/types.ts
  • packages/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/sdk in 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 in docs.json which includes navigation structure, theme, and metadata
Navigation structure in docs.json should be organized using navigation.dropdowns with 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.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/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 descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when 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.mdx
  • CLAUDE.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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • .changeset/ai-sdk-chat-transport.md
  • packages/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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • .changeset/ai-sdk-chat-transport.md
  • packages/core/src/v3/realtimeStreams/types.ts
  • 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 the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • docs/guides/ai-chat.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • .changeset/ai-sdk-chat-transport.md
  • 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 `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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • .changeset/ai-sdk-chat-transport.md
  • 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:

  • docs/guides/ai-chat.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • .changeset/ai-sdk-chat-transport.md
  • 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: Use `TriggerAuthContext` provider to supply Public Access Token to Trigger.dev React hooks

Applied to files:

  • docs/guides/ai-chat.mdx
  • packages/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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • 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/**/*.{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.mdx
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat-constants.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • .changeset/ai-sdk-chat-transport.md
  • 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/chat.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/package.json
  • 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/chat.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/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.md
  • packages/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.md
  • packages/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.

./chat and ./chat/react are 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-chat under “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 setLastSeqNum correctly guards against backward sequence number movement, shiftBuffer properly handles cleanup when the buffer becomes empty, and both methods align with the documented behavior in types.ts.


181-188: LGTM! Clean stream disconnection implementation.

The disconnectStream method 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 InputStreamManager interface with appropriate default behavior: shiftBuffer returns false (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/react import 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/afterEach correctly saves and restores global.fetch, and vi.restoreAllMocks() ensures clean state between tests.


919-1022: Excellent coverage of lastEventId tracking and SSE reconnection.

The tests thoroughly verify that Last-Event-ID is 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 ai package types and @trigger.dev/core/v3 utilities that are isomorphic.


38-116: LGTM!

Type definitions are well-structured with comprehensive JSDoc. The accessToken flexibility (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_complete control chunks
  • Skip-to-turn-complete state for abort/resume scenarios
  • lastEventId tracking 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 reconnectToStream method correctly handles missing sessions by returning null. The createChatTransport factory function provides a clean functional alternative with good documentation.

Also applies to: 392-412


274-277: ⚠️ Potential issue | 🔴 Critical

Update minimum Node.js engine requirement to match AbortSignal.any() availability.

The SDK declares engines.node >= 18.20.0 in package.json, but uses AbortSignal.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.node to >= 20.3.0 in 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 code
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

Comment on lines +216 to +238
```
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
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +268 to +269
let _chatPipeCount = 0;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/trigger-sdk/src/v3/ai.ts (2)

541-545: ⚠️ Potential issue | 🔴 Critical

Drain 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 | 🔴 Critical

Scope pipe-call tracking per run/turn instead of module-global state.

Line 245 defines _chatPipeCount as 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 shared CHAT_STREAM_KEY constant 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

📥 Commits

Reviewing files that changed from the base of the PR and between d7817e0 and 6b4e3dd.

📒 Files selected for processing (3)
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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.ts
  • packages/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/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths 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.ts
  • packages/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.ts
  • packages/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.ts
  • packages/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.ts
  • packages/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.ts
  • packages/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.ts
  • packages/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
  • 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 `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • packages/trigger-sdk/src/v3/chat.ts
  • packages/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.ts
  • packages/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.ts
  • 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 `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • packages/trigger-sdk/src/v3/chat.ts
  • 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/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.

lastEventId tracking plus skipToTurnComplete handling is robust and fits turn-based chat streaming well.

Comment on lines +382 to +425
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;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
packages/trigger-sdk/src/v3/ai.ts (3)

618-621: ⚠️ Potential issue | 🔴 Critical

Only 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 | 🟠 Major

Custom stream keys are not threaded through chatTask auto-pipe and turn-complete writes.

pipeChat and writeTurnCompleteChunk still default to CHAT_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

_chatPipeCount is 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 shared CHAT_STREAM_KEY constant 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: Use typescript fence labels instead of ts/tsx in 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b4e3dd and cc1ce9b.

⛔ Files ignored due to path filters (16)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • references/ai-chat/.gitignore is excluded by !references/**
  • references/ai-chat/package.json is excluded by !references/**
  • references/ai-chat/prisma.config.ts is excluded by !references/**
  • references/ai-chat/prisma/migrations/20260305112427_init/migration.sql is excluded by !references/**
  • references/ai-chat/prisma/migrations/migration_lock.toml is excluded by !references/**
  • references/ai-chat/prisma/schema.prisma is excluded by !references/**
  • references/ai-chat/src/app/actions.ts is excluded by !references/**
  • references/ai-chat/src/app/page.tsx is excluded by !references/**
  • references/ai-chat/src/components/chat-app.tsx is excluded by !references/**
  • references/ai-chat/src/components/chat-sidebar.tsx is excluded by !references/**
  • references/ai-chat/src/components/chat.tsx is excluded by !references/**
  • references/ai-chat/src/lib/models.ts is excluded by !references/**
  • references/ai-chat/src/lib/prisma.ts is excluded by !references/**
  • references/ai-chat/src/trigger/chat.ts is excluded by !references/**
  • references/ai-chat/trigger.config.ts is excluded by !references/**
📒 Files selected for processing (5)
  • docs/guides/ai-chat.mdx
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat-react.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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/sdk in 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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths 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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/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 descriptive describe and it blocks
Tests should avoid mocks or stubs and use the helpers from @internal/testcontainers when 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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • 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 `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • docs/guides/ai-chat.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • 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 `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • docs/guides/ai-chat.mdx
  • packages/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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • 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 `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.mdx
  • packages/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
  • packages/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, skipToTurnComplete behavior, 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.

Comment on lines +21 to +22
Requires `@trigger.dev/sdk` version **4.4.0 or later** and the `ai` package **v5.0.0 or later**.
</Note>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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.mdx

Repository: 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.

Comment on lines +103 to +104
global.fetch = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
const urlStr = typeof url === "string" ? url : url.toString();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Comment on lines +277 to +279
const apiClient = new ApiClient(this.baseURL, session.publicAccessToken);
await apiClient.sendInputStream(session.runId, CHAT_MESSAGES_STREAM_ID, minimalPayload);
return this.subscribeToStream(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "index.ts" | grep -E "apiClient|v3" | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 930


🏁 Script executed:

rg -l "class ApiClient" packages/core

Repository: 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 -50

Repository: triggerdotdev/trigger.dev

Length of output: 755


🏁 Script executed:

# Check sendInputStream method
rg -A 20 "sendInputStream\(" packages/core/src/v3/apiClient/index.ts | head -50

Repository: triggerdotdev/trigger.dev

Length of output: 641


🏁 Script executed:

wc -l packages/trigger-sdk/src/v3/chat.ts

Repository: triggerdotdev/trigger.dev

Length of output: 108


🏁 Script executed:

sed -n '270,310p' packages/trigger-sdk/src/v3/chat.ts

Repository: triggerdotdev/trigger.dev

Length of output: 1541


🏁 Script executed:

sed -n '420,430p' packages/trigger-sdk/src/v3/chat.ts

Repository: 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 -80

Repository: 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-effort

Applies 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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

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 | 🔴 Critical

Potential input event loss when advancing sequence after disconnect.

disconnectStream() aborts tailing asynchronously, so new tail events can still advance seq before shutdown. Then setLastSeqNum(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, turnAccessToken remains 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() with ChatTaskPayload, 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: Prefer type aliases 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5ee5959 and cfe56ab.

⛔ Files ignored due to path filters (4)
  • references/ai-chat/src/app/actions.ts is excluded by !references/**
  • references/ai-chat/src/components/chat-app.tsx is excluded by !references/**
  • references/ai-chat/src/components/chat.tsx is excluded by !references/**
  • references/ai-chat/src/trigger/chat.ts is excluded by !references/**
📒 Files selected for processing (10)
  • docs/guides/ai-chat.mdx
  • packages/core/src/v3/realtimeStreams/manager.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV1.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/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/v3 or use deprecated client.defineJob
Import from @trigger.dev/core subpaths 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.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV1.ts
  • packages/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.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV1.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV1.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV1.ts
  • packages/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.ts
  • packages/core/src/v3/realtimeStreams/streamInstance.ts
  • packages/core/src/v3/realtimeStreams/noopManager.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/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/sdk in 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.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/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.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/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.ts
  • docs/guides/ai-chat.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/core/src/v3/realtimeStreams/types.ts
  • packages/core/src/v3/realtimeStreams/streamsWriterV2.ts
  • packages/trigger-sdk/src/v3/streams.ts
  • packages/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.mdx
  • packages/trigger-sdk/src/v3/chat.ts
  • packages/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.mdx
  • 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 `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.ts
  • packages/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.ts
  • packages/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: Pass headers option to sendInputStream API call.

The extraHeaders stored on the transport are used for SSE subscriptions (Line 415) but not passed to the sendInputStream call 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: Pass headers option to triggerTask API call.

Same issue as sendInputStream — the extraHeaders are not forwarded to the triggerTask call, 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: Pass headers option to stop signal sendInputStream call.

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_complete control chunks and token refresh
  • Tracks lastEventId for stream resumption
  • Manages abort signals and graceful shutdown
  • Skips leftover chunks after stop via skipToTurnComplete
packages/trigger-sdk/src/v3/ai.ts (6)

247-248: Global _chatPipeCount is race-prone with concurrent chat runs.

_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 — 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: chatTask does not support custom streamKey end-to-end.

TriggerChatTransport allows configuring a custom streamKey, but chatTask hardcodes CHAT_STREAM_KEY for 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 streamKey option to ChatTaskOptions and thread it through to pipeChat and writeTurnCompleteChunk:

 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 onChatStart and onTurnStart hooks 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 — stripProviderMetadata helper.

This correctly strips ephemeral OpenAI itemId fields that would cause 404 errors when sent back in subsequent streamText calls. The implementation preserves other provider metadata while removing only the problematic fields.

docs/guides/ai-chat.mdx (2)

20-22: Verify the ai package version requirement.

The documentation states the ai package 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) and onTurnComplete (after)
  • The purpose of lastEventId for stream resumption
  • Complete code examples with database operations
packages/core/src/v3/realtimeStreams/types.ts (1)

29-31: StreamWriteResult is 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 for StreamWriteResult.

packages/core/src/v3/realtimeStreams/streamsWriterV2.ts (1)

173-176: Capturing lastSeqNum and exposing it from wait() 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/collapsed wiring and waitpointId attribution improve trace clarity.

Also applies to: 757-775, 822-823

Comment on lines +804 to +817
### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
### 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants