Skip to content

feat(sdk): AI SDK chat transport — run useChat as tasks#3065

Draft
ericallam wants to merge 16 commits intomainfrom
cursor/ai-sdk-chat-transport-5c3a
Draft

feat(sdk): AI SDK chat transport — run useChat as tasks#3065
ericallam wants to merge 16 commits intomainfrom
cursor/ai-sdk-chat-transport-5c3a

Conversation

@ericallam
Copy link
Member

@ericallam ericallam commented Feb 15, 2026

Adds first-class AI SDK chat integration to @trigger.dev/sdk, enabling developers to use Vercel AI SDK's useChat hook with Trigger.dev tasks as the backend. Chat completions run as durable, retryable tasks with built-in realtime streaming — no custom API routes needed.

What this enables

Instead of building a custom /api/chat endpoint, developers point useChat at a Trigger.dev task. The transport triggers the task, subscribes to its realtime stream, and pipes UIMessageChunk events back to the AI SDK — all handled automatically.

Frontend (@trigger.dev/sdk/chat)

Browser-safe module — no server-side AI SDK dependencies.

import { useChat } from "@ai-sdk/react";
import { TriggerChatTransport } from "@trigger.dev/sdk/chat";

function Chat({ accessToken }: { accessToken: string }) {
  const { messages, sendMessage, status } = useChat({
    transport: new TriggerChatTransport({
      task: "my-chat-task",
      accessToken,
    }),
  });
}

Backend (@trigger.dev/sdk/ai)

Simple — return streamText result, auto-piped:

import { chatTask } from "@trigger.dev/sdk/ai";
import { streamText, convertToModelMessages } from "ai";

export const myChatTask = chatTask({
  id: "my-chat-task",
  run: async ({ messages }) => {
    return streamText({
      model: openai("gpt-4o"),
      messages: convertToModelMessages(messages),
    });
  },
});

Complex — pipeChat() from anywhere in your agent code:

import { chatTask, pipeChat } from "@trigger.dev/sdk/ai";

export const myAgentTask = chatTask({
  id: "my-agent-task",
  run: async ({ messages }) => {
    await runComplexAgentLoop(messages);
    // pipeChat() called deep inside the agent loop
  },
});

New exports

@trigger.dev/sdk/chat (frontend, browser-safe): TriggerChatTransport, createChatTransport(), TriggerChatTransportOptions

@trigger.dev/sdk/ai (backend, extends existing ai.tool): chatTask(), pipeChat(), CHAT_STREAM_KEY, ChatTaskPayload, ChatTaskOptions, PipeChatOptions

Testing

  • 18 unit tests covering trigger + stream flow, reconnection, abort, concurrent sessions, error handling
  • All export types verified green across node10, node16 CJS/ESM, and bundler via attw

Checklist

  • New subpath exports added to SDK package.json + tshy config
  • Types verified with attw --pack . (all green)
  • 18/18 tests passing
  • Changeset added
  • CodeRabbit review feedback addressed

cursoragent and others added 3 commits February 15, 2026 11:40
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>
@cursor
Copy link

cursor bot commented Feb 15, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@changeset-bot
Copy link

changeset-bot bot commented Feb 15, 2026

🦋 Changeset detected

