From 88096fc3fd02293df630aa25fe8f0e7e509ea277 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Fri, 1 May 2026 10:33:28 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20consolidate=20chat.agent=20/=20Session?= =?UTF-8?q?s=20/=20Skills=20changesets=20(26=20=E2=86=92=208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Roll up all the chat.agent feature work that's been accumulating on this branch into 8 user-facing CHANGELOG entries. No behavior change — just tidying up the .changeset/ directory before merge. Final shape: - chat-agent.md (sdk minor + core patch) — the headline; folds 13: ai-sdk-chat-transport, ai-chat-sandbox-and-ctx, chat-agent-*, chat-customagent-session-binding-and-stop-fixes, chat-reconnect-isstreaming-optional, chat-run-pat-renewal, chat-store-primitive, chat-transport-session-renew-plus-preload, drop-legacy-chat-stream-constants, dry-sloths-divide, trigger-chat-transport-watch-mode. - sessions-primitive.md (core + sdk patch) — folds 3: session-primitive, session-sdk-toolkit, session-trigger-config-extra-fields. - agent-skills.md (sdk + core + build + cli patch) — folds 2: chat-agent-skills-phase-1, skills-runtime-subpath. - ai-tool-helpers.md (sdk patch) — folds 2: ai-tool-execute-helper, ai-tool-toolset-typing. - mock-chat-agent-test-harness.md (sdk + core patch) — folds 3: mock-chat-agent-test-harness, mock-task-context-test-infra, mock-chat-agent-setup-locals. - mcp-agent-chat-sessions.md (cli patch) — kept standalone. - add-is-replay-context.md (core patch) — kept standalone (general task feature). - truncate-error-stacks.md (core patch) — kept standalone (general infra). Bumps preserved (chat-agent stays minor on sdk; everything else patch). Auto-named "dry-sloths-divide" got merged into chat-agent and dropped. --- .changeset/agent-skills.md | 18 +++++ .changeset/ai-chat-sandbox-and-ctx.md | 6 -- .changeset/ai-sdk-chat-transport.md | 42 ------------ .changeset/ai-tool-execute-helper.md | 5 -- .changeset/ai-tool-helpers.md | 8 +++ .changeset/ai-tool-toolset-typing.md | 6 -- .changeset/chat-agent-action-trigger-type.md | 5 -- .changeset/chat-agent-end-run.md | 5 -- .changeset/chat-agent-finish-reason.md | 5 -- .changeset/chat-agent-skills-phase-1.md | 16 ----- .changeset/chat-agent.md | 66 +++++++++++++++++++ ...tomagent-session-binding-and-stop-fixes.md | 9 --- .../chat-reconnect-isstreaming-optional.md | 5 -- .changeset/chat-run-pat-renewal.md | 6 -- .changeset/chat-store-primitive.md | 21 ------ ...at-transport-session-renew-plus-preload.md | 8 --- .../drop-legacy-chat-stream-constants.md | 11 ---- .changeset/dry-sloths-divide.md | 5 -- .changeset/mock-chat-agent-setup-locals.md | 6 -- .changeset/mock-chat-agent-test-harness.md | 14 +++- .changeset/mock-task-context-test-infra.md | 5 -- .changeset/session-sdk-toolkit.md | 11 ---- .../session-trigger-config-extra-fields.md | 5 -- .changeset/sessions-primitive.md | 22 +++++++ .changeset/skills-runtime-subpath.md | 5 -- .../trigger-chat-transport-watch-mode.md | 23 ------- 26 files changed, 126 insertions(+), 212 deletions(-) create mode 100644 .changeset/agent-skills.md delete mode 100644 .changeset/ai-chat-sandbox-and-ctx.md delete mode 100644 .changeset/ai-sdk-chat-transport.md delete mode 100644 .changeset/ai-tool-execute-helper.md create mode 100644 .changeset/ai-tool-helpers.md delete mode 100644 .changeset/ai-tool-toolset-typing.md delete mode 100644 .changeset/chat-agent-action-trigger-type.md delete mode 100644 .changeset/chat-agent-end-run.md delete mode 100644 .changeset/chat-agent-finish-reason.md delete mode 100644 .changeset/chat-agent-skills-phase-1.md create mode 100644 .changeset/chat-agent.md delete mode 100644 .changeset/chat-customagent-session-binding-and-stop-fixes.md delete mode 100644 .changeset/chat-reconnect-isstreaming-optional.md delete mode 100644 .changeset/chat-run-pat-renewal.md delete mode 100644 .changeset/chat-store-primitive.md delete mode 100644 .changeset/chat-transport-session-renew-plus-preload.md delete mode 100644 .changeset/drop-legacy-chat-stream-constants.md delete mode 100644 .changeset/dry-sloths-divide.md delete mode 100644 .changeset/mock-chat-agent-setup-locals.md delete mode 100644 .changeset/mock-task-context-test-infra.md delete mode 100644 .changeset/session-sdk-toolkit.md delete mode 100644 .changeset/session-trigger-config-extra-fields.md create mode 100644 .changeset/sessions-primitive.md delete mode 100644 .changeset/skills-runtime-subpath.md delete mode 100644 .changeset/trigger-chat-transport-watch-mode.md diff --git a/.changeset/agent-skills.md b/.changeset/agent-skills.md new file mode 100644 index 000000000..287a32b9f --- /dev/null +++ b/.changeset/agent-skills.md @@ -0,0 +1,18 @@ +--- +"@trigger.dev/sdk": patch +"@trigger.dev/core": patch +"@trigger.dev/build": patch +"trigger.dev": patch +--- + +Agent Skills — developer-authored folders (`SKILL.md` + scripts/references/assets) bundled into the deploy image automatically and discovered by the chat agent via progressive disclosure. Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. + +**SDK:** + +- `skills.define({ id, path })` registers a skill with the resource catalog; the Trigger.dev CLI bundles the folder into `/app/.trigger/skills/{id}/` automatically — no `trigger.config.ts` changes, no build extension. +- `SkillHandle.local()` reads the bundled `SKILL.md` at runtime, parses frontmatter, returns a `ResolvedSkill`. +- `chat.skills.set([...])` stores resolved skills for the current run. +- `chat.toStreamTextOptions()` auto-injects the skills preamble into the system prompt and merges three tools — `loadSkill`, `readFile`, `bash` — scoped per-skill with path-traversal guards and output caps (64 KB stdout/stderr, 1 MB `readFile`). `bash` runs with `cwd` = skill directory; the turn's abort signal propagates. +- `@trigger.dev/sdk/ai/skills-runtime` subpath — the `bash` + `readFile` runtime primitives (backed by `node:child_process` + `node:fs/promises`) live here, not in `@trigger.dev/sdk/ai`. Fixes client-bundle build errors (`UnhandledSchemeError: Reading from "node:child_process"…`) that hit Next.js + Webpack when a browser page imports types from `@trigger.dev/sdk/ai` (for example `ChatUiMessage` via a shared tools file). The chat-agent factory loads the runtime lazily via a computed-string dynamic import, so server workers still get full skill support without any caller changes. + +This is the SDK + CLI layer only — no backend, no dashboard overrides yet. Dashboard-editable `SKILL.md` text and override flow are on the roadmap; `skill.resolve()` currently throws. diff --git a/.changeset/ai-chat-sandbox-and-ctx.md b/.changeset/ai-chat-sandbox-and-ctx.md deleted file mode 100644 index 7f4533926..000000000 --- a/.changeset/ai-chat-sandbox-and-ctx.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `TaskRunContext` (`ctx`) to all `chat.task` lifecycle events, `CompactedEvent`, and `ChatTaskRunPayload`. Export `TaskRunContext` from `@trigger.dev/sdk`. - diff --git a/.changeset/ai-sdk-chat-transport.md b/.changeset/ai-sdk-chat-transport.md deleted file mode 100644 index f5cdb9187..000000000 --- a/.changeset/ai-sdk-chat-transport.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -"@trigger.dev/sdk": minor ---- - -Add AI SDK chat transport integration via two new subpath exports: - -**`@trigger.dev/sdk/chat`** (frontend, browser-safe): -- `TriggerChatTransport` — custom `ChatTransport` for the AI SDK's `useChat` hook that runs chat completions as durable Trigger.dev tasks -- `createChatTransport()` — factory function - -```tsx -import { useChat } from "@ai-sdk/react"; -import { TriggerChatTransport } from "@trigger.dev/sdk/chat"; - -const { messages, sendMessage } = useChat({ - transport: new TriggerChatTransport({ - task: "my-chat-task", - accessToken, - }), -}); -``` - -**`@trigger.dev/sdk/ai`** (backend, extends existing `ai.tool`/`ai.currentToolOptions`): -- `chatTask()` — pre-typed task wrapper with auto-pipe support -- `pipeChat()` — pipe a `StreamTextResult` or stream to the frontend -- `CHAT_STREAM_KEY` — the default stream key constant -- `ChatTaskPayload` type - -```ts -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), - }); - }, -}); -``` diff --git a/.changeset/ai-tool-execute-helper.md b/.changeset/ai-tool-execute-helper.md deleted file mode 100644 index 6f7b89145..000000000 --- a/.changeset/ai-tool-execute-helper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `ai.toolExecute(task)` so you can pass Trigger's subtask/metadata wiring as the `execute` handler to AI SDK `tool()` while defining `description` and `inputSchema` yourself. Refactors `ai.tool()` to share the same internal handler. diff --git a/.changeset/ai-tool-helpers.md b/.changeset/ai-tool-helpers.md new file mode 100644 index 000000000..ba77b0664 --- /dev/null +++ b/.changeset/ai-tool-helpers.md @@ -0,0 +1,8 @@ +--- +"@trigger.dev/sdk": patch +--- + +AI SDK `tool()` helpers for Trigger subtasks: + +- `ai.toolExecute(task)` — pass Trigger's subtask/metadata wiring as the `execute` handler to AI SDK `tool()` while you define `description` and `inputSchema` yourself. `ai.tool()` is now refactored to share the same internal handler. +- `ai.tool(task)` (`toolFromTask`) aligns with AI SDK `ToolSet`: Zod-backed tasks use static `tool()`; returns are asserted as `Tool & ToolSet[string]`. Minimum `ai` devDependency raised to `^6.0.116` so emitted types resolve the same `ToolSet` as apps on AI SDK 6.0.x — avoids cross-version `ToolSet` mismatches in monorepos. diff --git a/.changeset/ai-tool-toolset-typing.md b/.changeset/ai-tool-toolset-typing.md deleted file mode 100644 index de67be637..000000000 --- a/.changeset/ai-tool-toolset-typing.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Align `ai.tool()` (`toolFromTask`) with the AI SDK `ToolSet` shape: Zod-backed tasks use static `tool()`; returns are asserted as `Tool & ToolSet[string]`. Raise the SDK's minimum `ai` devDependency to `^6.0.116` so emitted types resolve the same `ToolSet` as apps on AI SDK 6.0.x (avoids cross-version `ToolSet` mismatches in monorepos). - diff --git a/.changeset/chat-agent-action-trigger-type.md b/.changeset/chat-agent-action-trigger-type.md deleted file mode 100644 index 050951c02..000000000 --- a/.changeset/chat-agent-action-trigger-type.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Include `"action"` in the `ChatTaskPayload.trigger` union. `run()` is invoked with `trigger: "action"` after `onAction` processes a typed action, but the type previously omitted it. Users can now cleanly short-circuit the LLM call for actions that don't need a response (e.g. user-initiated compaction): `if (trigger === "action") return;`. diff --git a/.changeset/chat-agent-end-run.md b/.changeset/chat-agent-end-run.md deleted file mode 100644 index e16a45cba..000000000 --- a/.changeset/chat-agent-end-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `chat.endRun()` — exits the run after the current turn completes, without the upgrade-required signal that `chat.requestUpgrade()` sends. Use when an agent finishes its work on its own terms (one-shot responses, goal achieved, budget exhausted) instead of waiting idle for the next user message. Call from `run()`, `chat.defer()`, `onBeforeTurnComplete`, or `onTurnComplete`. diff --git a/.changeset/chat-agent-finish-reason.md b/.changeset/chat-agent-finish-reason.md deleted file mode 100644 index bf6a3b238..000000000 --- a/.changeset/chat-agent-finish-reason.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Expose `finishReason` on `TurnCompleteEvent` and `BeforeTurnCompleteEvent`. Surfaces the AI SDK's `FinishReason` (`"stop" | "tool-calls" | "length" | ...`) so hooks can distinguish a normal turn end from one paused on a pending tool call (HITL flows like `ask_user`). Undefined for manual `pipeChat()` or aborted streams. diff --git a/.changeset/chat-agent-skills-phase-1.md b/.changeset/chat-agent-skills-phase-1.md deleted file mode 100644 index 4af73f20e..000000000 --- a/.changeset/chat-agent-skills-phase-1.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch -"@trigger.dev/build": patch -"trigger.dev": patch ---- - -Add agent skills — developer-authored folders (`SKILL.md` + scripts/references/assets) bundled into the deploy image automatically, discovered by the chat agent via progressive disclosure. Built on the [AI SDK cookbook pattern](https://ai-sdk.dev/cookbook/guides/agent-skills) — portable across providers. - -**New:** -- `skills.define({ id, path })` registers a skill with the resource catalog; the Trigger.dev CLI bundles the folder into `/app/.trigger/skills/{id}/` automatically — no `trigger.config.ts` changes, no build extension. -- `SkillHandle.local()` reads the bundled `SKILL.md` at runtime, parses frontmatter, returns a `ResolvedSkill`. -- `chat.skills.set([...])` stores resolved skills for the current run. -- `chat.toStreamTextOptions()` auto-injects the skills preamble into the system prompt and merges three tools — `loadSkill`, `readFile`, `bash` — scoped per-skill with path-traversal guards and output caps (64 KB stdout/stderr, 1 MB `readFile`). `bash` runs with `cwd` = skill directory; the turn's abort signal propagates. - -Phase 1 is SDK + CLI only — no backend, no dashboard overrides. Dashboard-editable `SKILL.md` text lands in Phase 2 (`skill.resolve()` currently throws). diff --git a/.changeset/chat-agent.md b/.changeset/chat-agent.md new file mode 100644 index 000000000..6b447d9b1 --- /dev/null +++ b/.changeset/chat-agent.md @@ -0,0 +1,66 @@ +--- +"@trigger.dev/sdk": minor +"@trigger.dev/core": patch +--- + +`chat.agent` — durable AI chat as Trigger.dev tasks, with frontend wiring for the AI SDK's `useChat` hook. Built on top of the new Sessions primitive (separate changeset). + +## SDK + +**`@trigger.dev/sdk/ai`** (backend): + +- `chat.agent({ id, run, ... })` — durable agent with full lifecycle hooks (`onPreload`, `onChatStart`, `onTurnStart`, `onBeforeTurnComplete`, `onTurnComplete`, `onCompacted`, `onValidateMessages`, `onAction`, `hydrateMessages`, `hydrateStore`). Auto-pipes a returned `streamText` result to the frontend. +- `chat.customAgent({ id, run })` — minimal protocol-level escape hatch; same session binding as `chat.agent`. +- `chat.withUIMessage().agent({...})` — generic-typed agent for custom `UIMessage` subtypes (typed `data-*` parts, tool maps, etc.). Ships `InferChatUIMessage`, generic `ChatUIMessageStreamOptions`, generic compaction + pending-message event types. `usePendingMessages` accepts a UI-message type parameter; `InferChatUIMessage` re-exported from `@trigger.dev/sdk/chat/react`. +- `chat.pipe(stream)` — pipe a `StreamTextResult` or stream from anywhere inside the agent. +- `chat.endRun()` — exit the run after the current turn completes, without the upgrade-required signal that `chat.requestUpgrade()` sends. Use for one-shot responses, agent-finished-its-work, or budget-exhausted exits. +- `chat.store` — typed, bidirectional shared data slot. `set` / `patch` (RFC 6902) / `get` / `onChange`; per-run scoped. Emits `store-snapshot` / `store-delta` chunks on the chat output stream. `hydrateStore` config for restore-on-continuation; `incomingStore` wire field for client-set data at turn start. +- `chat.sessionId` — getter for the friendlyId (`session_*`) of the run's backing Session. Throws outside chat.agent / chat.customAgent. +- `TaskRunContext` (`ctx`) on every lifecycle event, `CompactedEvent`, and `ChatTaskRunPayload`. `TaskRunContext` re-exported from `@trigger.dev/sdk`. +- `finishReason` on `TurnCompleteEvent` and `BeforeTurnCompleteEvent` — surfaces AI SDK's `FinishReason` (`"stop" | "tool-calls" | "length" | ...`) so hooks can distinguish a normal end from a paused-on-tool-call HITL flow. Undefined for manual `pipeChat()` or aborted streams. +- `ChatTaskPayload.trigger` includes `"action"`. Actions short-circuit the LLM call cleanly: `if (trigger === "action") return;`. + +```ts +import { chat } from "@trigger.dev/sdk/ai"; +import { streamText } from "ai"; +import { openai } from "@ai-sdk/openai"; + +export const myChat = chat.agent({ + id: "my-chat", + run: async ({ messages, signal }) => + streamText({ model: openai("gpt-4o"), messages, abortSignal: signal }), +}); +``` + +**`@trigger.dev/sdk/chat`** (frontend / browser): + +- `TriggerChatTransport` — `ChatTransport` for `useChat`. Backed by Sessions: posts to `session.in/append`, subscribes to `session.out` SSE. +- `watch: true` option — read-only observation of an existing run. Keeps the internal stream open across `trigger:turn-complete` markers so a single `useChat` / `resumeStream` subscription observes every turn of a long-lived agent. Useful for dashboard viewers / debug UIs. Default `false` preserves interactive behavior. +- `RenewRunAccessTokenParams` includes the durable `sessionId` alongside `chatId` + `runId`. Renew handlers should mint with `read:sessions:{sessionId}` + `write:sessions:{sessionId}` scopes; renewing without session scopes throws the transport into a 401 loop on the first append after expiry. +- Run-scoped PAT renewal (`renewRunAccessToken`); fail fast on 401/403 for SSE without retry backoff. `isTriggerRealtimeAuthError` exported for auth-error detection. +- `transport.preload(chatId)` no longer calls `apiClient.createSession` from the browser — the server action returns `sessionId` in its result, matching how `sendMessages` already worked. Browser deployments using the `triggerTask` callback path therefore no longer need `write:sessions` on any browser-side token. +- `reconnectToStream` no longer requires callers to persist an `isStreaming` flag in `ChatSession` state — the short-circuit only triggers on explicit `isStreaming === false`. + +**`@trigger.dev/sdk/chat/react`**: + +- `useTriggerChatTransport({ task, accessToken, ... })` — memoized hook wrapping `TriggerChatTransport` for `useChat`. + +## chat.agent fixes folded in + +- `chat.customAgent` now binds the session handle (previously only `chat.agent` set up the per-run `SessionHandle`, so any custom agent that called `chat.messages.*`, `chat.stream.*`, `chat.createSession`, or `chat.createStopSignal` threw `chat.agent session handle is not initialized`). `chat.customAgent` now wraps the user's `run` and opens the session via `payload.sessionId ?? payload.chatId` before invoking it. +- Stop mid-stream no longer hangs the turn loop. The AI SDK's `runResult.totalUsage` promise can stay unresolved indefinitely on aborted Anthropic streams; the await is now raced against a 2s timeout so a stuck `totalUsage` falls through to a non-fatal "usage unknown" path and the turn finalizes correctly. + +## Cleanup + +The pre-Sessions chat stream-ID constants are gone: + +- `CHAT_STREAM_KEY`, `CHAT_MESSAGES_STREAM_ID`, `CHAT_STOP_STREAM_ID` are no longer exported from `@trigger.dev/sdk/ai` or `@trigger.dev/core/v3/chat-client`. +- `packages/trigger-sdk/src/v3/chat-constants.ts` deleted. +- The labels still contain the same string values — they're now opaque breadcrumbs rather than user-consumable constants. Behavior and telemetry attrs unchanged. + +These constants only mattered before chat.agent moved onto the Session primitive. Customers who referenced them externally should migrate to `sessions.open(sessionId).out.writer(...)` / `sessions.open(sessionId).in.on(...)` — same primitives, now session-keyed. + +## Core + +- New `chat.store` chunk types and `applyChatStorePatch` helper exported from `@trigger.dev/core/v3/chat-client`. +- `RenewRunAccessTokenParams` payload extended with `sessionId`. diff --git a/.changeset/chat-customagent-session-binding-and-stop-fixes.md b/.changeset/chat-customagent-session-binding-and-stop-fixes.md deleted file mode 100644 index 2bfff2087..000000000 --- a/.changeset/chat-customagent-session-binding-and-stop-fixes.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Three chat.agent fixes surfaced by smoke-testing the Sessions migration: - -- **`chat.customAgent` now binds the session handle.** Previously only `chat.agent` set up the per-run `SessionHandle` in run-locals, so any custom agent that called `chat.messages.*`, `chat.stream.*`, `chat.createSession`, or `chat.createStopSignal` threw `chat.agent session handle is not initialized`. `chat.customAgent` now wraps the user's `run` function and opens the session via `payload.sessionId ?? payload.chatId` before invoking it, matching `chat.agent`'s behavior. -- **Stop mid-stream no longer hangs the turn loop.** When the user aborts a turn, the AI SDK's `runResult.totalUsage` promise can stay unresolved indefinitely on Anthropic streams, blocking `onTurnComplete` / `writeTurnComplete` / the next-message wait. The await is now raced against a 2s timeout (mirroring the existing `onFinishPromise` race), so a stuck `totalUsage` falls through to a non-fatal "usage unknown" path and the turn finalizes correctly. -- **New `chat.sessionId` getter.** Returns the friendlyId (`session_*`) of the run's backing Session. Useful in `onPreload` / `onChatStart` / `onTurnComplete` for persisting the session id alongside `runId` so reloads can resume the same conversation. Throws if called outside a chat.agent / chat.customAgent run. diff --git a/.changeset/chat-reconnect-isstreaming-optional.md b/.changeset/chat-reconnect-isstreaming-optional.md deleted file mode 100644 index 302957c61..000000000 --- a/.changeset/chat-reconnect-isstreaming-optional.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`TriggerChatTransport.reconnectToStream` no longer requires callers to persist an `isStreaming` flag in `ChatSession` state. Previously, any falsy `isStreaming` (including `undefined` when the flag was dropped from persistence) short-circuited reconnect to `null` and left the UI hanging on incomplete streams. Now the short-circuit only triggers on an explicit `isStreaming === false`, so callers can drop the flag entirely and let the server decide via the session's own `.out` tail. Existing callers that still persist `isStreaming` are unaffected. diff --git a/.changeset/chat-run-pat-renewal.md b/.changeset/chat-run-pat-renewal.md deleted file mode 100644 index 8d4b6cb80..000000000 --- a/.changeset/chat-run-pat-renewal.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/core": patch -"@trigger.dev/sdk": patch ---- - -Add run-scoped PAT renewal for chat transport (`renewRunAccessToken`), fail fast on 401/403 for SSE without retry backoff, and export `isTriggerRealtimeAuthError` for auth-error detection. diff --git a/.changeset/chat-store-primitive.md b/.changeset/chat-store-primitive.md deleted file mode 100644 index 3f63d8652..000000000 --- a/.changeset/chat-store-primitive.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Add `chat.store` — a typed, bidirectional shared data slot on `chat.agent`. Agent-side foundation for TRI-8602. Independent of AG-UI — the same primitive will back the AG-UI `STATE_SNAPSHOT` / `STATE_DELTA` translator later. - -**New on the agent:** -- `chat.store.set(value)` — replace, emits a `store-snapshot` chunk on the existing chat output stream. -- `chat.store.patch([...])` — RFC 6902 JSON Patch, emits a `store-delta` chunk. -- `chat.store.get()` — read the current value (scoped to the run). -- `chat.store.onChange((value, ops) => ...)` — subscribe to changes. -- `hydrateStore?: (event) => value` config on `chat.agent` — mirrors `hydrateMessages`; restore the store after a continuation from your own persistence layer. -- `ChatTaskWirePayload.incomingStore` — optional wire field applied at turn start before `run()` fires (last-write-wins over `hydrateStore`). - -**New in core:** -- `store-snapshot` / `store-delta` chunk types and `applyChatStorePatch` helper exported from `@trigger.dev/core/v3/chat-client`. - -The store lives in memory for the lifetime of the run and is persisted by the existing chat output stream plus the `hydrateStore` hook across continuations — no new infrastructure. - -Client-side pieces (transport `getStore` / `setStore` / `applyStorePatch` / listeners, `AgentChat` accessors, `useChatStore` React hook, reference demo, docs) land in a follow-up. diff --git a/.changeset/chat-transport-session-renew-plus-preload.md b/.changeset/chat-transport-session-renew-plus-preload.md deleted file mode 100644 index 70ac843ce..000000000 --- a/.changeset/chat-transport-session-renew-plus-preload.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -`TriggerChatTransport` fixes for session-scoped auth and end-to-end UI smoke parity: - -- `RenewRunAccessTokenParams` now includes the durable `sessionId` alongside `chatId` + `runId`. Server-side renew handlers should mint the renewed PAT with `read:sessions:{sessionId}` + `write:sessions:{sessionId}` scopes (in addition to the existing run scopes) so it keeps authenticating against the session `.in` append + `.out` subscribe endpoints. Renewing without session scopes sends the transport into a 401 loop on the first append after expiry. -- `transport.preload(chatId)` on the `triggerTask` callback path no longer calls `apiClient.createSession` from the browser. The server action (e.g. `chat.createTriggerAction`) creates the session with its secret key and returns the `sessionId` in its result, matching how `sendMessages` already worked. Browser deployments that use the `triggerTask` callback path therefore no longer need `write:sessions` on any browser-side token. diff --git a/.changeset/drop-legacy-chat-stream-constants.md b/.changeset/drop-legacy-chat-stream-constants.md deleted file mode 100644 index caa517892..000000000 --- a/.changeset/drop-legacy-chat-stream-constants.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Drop the pre-Sessions chat stream-ID constants from the public surface: - -- `CHAT_STREAM_KEY`, `CHAT_MESSAGES_STREAM_ID`, `CHAT_STOP_STREAM_ID` are no longer exported from `@trigger.dev/sdk/ai` or `@trigger.dev/core/v3/chat-client`. Deletes `packages/trigger-sdk/src/v3/chat-constants.ts`. -- The `chat.stream.id` / `chat.messages.id` / `chat.stopSignal.id` labels still contain the same string values (`"chat"` / `"chat-messages"` / `"chat-stop"`) — now inlined as opaque breadcrumbs rather than user-consumable constants. Behavior and telemetry attrs are unchanged. - -These constants only mattered before the chat.agent I/O moved onto the Session primitive — the SDK no longer writes to run-scoped `streams.writer(CHAT_STREAM_KEY, …)` / `streams.input(CHAT_*_STREAM_ID)` at all. Customers who still referenced them externally should migrate to `sessions.open(sessionId).out.writer(...)` / `sessions.open(sessionId).in.on(...)` — same primitives, now session-keyed. diff --git a/.changeset/dry-sloths-divide.md b/.changeset/dry-sloths-divide.md deleted file mode 100644 index 31e7ec9b9..000000000 --- a/.changeset/dry-sloths-divide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `chat.withUIMessage()` for typed AI SDK `UIMessage` in chat task hooks, optional factory `streamOptions` merged with `uiMessageStreamOptions`, and `InferChatUIMessage` helper. Generic `ChatUIMessageStreamOptions`, compaction, and pending-message event types. `usePendingMessages` accepts a UI message type parameter; re-export `InferChatUIMessage` from `@trigger.dev/sdk/chat/react`. diff --git a/.changeset/mock-chat-agent-setup-locals.md b/.changeset/mock-chat-agent-setup-locals.md deleted file mode 100644 index b6bb587d3..000000000 --- a/.changeset/mock-chat-agent-setup-locals.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@trigger.dev/sdk": patch -"@trigger.dev/core": patch ---- - -Add `setupLocals` option to `mockChatAgent` for dependency injection in tests. Pre-seed `locals` (database clients, service stubs) before the agent's `run()` starts, so hooks read the test instance via `locals.get()` without leaking through untrusted `clientData`. Also exposes `drivers.locals.set()` on `runInMockTaskContext`. diff --git a/.changeset/mock-chat-agent-test-harness.md b/.changeset/mock-chat-agent-test-harness.md index ce7cdd88f..27bb9e3ec 100644 --- a/.changeset/mock-chat-agent-test-harness.md +++ b/.changeset/mock-chat-agent-test-harness.md @@ -3,6 +3,16 @@ "@trigger.dev/core": patch --- -Add `mockChatAgent` test harness at `@trigger.dev/sdk/ai/test` for unit-testing `chat.agent` definitions offline. Drives a real agent's turn loop without network or task runtime: send messages, actions, and stop signals via driver methods, inspect captured output chunks, and verify hooks fire. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. +Offline test harness for `chat.agent` — drive a real agent's turn loop in-process, no network, no task runtime. Pairs with `MockLanguageModelV3` from `ai/test` for model mocking. -Also adds `TestRunMetadataManager` to `@trigger.dev/core/v3/test` (in-memory metadata manager used by the harness), and exposes an `onWrite` hook on `TestRealtimeStreamsManager` so harnesses can react to stream writes without polling. +**`@trigger.dev/sdk/ai/test`:** + +- `mockChatAgent(agent, options)` — drives a chat.agent definition end-to-end. Send messages, actions, and stop signals via driver methods; inspect captured output chunks; verify hooks fire. +- `setupLocals` option — pre-seed `locals` (database clients, service stubs) before the agent's `run()` starts, so hooks read the test instance via `locals.get()` without leaking through untrusted `clientData`. + +**`@trigger.dev/core/v3/test`:** + +- `runInMockTaskContext(fn, options)` — broader test harness for any task code. Installs in-memory managers for `locals`, `lifecycleHooks`, `runtime`, `inputStreams`, and `realtimeStreams`, plus a mock `TaskContext`. Drivers send data into input streams and inspect chunks written to output streams. +- `TestRunMetadataManager` — in-memory metadata manager used by the harness. +- `TestRealtimeStreamsManager.onWrite` hook — react to stream writes without polling. +- `drivers.locals.set()` exposed for direct DI. diff --git a/.changeset/mock-task-context-test-infra.md b/.changeset/mock-task-context-test-infra.md deleted file mode 100644 index bae4ef8f9..000000000 --- a/.changeset/mock-task-context-test-infra.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Add `runInMockTaskContext` test harness at `@trigger.dev/core/v3/test` for unit-testing task code offline. Installs in-memory managers for `locals`, `lifecycleHooks`, `runtime`, `inputStreams`, and `realtimeStreams`, plus a mock `TaskContext`, so tasks can be driven end-to-end without hitting the Trigger.dev runtime. Provides drivers to send data into input streams and inspect chunks written to output streams. diff --git a/.changeset/session-sdk-toolkit.md b/.changeset/session-sdk-toolkit.md deleted file mode 100644 index 877ad9f6b..000000000 --- a/.changeset/session-sdk-toolkit.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@trigger.dev/core": patch -"@trigger.dev/sdk": patch ---- - -Extend `SessionHandle` with two asymmetric channels mirroring the run-scoped streams primitives: - -- `.in` (`SessionInputChannel`) mirrors `streams.input` — `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout` for the task to consume, `send` for external clients to produce. `.wait` / `.waitWithIdleTimeout` suspend the run on a session-stream waitpoint; the run resumes when a record lands on `.in`. -- `.out` (`SessionOutputChannel`) mirrors `streams.define` — `append` / `pipe` / `writer` for the task to produce records (all route through direct-to-S2 for uniform parsed-object serialization), plus `read` for external SSE subscribers. - -Adds the `sessionStreams` global + `StandardSessionStreamManager` (SSE-backed tail + buffer keyed on `{sessionId, io}`, registered in dev/managed run workers), `SessionStreamInstance` for direct-to-S2 piping, and `ApiClient.createSessionStreamWaitpoint` wiring. diff --git a/.changeset/session-trigger-config-extra-fields.md b/.changeset/session-trigger-config-extra-fields.md deleted file mode 100644 index 5684d8d23..000000000 --- a/.changeset/session-trigger-config-extra-fields.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/core": patch ---- - -Extend `SessionTriggerConfig` with three optional fields previously missing from the schema: `maxDuration` (per-run wall-clock cap, seconds), `lockToVersion` (pin every run to a specific worker version), and `region` (geographic scheduling). Each forwards to the matching field on `TaskRunOptions` when the run is triggered. Existing sessions without these fields are unaffected. diff --git a/.changeset/sessions-primitive.md b/.changeset/sessions-primitive.md new file mode 100644 index 000000000..c0e9463fd --- /dev/null +++ b/.changeset/sessions-primitive.md @@ -0,0 +1,22 @@ +--- +"@trigger.dev/core": patch +"@trigger.dev/sdk": patch +--- + +Sessions — durable, task-bound, bidirectional channel pair that outlives any single run. Foundation for `chat.agent` (separate changeset) and any other "one identifier, many runs over time" workflow. + +A `Session` row is keyed on `(env, externalId)` (idempotent upsert), task-bound (`taskIdentifier` + `triggerConfig` are required), and owns its current run via `currentRunId` + `currentRunVersion` (optimistic claim). Three trigger paths: session create, append-time probe (a new run is triggered if the previous one has terminated), and `end-and-continue` for in-task version handoffs. + +## SDK + +- `SessionHandle` with two asymmetric channels mirroring run-scoped streams: + - `.in` (`SessionInputChannel`) mirrors `streams.input` — `on` / `once` / `peek` / `wait` / `waitWithIdleTimeout` for the task to consume, `send` for external clients to produce. `.wait` / `.waitWithIdleTimeout` suspend the run on a session-stream waitpoint; the run resumes when a record lands on `.in`. + - `.out` (`SessionOutputChannel`) mirrors `streams.define` — `append` / `pipe` / `writer` for the task to produce records (all route through direct-to-S2 for uniform parsed-object serialization), plus `read` for external SSE subscribers. +- `sessionStreams` global + `StandardSessionStreamManager` (SSE-backed tail + buffer keyed on `{sessionId, io}`, registered in dev/managed run workers). +- `SessionStreamInstance` for direct-to-S2 piping; `ApiClient.createSessionStreamWaitpoint` wiring. + +## Core + +- `SessionId` friendly-ID generator and Session schemas, exported from `@trigger.dev/core/v3/isomorphic` alongside `RunId`, `BatchId`, etc. +- `CreateSessionStreamWaitpoint` request/response schemas alongside the main Session CRUD. +- `SessionTriggerConfig` schema: `basePayload`, `machine`, `queue`, `tags`, `maxAttempts`, `idleTimeoutInSeconds`, plus `maxDuration` (per-run wall-clock cap, seconds), `lockToVersion` (pin every run to a specific worker version), and `region` (geographic scheduling). Each forwards to the matching field on `TaskRunOptions` when the run is triggered. diff --git a/.changeset/skills-runtime-subpath.md b/.changeset/skills-runtime-subpath.md deleted file mode 100644 index de6787a70..000000000 --- a/.changeset/skills-runtime-subpath.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Split the skill-runtime primitives (`bash` + `readFile` tool implementations, backed by `node:child_process` + `node:fs/promises`) out of `@trigger.dev/sdk/ai` into a new `@trigger.dev/sdk/ai/skills-runtime` subpath. Fixes client-bundle build errors (`UnhandledSchemeError: Reading from "node:child_process"…`) that hit Next.js + Webpack when a browser page imports types from `@trigger.dev/sdk/ai` (for example `ChatUiMessage` via a shared tools file). The chat-agent factory now loads the runtime lazily via a computed-string dynamic import, so server workers still get full skill support without any caller changes. diff --git a/.changeset/trigger-chat-transport-watch-mode.md b/.changeset/trigger-chat-transport-watch-mode.md deleted file mode 100644 index 1b2346d85..000000000 --- a/.changeset/trigger-chat-transport-watch-mode.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -"@trigger.dev/sdk": patch ---- - -Add `watch` option to `TriggerChatTransport` for read-only observation of an existing chat run. - -When set to `true`, the transport keeps its internal `ReadableStream` open across `trigger:turn-complete` control chunks instead of closing it after each turn. This lets a single `useChat` / `resumeStream` subscription observe every turn of a long-lived agent run — useful for dashboard viewers or debug UIs that only want to watch an existing conversation as it unfolds, rather than drive it. - -```tsx -const transport = new TriggerChatTransport({ - task: "my-chat-task", - accessToken: runScopedPat, - watch: true, - sessions: { - [chatId]: { runId, publicAccessToken: runScopedPat }, - }, -}); - -const { messages, resumeStream } = useChat({ id: chatId, transport }); -useEffect(() => { resumeStream(); }, [resumeStream]); -``` - -Non-watch transports are unaffected — the default remains `false` and existing behavior (close on turn-complete so `useChat` can flip to `"ready"` between turns) is preserved for interactive playground-style flows.