Latest commit: 7badb14

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 Feb 15, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (11)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • 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/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/**

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds a chat transport integration to the Trigger.dev SDK with two subpath exports: a browser-safe frontend module (@trigger.dev/sdk/chat) that provides TriggerChatTransport and createChatTransport, and backend additions in the AI module (@trigger.dev/sdk/ai) that provide chatTask, pipeChat, CHAT_STREAM_KEY, and related types. Package exports were updated to expose the new chat entry. A comprehensive test suite for the frontend transport was added to validate messaging, streaming, sessions, aborts, token handling, and URL/header behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Summary of changes

  • Frontend: new TriggerChatTransport class and createChatTransport() factory implementing ChatTransport<UIMessage>, with:

    • sendMessages() to trigger durable tasks and return a ReadableStream of UIMessageChunk
    • reconnectToStream() to resume streams using stored session state
    • per-chat session tracking (runId, token), token resolution (string or function), SSE subscription handling, abort/timeout behavior, and configurable options (task, accessToken, baseURL, streamKey, headers, streamTimeoutSeconds)
    • exports: TriggerChatTransportOptions, TriggerChatTransport, createChatTransport
  • Backend (AI module): chat task and streaming utilities:

    • CHAT_STREAM_KEY constant (default "chat")
    • ChatTaskPayload type describing chat payloads (messages, chatId, trigger, optional messageId/metadata)
    • PipeChatOptions type (streamKey, signal, target)
    • pipeChat() async function to pipe UI-message streams / async iterables / ReadableStreams to the frontend
    • ChatTaskOptions type and chatTask() helper that returns a pre-typed task which auto-pipes toUIMessageStream() results
    • internal stream detection utilities and integration with existing task/stream tooling
  • Package configuration: packages/trigger-sdk/package.json updated to add a ./chat export alongside ./ai with proper ESM/CommonJS mappings and typesVersions entries.

  • Tests: extensive new test suite for TriggerChatTransport covering construction, message sending, streaming parsing, SSE handling, abort scenarios, reconnects, multi-session isolation, dynamic access tokens, payload merging, and URL/header variations.

  • Docs: added docs/guides/ai-chat.mdx and updated docs/docs.json to include the new guide.

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Title check ✅ Passed The title accurately summarizes the main change: introducing AI SDK chat transport that enables useChat to run as durable Trigger.dev tasks.
Description check ✅ Passed Pull request description is comprehensive and well-structured, covering what is enabled, new exports, testing details, and a complete checklist.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/ai-sdk-chat-transport-5c3a

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.

cursoragent and others added 5 commits February 15, 2026 11:46
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>
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

🤖 Fix all issues with AI agents
In `@packages/ai/package.json`:
- Around line 43-45: The peerDependency for the "ai" package is too broad and
must be narrowed to "^6.0.0" because this package implements the v6
ChatTransport API (e.g., ChatTransport methods sendMessages with { trigger,
chatId, messageId, messages, abortSignal } and reconnectToStream returning
Promise<ReadableStream<UIMessageChunk> | null>); update the peerDependencies
entry for "ai" in package.json to "^6.0.0" (or alternatively release a separate
major package for v5 vs v6), ensuring only the v6-compatible consumer is
allowed.

In `@packages/ai/src/transport.ts`:
- Around line 113-120: The payload construction currently spreads ...(body ??
{}) after core fields so any keys in body can silently override core properties
like messages, chatId, trigger, messageId, metadata; update the payload creation
in transport.ts so the body spread comes first (e.g., { ...(body ?? {}),
messages, chatId, trigger, messageId, metadata }) to ensure the core fields
(messages, chatId, trigger, messageId, metadata) always take precedence, or
alternatively add a comment documenting that body is allowed to override these
keys if that behavior is intentional.
- Around line 205-210: The abort handling currently calls reader.cancel()
without awaiting it before reader.releaseLock(), which can throw because
cancel() returns a Promise; update the abort branch in the stream handler to
await the Promise returned by reader.cancel() before calling
reader.releaseLock() (or omit releaseLock() and just await reader.cancel() then
controller.close() and return) so that abortSignal, reader.cancel(),
reader.releaseLock(), and controller.close() are handled in the correct order
and the cancel promise is settled before releasing the lock.
🧹 Nitpick comments (6)
packages/ai/src/transport.ts (2)

213-213: Unsafe cast of value.chunk to UIMessageChunk.

value.chunk is cast with as UIMessageChunk without runtime validation. If the SSE stream delivers unexpected data (e.g., a heartbeat or malformed event), this silently enqueues bad data into the stream. Consider a lightweight guard or at least a null/undefined check before enqueuing.

Minimal guard example
-              controller.enqueue(value.chunk as UIMessageChunk);
+              if (value.chunk != null) {
+                controller.enqueue(value.chunk as UIMessageChunk);
+              }

134-137: Update TriggerTaskResponse schema to include publicAccessToken property.

The publicAccessToken is always present in the response (guaranteed by the triggerTask() implementation in ApiClient), but the Zod schema at packages/core/src/v3/schemas/api.ts only defines id and isCached. This type mismatch forces the defensive in check and manual type assertion in this code. Add publicAccessToken: z.string() to the schema so TypeScript accurately reflects the runtime behavior.

packages/ai/src/transport.test.ts (4)

22-36: Date.now() in message factories can produce duplicate IDs.

When multiple messages are created in the same test (e.g., lines 818–821), Date.now() may return the same value, yielding identical ids. Consider using an incrementing counter instead.

🔧 Proposed fix
+let msgCounter = 0;
+
 function createUserMessage(text: string): UIMessage {
   return {
-    id: `msg-${Date.now()}`,
+    id: `msg-user-${++msgCounter}`,
     role: "user",
     parts: [{ type: "text", text }],
   };
 }
 
 function createAssistantMessage(text: string): UIMessage {
   return {
-    id: `msg-${Date.now()}`,
+    id: `msg-asst-${++msgCounter}`,
     role: "assistant",
     parts: [{ type: "text", text }],
   };
 }

96-165: Extract a shared fetch-mock helper to reduce boilerplate.

Nearly every test block rebuilds the same two-branch fetch mock (trigger endpoint + SSE stream endpoint) with minor variations. A shared factory like createMockFetch({ runId, publicToken, sseChunks, triggerStatus }) would eliminate ~150 lines of duplication and make each test's intent more prominent.

♻️ Sketch of a shared helper
type MockFetchOptions = {
  runId?: string;
  publicToken?: string;
  triggerStatus?: number;
  triggerBody?: Record<string, unknown>;
  sseChunks?: UIMessageChunk[];
  sseStream?: ReadableStream<Uint8Array>;
};

function createMockFetch(opts: MockFetchOptions = {}) {
  const {
    runId = "run_default",
    publicToken = "pub_token",
    triggerStatus = 200,
    triggerBody,
    sseChunks = [],
    sseStream,
  } = opts;

  return vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
    const urlStr = typeof url === "string" ? url : url.toString();

    if (urlStr.includes("/trigger")) {
      return new Response(
        JSON.stringify(triggerBody ?? { id: runId }),
        {
          status: triggerStatus,
          headers: {
            "content-type": "application/json",
            "x-trigger-jwt": publicToken,
          },
        }
      );
    }

    if (urlStr.includes("/realtime/v1/streams/")) {
      return new Response(sseStream ?? createSSEStream(sseEncode(sseChunks)), {
        status: 200,
        headers: {
          "content-type": "text/event-stream",
          "X-Stream-Version": "v1",
        },
      });
    }

    throw new Error(`Unexpected fetch URL: ${urlStr}`);
  });
}

Also applies to: 233-345, 459-492, 575-641, 643-713, 715-777, 779-841


436-457: createChatTransport tests only assert instanceof.

These tests verify that the factory returns a TriggerChatTransport but don't confirm options are forwarded (e.g., verify that sendMessages hits the custom baseURL). Since the constructor and sendMessages are thoroughly tested elsewhere this is acceptable, but a quick behavioral assertion would strengthen the factory test.


348-433: No test covers the publicAccessToken response-body path.

All mocks return { id: "..." } without a publicAccessToken field, so the transport always falls back to the caller-supplied token for stream subscriptions. Consider adding a case where the trigger response includes publicAccessToken in the body to exercise that branch in sendMessages (transport.ts, lines 133–136).

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4e08bd and c3656a5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/package.json
  • packages/ai/src/index.ts
  • packages/ai/src/transport.test.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/types.ts
  • packages/ai/src/version.ts
  • packages/ai/tsconfig.json
  • packages/ai/vitest.config.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{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}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • packages/ai/vitest.config.ts
  • packages/ai/src/version.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/transport.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

  • packages/ai/vitest.config.ts
  • packages/ai/src/version.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/transport.test.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/ai/vitest.config.ts
  • packages/ai/src/version.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/transport.test.ts
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • packages/ai/vitest.config.ts
  • packages/ai/tsconfig.json
  • packages/ai/src/version.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/transport.test.ts
  • packages/ai/package.json
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/ai/vitest.config.ts
  • packages/ai/tsconfig.json
  • packages/ai/src/version.ts
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/src/transport.test.ts
  • packages/ai/package.json
**/tsconfig.json

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use strict mode in TypeScript configuration

Files:

  • packages/ai/tsconfig.json
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use vitest for all tests in the Trigger.dev repository

Files:

  • packages/ai/src/transport.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

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • packages/ai/src/transport.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • packages/ai/src/transport.test.ts
🧠 Learnings (31)
📓 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: .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 the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties
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
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
📚 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: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest for running unit tests

Applied to files:

  • packages/ai/vitest.config.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/ai/vitest.config.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vitest exclusively for testing and never mock anything - use testcontainers instead

Applied to files:

  • packages/ai/vitest.config.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to apps/webapp/**/*.test.{ts,tsx} : For testable code in the webapp, never import env.server.ts in test files - pass configuration as options instead

Applied to files:

  • packages/ai/vitest.config.ts
📚 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/**/*.test.{ts,tsx} : Test files should only import classes and functions from `app/**/*.ts` files and should not import `env.server.ts` directly or indirectly; pass configuration through options instead

Applied to files:

  • packages/ai/vitest.config.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.config.ts : Configure Trigger.dev project in `trigger.config.ts` using `defineConfig()` with project ref and task directories

Applied to files:

  • packages/ai/vitest.config.ts
📚 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: Applies to **/*.test.{ts,tsx,js,jsx} : Test files should live beside the files under test and use descriptive `describe` and `it` blocks

Applied to files:

  • packages/ai/vitest.config.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.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/ai/vitest.config.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Place test files next to source files with naming pattern: source file (e.g., `MyService.ts`) → `MyService.test.ts`

Applied to files:

  • packages/ai/vitest.config.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 **/tsconfig.json : Use strict mode in TypeScript configuration

Applied to files:

  • packages/ai/tsconfig.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/ai/tsconfig.json
  • packages/ai/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 : Configure build process in trigger.config.ts using `build` object with external packages, extensions, and JSX settings

Applied to files:

  • packages/ai/tsconfig.json
  • packages/ai/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: The SDK at packages/trigger-sdk is an isomorphic TypeScript SDK

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/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} : Use `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/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 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:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/transport.ts
  • packages/ai/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} : Use the `task()` function from `trigger.dev/sdk/v3` to define tasks with id and run properties

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.ts
  • packages/ai/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} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.test.ts
  • packages/ai/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} : Use `tasks.trigger()` with type-only imports to trigger tasks from backend code without importing the task implementation

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.test.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/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} : Use `yourTask.trigger()` to trigger a task from inside another task with specified payload

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/src/transport.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} : Export tasks with unique IDs within the project to enable proper task discovery and execution

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
  • packages/ai/src/types.ts
  • packages/ai/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} : Use logger methods (debug, log, info, warn, error) from `trigger.dev/sdk/v3` for structured logging in tasks

Applied to files:

  • .changeset/ai-sdk-chat-transport.md
  • packages/ai/src/index.ts
📚 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/ai/src/index.ts
  • packages/ai/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} : Export every task, including subtasks

Applied to files:

  • packages/ai/src/index.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} : Generate example payloads for tasks when possible

Applied to files:

  • packages/ai/src/types.ts
  • packages/ai/src/transport.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 `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • packages/ai/src/types.ts
  • packages/ai/src/transport.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.batchTrigger()` to trigger multiple runs of a single task with different payloads

Applied to files:

  • packages/ai/src/types.ts
  • packages/ai/src/transport.test.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • packages/ai/package.json
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Every Trigger.dev task must be exported and have a unique `id` property with no timeouts in the run function

Applied to files:

  • packages/ai/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/ai/package.json
🧬 Code graph analysis (2)
packages/ai/src/version.ts (1)
packages/ai/src/index.ts (1)
  • VERSION (3-3)
packages/ai/src/transport.test.ts (1)
packages/ai/src/transport.ts (2)
  • TriggerChatTransport (67-231)
  • createChatTransport (254-256)
⏰ 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). (23)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 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 (8, 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 / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (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 / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: typecheck / typecheck
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Bun Runtime
🔇 Additional comments (12)
packages/ai/src/version.ts (1)

1-1: LGTM!

Standard version placeholder that gets patched by the update-version build script.

packages/ai/vitest.config.ts (1)

1-8: LGTM!

Standard Vitest configuration. The default export is required by the framework's config convention.

packages/ai/src/index.ts (1)

1-3: LGTM!

Clean barrel exports. ChatSessionState is correctly kept internal.

.changeset/ai-sdk-chat-transport.md (1)

1-41: LGTM!

Clear changeset with accurate usage examples matching the implementation.

packages/ai/src/types.ts (1)

1-117: LGTM!

Well-structured types with thorough JSDoc. Good use of type over interface per coding guidelines, and string union for trigger instead of an enum.

packages/ai/src/transport.ts (3)

1-11: LGTM!

Imports correctly use the @trigger.dev/core/v3 subpath (not root), and constants are well-named with sensible defaults.


81-91: LGTM!

Constructor correctly handles both string and function token types, with sensible defaults for all optional fields.


254-256: LGTM!

Clean factory function using a named function declaration per coding guidelines.

packages/ai/tsconfig.json (1)

1-10: No action required—strict mode is inherited from the base configuration.

The base config (.configs/tsconfig.base.json) has "strict": true enabled, which is inherited by this file through the extends directive. The file is compliant with the coding guideline.

packages/ai/src/transport.test.ts (3)

1-57: Good test structure and setup.

Vitest is used correctly, the test file is co-located with the source, and the beforeEach/afterEach properly saves and restores global.fetch. The helper functions (sseEncode, createSSEStream, sample data) are clean and well-factored.


96-165: Solid end-to-end happy-path coverage for sendMessages.

The test correctly verifies that triggering a task returns a ReadableStream, that SSE chunks are parsed into UIMessageChunk objects, and that the stream terminates properly. The payload-verification, custom streamKey, and custom headers tests (below) round out the coverage well.


495-572: Well-designed abort test.

The use of a deferred promise (streamResolve) to gate the SSE stream is a clean pattern for testing abort behavior without races. Good coverage of graceful close semantics.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 43 to 45
"peerDependencies": {
"ai": "^5.0.0 || ^6.0.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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the transport.ts file and see what's being imported
fd -e ts -e tsx -p | grep -E "transport\.(ts|tsx)" | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 97


🏁 Script executed:

# Search for ChatTransport imports in the codebase
rg "ChatTransport" --type ts --type tsx -n

Repository: triggerdotdev/trigger.dev

Length of output: 96


🌐 Web query:

When was ChatTransport introduced in Vercel AI SDK release history

💡 Result:

ChatTransport was introduced with Vercel AI SDK v5.0, when useChat moved to a transport-based architecture. The v5 release was announced on July 31, 2025. [1], [2]

Sources: AI SDK 5 announcement (Jul 31, 2025) [1]; useChat v5 docs showing the transport?: ChatTransport API and noting the v5 transport architecture [2].


🌐 Web query:

ChatTransport ai npm package v5 vs v6 compatibility

💡 Result:

ChatTransport is part of Vercel’s AI SDK (ai npm package). AI SDK v5 and v6 are different major versions and are not “drop-in compatible” at the type/API level—you should not mix ai@5.x with ai@6.x (or @ai-sdk/react@2.x with @ai-sdk/react@3.x) in the same app/library; keep the whole AI SDK stack on the same major. The official v5→v6 migration guide explicitly requires upgrading ai to ^6.0.0 and the @ai-sdk/* packages to their v6-compatible majors. [1]

What this means for ChatTransport implementations

  • If you wrote a custom transport against v5, it may need changes to compile/work on v6, because it must satisfy the v6 ChatTransport interface (e.g., sendMessages uses a { trigger: 'submit-message' | 'regenerate-message', chatId, messageId, messages, abortSignal, ...ChatRequestOptions } options object, and reconnectToStream returns Promise<ReadableStream<UIMessageChunk> | null>). [2]
  • The concept (transport-based useChat) exists in both v5 and v6, but treat the transport interface as versioned and pin it via peerDependencies if you publish a transport package. (Typical approach: release separate majors like your-transport@5 for ai@^5 and your-transport@6 for ai@^6.) [3][4]

Recommended path

  • App code: upgrade everything together per the v6 migration guide and run the codemod (npx @ai-sdk/codemod v6). [1]
  • Library/transport package: set peerDependencies to either ai@^5 or ai@^6 (not both unless you actively maintain compatibility), and provide separate releases if needed. [2]

Sources: v6 migration guide [1], v6 ChatTransport interface [2], v5/v6 transport docs showing the transport system around useChat [3][4].


🏁 Script executed:

# Read the transport.ts file to see the actual implementation
cat -n packages/ai/src/transport.ts

Repository: triggerdotdev/trigger.dev

Length of output: 10058


Narrow peer dependency to "ai": "^6.0.0" only.

ChatTransport exists in both AI SDK v5 and v6, but the interface is incompatible between versions. This implementation targets the v6 ChatTransport API (e.g., sendMessages signature with { trigger, chatId, messageId, messages, abortSignal } and reconnectToStream returning Promise<ReadableStream<UIMessageChunk> | null>). The current peer dependency "^5.0.0 || ^6.0.0" allows v5, which has a different interface and will cause runtime errors. Either narrow to "^6.0.0" or maintain separate major versions (e.g., @trigger.dev/ai@5 for ai@^5, @trigger.dev/ai@6 for ai@^6).

🤖 Prompt for AI Agents
In `@packages/ai/package.json` around lines 43 - 45, The peerDependency for the
"ai" package is too broad and must be narrowed to "^6.0.0" because this package
implements the v6 ChatTransport API (e.g., ChatTransport methods sendMessages
with { trigger, chatId, messageId, messages, abortSignal } and reconnectToStream
returning Promise<ReadableStream<UIMessageChunk> | null>); update the
peerDependencies entry for "ai" in package.json to "^6.0.0" (or alternatively
release a separate major package for v5 vs v6), ensuring only the v6-compatible
consumer is allowed.

Comment on lines 113 to 120
const payload = {
messages,
chatId,
trigger,
messageId,
metadata,
...(body ?? {}),
};
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

body spread can silently override core payload fields (messages, chatId, trigger, etc.).

The spread ...(body ?? {}) is placed after the core fields, so any key in body that matches a core field will override it. If this is intentional for extensibility, consider documenting this precedence. If not, swap the order so core fields win:

Proposed fix if core fields should take precedence
     const payload = {
+      ...(body ?? {}),
       messages,
       chatId,
       trigger,
       messageId,
       metadata,
-      ...(body ?? {}),
     };
📝 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
const payload = {
messages,
chatId,
trigger,
messageId,
metadata,
...(body ?? {}),
};
const payload = {
...(body ?? {}),
messages,
chatId,
trigger,
messageId,
metadata,
};
🤖 Prompt for AI Agents
In `@packages/ai/src/transport.ts` around lines 113 - 120, The payload
construction currently spreads ...(body ?? {}) after core fields so any keys in
body can silently override core properties like messages, chatId, trigger,
messageId, metadata; update the payload creation in transport.ts so the body
spread comes first (e.g., { ...(body ?? {}), messages, chatId, trigger,
messageId, metadata }) to ensure the core fields (messages, chatId, trigger,
messageId, metadata) always take precedence, or alternatively add a comment
documenting that body is allowed to override these keys if that behavior is
intentional.

Comment on lines 205 to 210
if (abortSignal?.aborted) {
reader.cancel();
reader.releaseLock();
controller.close();
return;
}
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

reader.cancel() is not awaited before reader.releaseLock() — may throw.

ReadableStreamDefaultReader.cancel() returns a Promise. Calling releaseLock() before the cancel promise settles can throw a TypeError because the reader is still locked. Await the cancel, or simply rely on controller.close() and return (the underlying subscription already received the abort signal).

Proposed fix
              if (abortSignal?.aborted) {
-                reader.cancel();
-                reader.releaseLock();
+                await reader.cancel();
                 controller.close();
                 return;
               }
🤖 Prompt for AI Agents
In `@packages/ai/src/transport.ts` around lines 205 - 210, The abort handling
currently calls reader.cancel() without awaiting it before reader.releaseLock(),
which can throw because cancel() returns a Promise; update the abort branch in
the stream handler to await the Promise returned by reader.cancel() before
calling reader.releaseLock() (or omit releaseLock() and just await
reader.cancel() then controller.close() and return) so that abortSignal,
reader.cancel(), reader.releaseLock(), and controller.close() are handled in the
correct order and the cancel promise is settled before releasing the lock.

cursoragent and others added 4 commits February 15, 2026 12:54
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>
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: 4

🤖 Fix all issues with AI agents
In `@packages/trigger-sdk/src/v3/ai.ts`:
- Around line 342-360: The chatTask wrapper can double-pipe if userRun both
calls pipeChat(result) and returns a streamable; update chatTask to guard
against double-piping by tracking whether a stream has already been piped for
the current run (e.g., add a local WeakSet/Set or attach a unique Symbol flag to
the StreamTextResult inside pipeChat and check it before calling pipeChat again
in chatTask). Specifically, modify chatTask (the run wrapper) to check
isUIMessageStreamable(result) and then verify the "already piped" marker on the
result before calling pipeChat(result), or alternatively export/define a Symbol
that pipeChat sets so chatTask can skip piping when present; also add a JSDoc
note on chatTask/pipeChat that returning a streamable and manually calling
pipeChat are mutually exclusive if you opt for the simpler documentation
approach.
- Around line 186-206: The type guard isUIMessageStreamable only detects the v6
method toUIMessageStream, causing v4/v5 StreamTextResult (which expose
toDataStream) to be rejected; update isUIMessageStreamable to accept either
method (check for toUIMessageStream OR toDataStream) and ensure calling code
that uses toUIMessageStream (e.g., pipeChat/auto-piping logic) is adjusted to
call the available method (prefer toUIMessageStream if present, otherwise call
toDataStream) so both v4/v5 and v6+ SDKs work; alternatively, if you choose to
drop v4/v5 support, update the peer dependency to ^6.0.0 and add a clear
comment, but the recommended change is to extend isUIMessageStreamable and
dispatch to the existing method names.

In `@packages/trigger-sdk/src/v3/chat.ts`:
- Around line 166-175: The payload construction currently spreads ...(body ??
{}) after the standard fields so any colliding keys in body can overwrite
messages, chatId, trigger, messageId, or metadata; to fix, change the payload
creation in the same scope where payload is defined so that body is spread first
(e.g., start with ...(body ?? {}), then list messages, chatId, trigger,
messageId, metadata) so the explicit fields in payload win, or alternatively
nest the incoming body under a dedicated key like extraBody to avoid
collisions—update the code around the payload constant and ensure references to
payload remain unchanged.
- Around line 248-253: The abort handling block must await reader.cancel()
before calling reader.releaseLock() to avoid "Invalid state" errors; update the
abort branch in the stream-reading logic (the block that currently calls
reader.cancel(), reader.releaseLock(), controller.close()) to await the Promise
returned by reader.cancel() (and optionally wrap it in try/catch to swallow or
log cancellation errors), then call reader.releaseLock(), then
controller.close(), then return—ensuring you reference the same reader and
controller variables used in this function.
🧹 Nitpick comments (3)
packages/trigger-sdk/src/v3/chat.ts (1)

143-143: Sessions are never evicted from the Map.

In a long-lived SPA, every sendMessages call with a new chatId grows the map indefinitely with no cleanup path. Consider exposing a clearSession(chatId) method or evicting on the next sendMessages for the same chatId (which already overwrites the entry) — and perhaps adding a dispose/destroy method that clears all sessions.

packages/trigger-sdk/src/v3/chat.test.ts (2)

715-777: Add a test for body-field collision with standard payload keys.

The current test only merges non-overlapping keys (systemPrompt, temperature). There should be a test verifying that if body contains a key like messages or chatId, the standard field is preserved (or document that body intentionally overrides — which would be surprising). This relates to the payload construction order in chat.ts.


100-131: Consider extracting the repeated fetch mock setup into a shared helper.

Nearly every test recreates the same dual-branch mock (trigger endpoint + SSE endpoint) with minor variations. A shared factory like createMockFetch({ triggerResponse, sseChunks }) would reduce ~15 lines of boilerplate per test and make each test's intent clearer.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3656a5 and e4c30b0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • .changeset/ai-sdk-chat-transport.md
  • packages/trigger-sdk/package.json
  • 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)
  • .changeset/ai-sdk-chat-transport.md
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{js,ts,jsx,tsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Format code using Prettier before committing

Files:

  • packages/trigger-sdk/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/trigger-sdk/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
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/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.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}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

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

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
🧠 Learnings (27)
📓 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: .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
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
📚 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/package.json
  • 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 `trigger.dev/sdk/v3` for all imports in Trigger.dev tasks

Applied to files:

  • packages/trigger-sdk/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
📚 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
📚 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/package.json
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx} : Always import tasks from `trigger.dev/sdk`, never use `trigger.dev/sdk/v3` or deprecated `client.defineJob` pattern

Applied to files:

  • packages/trigger-sdk/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • 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} : Export tasks with unique IDs within the project to enable proper task discovery and execution

Applied to files:

  • packages/trigger-sdk/package.json
  • 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/package.json
  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • packages/trigger-sdk/src/v3/chat.ts
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to {packages,integrations}/**/* : Add a changeset when modifying any public package in `packages/*` or `integrations/*` using `pnpm run changeset:add`

Applied to files:

  • packages/trigger-sdk/package.json
📚 Learning: 2026-01-15T11:50:06.067Z
Learnt from: CR
Repo: triggerdotdev/trigger.dev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-15T11:50:06.067Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Import from `trigger.dev/core` using subpaths only, never import from 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-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: 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/package.json
  • packages/trigger-sdk/src/v3/chat.test.ts
📚 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} : Use `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

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

Applied to files:

  • packages/trigger-sdk/src/v3/ai.ts
  • packages/trigger-sdk/src/v3/chat.test.ts
  • 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 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} : 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 `yourTask.trigger()` to trigger a task from inside another task with specified payload

Applied to files:

  • packages/trigger-sdk/src/v3/ai.ts
  • 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} : Generate example payloads for tasks when possible

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 `tasks.batchTrigger()` to trigger multiple runs of a single task with different payloads

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.trigger()` to trigger multiple different tasks at once from backend code

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
🧬 Code graph analysis (1)
packages/trigger-sdk/src/v3/chat.test.ts (1)
packages/trigger-sdk/src/v3/chat.ts (2)
  • TriggerChatTransport (135-272)
  • createChatTransport (292-294)
⏰ 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). (26)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (8, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (7, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (7)
packages/trigger-sdk/package.json (2)

27-28: Export structure for ./chat is consistent and well-formed.

The new ./chat subpath export follows the exact same pattern as ./ai and the other existing exports, with proper import/require/types/@triggerdotdev/source mappings for both ESM and CJS. The typesVersions entry is also aligned.

Also applies to: 96-141


84-92: Consider documenting minimum version requirement for the ./chat export.

The peer dependency range for ai (^4.2.0 || ^5.0.0 || ^6.0.0) should be verified to ensure ChatTransport and the transport option for useChat are available across all supported versions. If these features were introduced in v5 or later, either tighten the peer dependency lower bound or add documentation to the export indicating the minimum version required.

packages/trigger-sdk/src/v3/ai.ts (1)

124-276: Well-structured backend chat helpers with good JSDoc.

The pipeChat function correctly resolves streams from multiple source types (UIMessageStreamable, AsyncIterable, ReadableStream), and the type guards use duck-typing appropriately for isomorphic compatibility. The CHAT_STREAM_KEY constant and ChatTaskPayload type provide clean integration points between frontend and backend.

packages/trigger-sdk/src/v3/chat.ts (2)

135-155: Clean isomorphic implementation with proper defaults.

The class uses only browser-compatible APIs (fetch via ApiClient, ReadableStream, SSEStreamSubscription), supports both static and dynamic access tokens, and provides sensible defaults. The arrow-function method properties for sendMessages/reconnectToStream ensure correct this binding when passed to useChat. Based on learnings, the Trigger.dev SDK should prefer isomorphic code like fetch and ReadableStream instead of Node.js-specific code.


233-270: The .chunk property is properly defined in the SSEStreamPart type at packages/core/src/v3/apiClient/runStream.ts:175 as:

export type SSEStreamPart<TChunk = unknown> = {
  id: string;
  chunk: TChunk;
  timestamp: number;
};

Since SSEStreamSubscription.subscribe() returns Promise<ReadableStream<SSEStreamPart>>, the code correctly accesses value.chunk with no risk of undefined at runtime. The cast to UIMessageChunk is appropriate. No issues found.

Likely an incorrect or invalid review comment.

packages/trigger-sdk/src/v3/chat.test.ts (2)

96-165: End-to-end stream integration test is solid.

Good coverage of the full flow: triggering a task, subscribing to the SSE stream, reading all chunks, and verifying content. The mock correctly simulates both the trigger API response and the SSE event stream.


495-573: Well-structured abort test with controlled stream timing.

The use of a Promise-based gate (streamWait) to control the mock stream's lifecycle is a clean technique for testing abort behavior without flaky timing dependencies.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +186 to +206
*/
type UIMessageStreamable = {
toUIMessageStream: (...args: any[]) => AsyncIterable<unknown> | ReadableStream<unknown>;
};

function isUIMessageStreamable(value: unknown): value is UIMessageStreamable {
return (
typeof value === "object" &&
value !== null &&
"toUIMessageStream" in value &&
typeof (value as any).toUIMessageStream === "function"
);
}

function isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
}

function isReadableStream(value: unknown): value is ReadableStream<unknown> {
return typeof value === "object" && value !== null && typeof (value as any).getReader === "function";
}
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 . -name "package.json" -path "*/trigger-sdk/*" | head -5

Repository: triggerdotdev/trigger.dev

Length of output: 104


🌐 Web query:

Vercel AI SDK v6 streamText toUIMessageStream method documentation

💡 Result:

In AI SDK v6, toUIMessageStream is a method on the streamText() result (not streamText itself):

  • API reference (includes signature + options): streamText()toUIMessageStream(options?: UIMessageStreamOptions): AsyncIterableStream<UIMessageChunk>. Options include onFinish, messageMetadata, sendReasoning, sendSources, sendStart, sendFinish, onError, and consumeSseStream (for abort handling). [1]
  • Related helpers on the same result object: toUIMessageStreamResponse() and pipeUIMessageStreamToResponse() (for returning/piping an HTTP response using the UI message stream protocol). [1]
  • Usage examples:
    • Fastify cookbook shows reply.send(result.toUIMessageStream()) and writer.merge(result.toUIMessageStream({ sendStart: false })). [2]
    • “Streaming Custom Data” shows merging a streamText() UI stream into a custom createUIMessageStream via writer.merge(result.toUIMessageStream()). [3]

Sources: [1] streamText() reference, [2] Fastify cookbook, [3] Streaming Custom Data docs.


🌐 Web query:

Vercel AI SDK method names toDataStream toAIStream history versions

💡 Result:

ai (Vercel AI SDK) streaming helper method name history

AI SDK version What existed What changed / what to use instead
2.x AIStream, StreamingTextResponse, streamToResponse These 2.x exports were removed; Vercel recommends streamText(...).toDataStream() / .toDataStreamResponse() instead. [1]
3.4 streamText(...) result had AI-stream helpers: toAIStream(), pipeAIStreamToResponse(...), toAIStreamResponse() These methods still existed in 3.4 but were later removed. [1]
4.0+ streamText(...) result uses Data-stream helpers: toDataStream(), pipeDataStreamToResponse(...), toDataStreamResponse() In 4.0, the AI-stream methods (toAIStream, etc.) were removed in favor of the Data-stream methods above. [1]
4.0 (LangChain adapter) LangChainAdapter.toAIStream(...) Removed in 4.0; use LangChainAdapter.toDataStream(...). [1]
4.0+ (Express/etc.) streamToResponse(...) Reported removed “after version 4”; migration is towards piping the SDK data stream (pipeDataStreamToResponse) rather than streamToResponse. [2]

Sources: AI SDK 3.4 → 4.0 migration guide (breaking changes) [1], Vercel GitHub discussion on streamToResponse removal [2].


🏁 Script executed:

cat packages/trigger-sdk/package.json | grep -A 10 -B 2 "peerDependencies"

Repository: triggerdotdev/trigger.dev

Length of output: 377


🏁 Script executed:

cat -n packages/trigger-sdk/src/v3/ai.ts | head -230 | tail -50

Repository: triggerdotdev/trigger.dev

Length of output: 2084


🏁 Script executed:

rg "isUIMessageStreamable|toUIMessageStream|toDataStream" packages/trigger-sdk/src/v3/ai.ts

Repository: triggerdotdev/trigger.dev

Length of output: 881


Update isUIMessageStreamable to support AI SDK v4/v5 or declare v6+ requirement in peer dependency.

The code only checks for toUIMessageStream() (v6 method), but the peer dependency explicitly includes ^4.2.0 and ^5.0.0, which use toDataStream() instead. Users on v4 or v5 will experience silent failures when pipeChat() or auto-piping encounters a StreamTextResult—the type guard returns false, causing the stream to be rejected.

Either:

  1. Support both: add a fallback check for toDataStream() and abstract the method call
  2. Update peer dependency to ^6.0.0 only if v6 is the minimum supported version
  3. Document the implicit v6+ requirement in comments (least preferred)
🤖 Prompt for AI Agents
In `@packages/trigger-sdk/src/v3/ai.ts` around lines 186 - 206, The type guard
isUIMessageStreamable only detects the v6 method toUIMessageStream, causing
v4/v5 StreamTextResult (which expose toDataStream) to be rejected; update
isUIMessageStreamable to accept either method (check for toUIMessageStream OR
toDataStream) and ensure calling code that uses toUIMessageStream (e.g.,
pipeChat/auto-piping logic) is adjusted to call the available method (prefer
toUIMessageStream if present, otherwise call toDataStream) so both v4/v5 and v6+
SDKs work; alternatively, if you choose to drop v4/v5 support, update the peer
dependency to ^6.0.0 and add a clear comment, but the recommended change is to
extend isUIMessageStreamable and dispatch to the existing method names.

Comment on lines +342 to +360
export function chatTask<TIdentifier extends string>(
options: ChatTaskOptions<TIdentifier>
): Task<TIdentifier, ChatTaskPayload, unknown> {
const { run: userRun, ...restOptions } = options;

return createTask<TIdentifier, ChatTaskPayload, unknown>({
...restOptions,
run: async (payload: ChatTaskPayload) => {
const result = await userRun(payload);

// Auto-pipe if the run function returned a StreamTextResult or similar
if (isUIMessageStreamable(result)) {
await pipeChat(result);
}

return result;
},
});
}
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

Potential double-pipe if user calls pipeChat() inside run and also returns a streamable.

If a user both calls pipeChat(result) manually inside their run function and returns the StreamTextResult, the auto-pipe logic on line 353 would attempt to pipe a second time to the same stream key. Consider either guarding against this (e.g., tracking if pipeChat was already called for the current run) or adding a clear warning in the JSDoc that returning a streamable result and calling pipeChat() manually are mutually exclusive.

🤖 Prompt for AI Agents
In `@packages/trigger-sdk/src/v3/ai.ts` around lines 342 - 360, The chatTask
wrapper can double-pipe if userRun both calls pipeChat(result) and returns a
streamable; update chatTask to guard against double-piping by tracking whether a
stream has already been piped for the current run (e.g., add a local WeakSet/Set
or attach a unique Symbol flag to the StreamTextResult inside pipeChat and check
it before calling pipeChat again in chatTask). Specifically, modify chatTask
(the run wrapper) to check isUIMessageStreamable(result) and then verify the
"already piped" marker on the result before calling pipeChat(result), or
alternatively export/define a Symbol that pipeChat sets so chatTask can skip
piping when present; also add a JSDoc note on chatTask/pipeChat that returning a
streamable and manually calling pipeChat are mutually exclusive if you opt for
the simpler documentation approach.

Comment on lines +166 to +175
const { trigger, chatId, messageId, messages, abortSignal, body, metadata } = options;

const payload = {
messages,
chatId,
trigger,
messageId,
metadata,
...(body ?? {}),
};
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

body spread can silently override standard payload fields.

The spread ...(body ?? {}) comes after the standard fields (messages, chatId, trigger, messageId, metadata), so any key in body that collides will overwrite them. If a consumer passes body: { messages: [...] } or body: { chatId: "x" }, the real values are silently lost.

Spread body first, or nest it under a dedicated key:

🛡️ Proposed fix — spread body first so standard fields win
     const payload = {
+      ...(body ?? {}),
       messages,
       chatId,
       trigger,
       messageId,
       metadata,
-      ...(body ?? {}),
     };
📝 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
const { trigger, chatId, messageId, messages, abortSignal, body, metadata } = options;
const payload = {
messages,
chatId,
trigger,
messageId,
metadata,
...(body ?? {}),
};
const payload = {
...(body ?? {}),
messages,
chatId,
trigger,
messageId,
metadata,
};
🤖 Prompt for AI Agents
In `@packages/trigger-sdk/src/v3/chat.ts` around lines 166 - 175, The payload
construction currently spreads ...(body ?? {}) after the standard fields so any
colliding keys in body can overwrite messages, chatId, trigger, messageId, or
metadata; to fix, change the payload creation in the same scope where payload is
defined so that body is spread first (e.g., start with ...(body ?? {}), then
list messages, chatId, trigger, messageId, metadata) so the explicit fields in
payload win, or alternatively nest the incoming body under a dedicated key like
extraBody to avoid collisions—update the code around the payload constant and
ensure references to payload remain unchanged.

Comment on lines +248 to +253
if (abortSignal?.aborted) {
reader.cancel();
reader.releaseLock();
controller.close();
return;
}
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

reader.cancel() is not awaited before releaseLock().

ReadableStreamDefaultReader.cancel() returns a Promise. Calling releaseLock() immediately after without awaiting may cause an "Invalid state" error in some environments since the reader is still settling.

Proposed fix
              if (abortSignal?.aborted) {
-               reader.cancel();
-               reader.releaseLock();
+               await reader.cancel();
                controller.close();
                return;
              }
📝 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
if (abortSignal?.aborted) {
reader.cancel();
reader.releaseLock();
controller.close();
return;
}
if (abortSignal?.aborted) {
await reader.cancel();
controller.close();
return;
}
🤖 Prompt for AI Agents
In `@packages/trigger-sdk/src/v3/chat.ts` around lines 248 - 253, The abort
handling block must await reader.cancel() before calling reader.releaseLock() to
avoid "Invalid state" errors; update the abort branch in the stream-reading
logic (the block that currently calls reader.cancel(), reader.releaseLock(),
controller.close()) to await the Promise returned by reader.cancel() (and
optionally wrap it in try/catch to swallow or log cancellation errors), then
call reader.releaseLock(), then controller.close(), then return—ensuring you
reference the same reader and controller variables used in this function.

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

🤖 Fix all issues with AI agents
In `@packages/trigger-sdk/src/v3/chat.test.ts`:
- Around line 461-481: The test's description and inline comments are
inconsistent with the mock: the describe("publicAccessToken from trigger
response") / it("should use publicAccessToken from response body when
x-trigger-jwt header is absent") text contradicts the fetchSpy mock which
includes an "x-trigger-jwt" header; update the test to either (A) rename the
describe/it strings and adjust comments to state that the test verifies using
the x-trigger-jwt header (e.g., "should use x-trigger-jwt from trigger response
as stream auth token") to match the fetchSpy behavior, or (B) add a new test
that truly covers the absent-header case by creating a fetchSpy variant for the
"/trigger" branch that omits the "x-trigger-jwt" header and returns a response
body containing the publicAccessToken (so ApiClient will derive the token from
the body); ensure comments reference fetchSpy and the intended behavior
accordingly.
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e4c30b0 and eb2ccc0.

📒 Files selected for processing (2)
  • 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.ts
🧰 Additional context used
📓 Path-based instructions (9)
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.test.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}: Always import tasks from @trigger.dev/sdk, never use @trigger.dev/sdk/v3 or deprecated client.defineJob pattern
Every Trigger.dev task must be exported and have a unique id property with no timeouts in the run function

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use function declarations instead of default exports

Import from @trigger.dev/core using subpaths only, never import from root

Files:

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

**/*.test.{ts,tsx,js,jsx}: Use vitest exclusively for testing and never mock anything - use testcontainers instead
Place test files next to source files with naming pattern: source file (e.g., MyService.ts) → MyService.test.ts

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use testcontainers helpers (redisTest, postgresTest, containerTest) from @internal/testcontainers for Redis/PostgreSQL testing instead of mocks

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
{packages,integrations}/**/*

📄 CodeRabbit inference engine (CLAUDE.md)

Add a changeset when modifying any public package in packages/* or integrations/* using pnpm run changeset:add

Files:

  • packages/trigger-sdk/src/v3/chat.test.ts
🧠 Learnings (14)
📓 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: .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: .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
📚 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.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} : Use `schemaTask()` from `trigger.dev/sdk/v3` with Zod schema for payload validation

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 `.withStreams()` to subscribe to realtime streams from task metadata in addition to run changes

Applied to files:

  • 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 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.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} : Generate example payloads for tasks when possible

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 `tasks.batchTrigger()` to trigger multiple runs of a single task with different payloads

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 `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/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 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.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.trigger()` to trigger multiple different tasks at once from backend code

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.trigger()` to trigger a task from inside another task with specified payload

Applied to files:

  • packages/trigger-sdk/src/v3/chat.test.ts
⏰ 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 (7, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (1, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (2, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (3, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (5, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (6, 8)
  • GitHub Check: units / internal / 🧪 Unit Tests: Internal (4, 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 / webapp / 🧪 Unit Tests: Webapp (3, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (6, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (2, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (4, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (8, 8)
  • GitHub Check: units / webapp / 🧪 Unit Tests: Webapp (1, 8)
  • GitHub Check: units / packages / 🧪 Unit Tests: Packages (1, 1)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - npm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (ubuntu-latest - pnpm)
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - npm)
  • GitHub Check: sdk-compat / Deno Runtime
  • GitHub Check: sdk-compat / Node.js 20.20 (ubuntu-latest)
  • GitHub Check: sdk-compat / Bun Runtime
  • GitHub Check: e2e / 🧪 CLI v3 tests (windows-latest - pnpm)
  • GitHub Check: sdk-compat / Cloudflare Workers
  • GitHub Check: sdk-compat / Node.js 22.12 (ubuntu-latest)
  • GitHub Check: typecheck / typecheck
🔇 Additional comments (9)
packages/trigger-sdk/src/v3/chat.test.ts (9)

1-47: LGTM — clean, isomorphic test helpers.

Good use of vitest, ReadableStream/TextEncoder (isomorphic), and function declarations. The incrementing counter approach for unique message IDs is fine since no test depends on specific values.


49-59: LGTM — proper fetch mock lifecycle management.

Saving/restoring global.fetch alongside vi.restoreAllMocks() ensures test isolation. While the coding guidelines prefer avoiding mocks, fetch mocking is the standard approach for HTTP transport tests where no testcontainer alternative exists.


61-95: LGTM — constructor smoke tests are concise and sufficient.


97-167: LGTM — thorough end-to-end stream consumption test.

Validates both the trigger API call and the full SSE→UIMessageChunk pipeline, asserting individual chunk contents.


169-233: LGTM — good payload verification test.

Correctly validates the double-JSON structure (triggerBody.payload is a JSON string) and asserts all expected fields including messages, chatId, trigger, and metadata.


573-651: LGTM — well-designed abort flow test.

The promise-based flow control (streamWait/streamResolve) to simulate a slow stream and then verify abort behavior is clean. Correctly validates that the reader sees done: true after abort.


653-719: LGTM — good session isolation coverage.

Verifying independent reconnectToStream for two sessions plus null for an unknown session covers the key cases.


793-855: LGTM — body merging test validates both custom and standard fields.

Good coverage ensuring user-supplied body properties are merged without clobbering chatId/trigger.


857-919: LGTM — regenerate-message trigger test.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +461 to +481
describe("publicAccessToken from trigger response", () => {
it("should use publicAccessToken from response body when x-trigger-jwt header is absent", async () => {
const fetchSpy = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
const urlStr = typeof url === "string" ? url : url.toString();

if (urlStr.includes("/trigger")) {
// Return without x-trigger-jwt header — the ApiClient will attempt
// to generate a JWT from the access token. In this test the token
// generation will add a publicAccessToken to the result.
return new Response(
JSON.stringify({ id: "run_pat" }),
{
status: 200,
headers: {
"content-type": "application/json",
// Include x-trigger-jwt to simulate the server returning a public token
"x-trigger-jwt": "server-generated-public-token",
},
}
);
}
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

Misleading test description and stale comments.

The describe block says "publicAccessToken from trigger response" and the it block says "when x-trigger-jwt header is absent", but the mock at Line 477 does include the x-trigger-jwt header. The comment on Lines 467-469 ("Return without x-trigger-jwt header") also contradicts the actual mock. This appears to be leftover from an earlier iteration; the second comment on Line 476 ("Include x-trigger-jwt to simulate...") reflects the real behavior.

Either rename the test to match what it actually verifies (e.g., "should use x-trigger-jwt from trigger response as stream auth token") or add a separate test that truly covers the absent-header case.

Proposed fix for the test description and comments
-  describe("publicAccessToken from trigger response", () => {
-    it("should use publicAccessToken from response body when x-trigger-jwt header is absent", async () => {
+  describe("publicAccessToken from trigger response", () => {
+    it("should use x-trigger-jwt from trigger response as the stream auth token", async () => {
       const fetchSpy = vi.fn().mockImplementation(async (url: string | URL, init?: RequestInit) => {
         const urlStr = typeof url === "string" ? url : url.toString();
 
         if (urlStr.includes("/trigger")) {
-          // Return without x-trigger-jwt header — the ApiClient will attempt
-          // to generate a JWT from the access token. In this test the token
-          // generation will add a publicAccessToken to the result.
+          // Return with x-trigger-jwt header — this public token should be
+          // used for the subsequent stream subscription request.
           return new Response(
             JSON.stringify({ id: "run_pat" }),
             {
               status: 200,
               headers: {
                 "content-type": "application/json",
-                // Include x-trigger-jwt to simulate the server returning a public token
                 "x-trigger-jwt": "server-generated-public-token",
               },
             }
           );
         }
🤖 Prompt for AI Agents
In `@packages/trigger-sdk/src/v3/chat.test.ts` around lines 461 - 481, The test's
description and inline comments are inconsistent with the mock: the
describe("publicAccessToken from trigger response") / it("should use
publicAccessToken from response body when x-trigger-jwt header is absent") text
contradicts the fetchSpy mock which includes an "x-trigger-jwt" header; update
the test to either (A) rename the describe/it strings and adjust comments to
state that the test verifies using the x-trigger-jwt header (e.g., "should use
x-trigger-jwt from trigger response as stream auth token") to match the fetchSpy
behavior, or (B) add a new test that truly covers the absent-header case by
creating a fetchSpy variant for the "/trigger" branch that omits the
"x-trigger-jwt" header and returns a response body containing the
publicAccessToken (so ApiClient will derive the token from the body); ensure
comments reference fetchSpy and the intended behavior accordingly.

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>
@ericallam ericallam changed the title AI SDK chat transport feat(sdk): AI SDK chat transport — run useChat as durable Trigger.dev tasks Feb 15, 2026
@ericallam ericallam changed the title feat(sdk): AI SDK chat transport — run useChat as durable Trigger.dev tasks feat(sdk): AI SDK chat transport — run useChat as tasks Feb 15, 2026
cursoragent and others added 2 commits February 15, 2026 13:24
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>
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