From cef4dd6afda7011bb31b943b84a0cbcb5e46a7d6 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 4 Jun 2026 15:42:06 +0100 Subject: [PATCH 1/2] feat(sdk): add AI SDK 7 support The `ai` peer range now includes v7, and the chat surfaces work against the v7 ESM-only build. v5 and v6 are unchanged. On v7, model-call spans moved from `ai` core into `@ai-sdk/otel`: install it alongside `ai@7` and the SDK registers it at chat agent boot so `experimental_telemetry` keeps flowing into run traces. An already-registered integration is detected and skipped; set `TRIGGER_AI_SDK_OTEL_AUTOREGISTER=0` to disable auto-registration. `ai@7` is ESM-only, so runtime value imports from `ai` sit behind a paired ESM/CJS shim. --- .changeset/ai-sdk-7-support.md | 5 + packages/core/package.json | 3 +- packages/core/tsconfig.ai-v7.json | 14 ++ packages/trigger-sdk/package.json | 9 +- .../src/imports/ai-runtime-cjs.cts | 26 ++++ .../trigger-sdk/src/imports/ai-runtime.ts | 39 +++++ packages/trigger-sdk/src/v3/ai.ts | 32 +++- .../trigger-sdk/src/v3/aiAutoTelemetry.ts | 83 +++++++++++ packages/trigger-sdk/src/v3/chat-client.ts | 4 +- packages/trigger-sdk/src/v3/chat-server.ts | 51 +++++-- packages/trigger-sdk/tsconfig.ai-v7.json | 17 +++ pnpm-lock.yaml | 138 ++++++++++++++++-- 12 files changed, 387 insertions(+), 34 deletions(-) create mode 100644 .changeset/ai-sdk-7-support.md create mode 100644 packages/core/tsconfig.ai-v7.json create mode 100644 packages/trigger-sdk/src/imports/ai-runtime-cjs.cts create mode 100644 packages/trigger-sdk/src/imports/ai-runtime.ts create mode 100644 packages/trigger-sdk/src/v3/aiAutoTelemetry.ts create mode 100644 packages/trigger-sdk/tsconfig.ai-v7.json diff --git a/.changeset/ai-sdk-7-support.md b/.changeset/ai-sdk-7-support.md new file mode 100644 index 00000000000..9f81c50973f --- /dev/null +++ b/.changeset/ai-sdk-7-support.md @@ -0,0 +1,5 @@ +--- +"@trigger.dev/sdk": patch +--- + +Adds AI SDK 7 support. The `ai` peer range now includes v7, and the `chat.agent` / chat surfaces work against v7's ESM-only build. On v7, install `@ai-sdk/otel` alongside `ai` and the SDK registers it for you so `experimental_telemetry` spans keep flowing into your run traces (v7 stopped emitting them from `ai` core). v5 and v6 keep working unchanged. diff --git a/packages/core/package.json b/packages/core/package.json index b089600a1f8..5940cdb56f1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -182,7 +182,7 @@ "bundle-vendor": "node scripts/bundle-superjson.mjs", "build": "pnpm run bundle-vendor && tshy && node scripts/bundle-superjson.mjs --copy && pnpm run update-version", "dev": "pnpm run bundle-vendor && tshy --watch", - "typecheck": "pnpm run bundle-vendor && tsc --noEmit -p tsconfig.src.json", + "typecheck": "pnpm run bundle-vendor && tsc --noEmit -p tsconfig.src.json && tsc --noEmit -p tsconfig.ai-v7.json", "pretest": "pnpm run bundle-vendor", "test": "vitest", "check-exports": "attw --pack ." @@ -233,6 +233,7 @@ "@types/lodash.get": "^4.4.9", "@types/readable-stream": "^4.0.14", "ai": "^6.0.0", + "ai-v7": "npm:ai@7.0.0-canary.159", "defu": "^6.1.4", "esbuild": "^0.23.0", "rimraf": "^6.0.1", diff --git a/packages/core/tsconfig.ai-v7.json b/packages/core/tsconfig.ai-v7.json new file mode 100644 index 00000000000..e2f9ae9e23f --- /dev/null +++ b/packages/core/tsconfig.ai-v7.json @@ -0,0 +1,14 @@ +{ + // Typechecks core's src against AI SDK 7 (the `ai-v7` aliased devDep). Core's + // `ai` surface is small (ChatSnapshotV1's UIMessage constraint, ToolTaskParameters' + // Schema), but it ships in the public type surface, so it gets the same v7 gate + // as the SDK. See packages/trigger-sdk/tsconfig.ai-v7.json for the `paths` + // file-direct rationale. + "extends": "./tsconfig.src.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "ai": ["./node_modules/ai-v7/dist/index.d.ts"] + } + } +} diff --git a/packages/trigger-sdk/package.json b/packages/trigger-sdk/package.json index a279457b1cc..a3e26a8b0a2 100644 --- a/packages/trigger-sdk/package.json +++ b/packages/trigger-sdk/package.json @@ -65,7 +65,7 @@ "clean": "rimraf dist .tshy .tshy-build .turbo", "build": "tshy && pnpm run update-version", "dev": "tshy --watch", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.ai-v7.json", "test": "vitest", "update-version": "tsx ../../scripts/updateVersion.ts", "check-exports": "attw --pack ." @@ -91,6 +91,7 @@ "@types/slug": "^5.0.3", "@types/ws": "^8.5.3", "ai": "^6.0.116", + "ai-v7": "npm:ai@7.0.0-canary.159", "encoding": "^0.1.13", "rimraf": "^6.0.1", "tshy": "^3.0.2", @@ -99,11 +100,15 @@ "zod": "3.25.76" }, "peerDependencies": { - "ai": "^5.0.0 || ^6.0.0", + "@ai-sdk/otel": ">=1.0.0-0 <2", + "ai": "^5.0.0 || ^6.0.0 || >=7.0.0-canary <8", "react": "^18.0 || ^19.0", "zod": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { + "@ai-sdk/otel": { + "optional": true + }, "ai": { "optional": true }, diff --git a/packages/trigger-sdk/src/imports/ai-runtime-cjs.cts b/packages/trigger-sdk/src/imports/ai-runtime-cjs.cts new file mode 100644 index 00000000000..8ad336dbaeb --- /dev/null +++ b/packages/trigger-sdk/src/imports/ai-runtime-cjs.cts @@ -0,0 +1,26 @@ +// CJS variant of ./ai-runtime.ts — tshy swaps this in for the CommonJS build. +// `require("ai")` of an ESM-only package is supported on Node >=20.19 / >=22.12. + +// @ts-ignore +const ai = require("ai"); + +// @ts-ignore +module.exports.convertToModelMessages = ai.convertToModelMessages; +// @ts-ignore +module.exports.dynamicTool = ai.dynamicTool; +// @ts-ignore +module.exports.generateId = ai.generateId; +// @ts-ignore +module.exports.getToolName = ai.getToolName; +// @ts-ignore +module.exports.isToolUIPart = ai.isToolUIPart; +// @ts-ignore +module.exports.jsonSchema = ai.jsonSchema; +// @ts-ignore +module.exports.readUIMessageStream = ai.readUIMessageStream; +// @ts-ignore +module.exports.stepCountIs = ai.stepCountIs; +// @ts-ignore +module.exports.tool = ai.tool; +// @ts-ignore +module.exports.zodSchema = ai.zodSchema; diff --git a/packages/trigger-sdk/src/imports/ai-runtime.ts b/packages/trigger-sdk/src/imports/ai-runtime.ts new file mode 100644 index 00000000000..8c713bce876 --- /dev/null +++ b/packages/trigger-sdk/src/imports/ai-runtime.ts @@ -0,0 +1,39 @@ +// Runtime VALUE imports from `ai`, isolated behind a paired ESM/CJS shim. +// +// `ai@7` is ESM-only (no `require` export). Under NodeNext + TS < 5.8 a value +// import of an ESM-only package emitted to a CJS file raises TS1479, which +// would break the SDK's CommonJS build. tshy maps `ai-runtime-cjs.cts` -> the +// CJS build and this `.ts` -> the ESM build, so each dialect gets the right +// form. `require(esm)` is stable on Node >=20.19 / >=22.12 (both our targets), +// so the CJS variant works at runtime. Mirrors `imports/uncrypto{,-cjs.cts}`. +// +// VALUES only — type-only imports from `ai` erase and don't trip TS1479, so +// they stay as direct `import type { … } from "ai"` at their use sites. + +// @ts-ignore +import { + convertToModelMessages, + dynamicTool, + generateId, + getToolName, + isToolUIPart, + jsonSchema, + readUIMessageStream, + stepCountIs, + tool, + zodSchema, +} from "ai"; + +// @ts-ignore +export { + convertToModelMessages, + dynamicTool, + generateId, + getToolName, + isToolUIPart, + jsonSchema, + readUIMessageStream, + stepCountIs, + tool, + zodSchema, +}; diff --git a/packages/trigger-sdk/src/v3/ai.ts b/packages/trigger-sdk/src/v3/ai.ts index 62aad9d8c57..cba0a09344d 100644 --- a/packages/trigger-sdk/src/v3/ai.ts +++ b/packages/trigger-sdk/src/v3/ai.ts @@ -40,14 +40,17 @@ import { } from "@trigger.dev/core/v3"; import type { FinishReason, + LanguageModelUsage, ModelMessage, + Tool, ToolSet, UIMessage, UIMessageChunk, UIMessageStreamOptions, - LanguageModelUsage, } from "ai"; import type { ChatSnapshotV1, StreamWriteResult } from "@trigger.dev/core/v3"; +// Runtime VALUES go through the ESM/CJS shim so the CJS build can `require` +// ESM-only `ai@7` (see ../imports/ai-runtime.ts). import { convertToModelMessages, dynamicTool, @@ -55,14 +58,24 @@ import { getToolName, isToolUIPart, jsonSchema, - JSONSchema7, readUIMessageStream, - Schema, tool as aiTool, - Tool, - ToolCallOptions, zodSchema, -} from "ai"; +} from "../imports/ai-runtime.js"; +import type { JSONSchema7, Schema } from "ai"; + +// `ToolCallOptions` is defined locally rather than imported from `ai`: v7 +// renamed/removed that export (it's `ToolExecutionOptions` now), so a +// direct import breaks on v7. This structural shape is wider than both majors' +// and reads the user-context field under both names (`experimental_context` on +// v6, `context` on v7). +type ToolCallOptions = { + toolCallId: string; + messages?: ModelMessage[]; + abortSignal?: AbortSignal; + experimental_context?: unknown; + context?: unknown; +}; import { type Attributes, trace } from "@opentelemetry/api"; import { auth } from "./auth.js"; import { locals } from "./locals.js"; @@ -88,6 +101,7 @@ import { type SessionSubscribeOptions, } from "./sessions.js"; import { createTask } from "./shared.js"; +import { ensureAiSdkTelemetry } from "./aiAutoTelemetry.js"; import { resourceCatalog, type SessionTriggerConfig } from "@trigger.dev/core/v3"; import { tracer } from "./tracer.js"; @@ -5147,6 +5161,12 @@ function chatAgent< ) => { locals.set(chatAgentRunContextKey, ctx); + // On AI SDK 7, register the `@ai-sdk/otel` integration (once per process) + // so `experimental_telemetry` spans flow into the run trace. Awaited here + // at run boot — before any `streamText` — and a no-op on v5/v6 or when the + // optional `@ai-sdk/otel` peer isn't installed. See ./aiAutoTelemetry.ts. + await ensureAiSdkTelemetry(); + // Bind the run to its backing Session so every module-level helper // (chat.stream, chat.messages, chat.stopSignal) resolves to this // chat's `.in` / `.out` channels. diff --git a/packages/trigger-sdk/src/v3/aiAutoTelemetry.ts b/packages/trigger-sdk/src/v3/aiAutoTelemetry.ts new file mode 100644 index 00000000000..7533dc9bf05 --- /dev/null +++ b/packages/trigger-sdk/src/v3/aiAutoTelemetry.ts @@ -0,0 +1,83 @@ +/** + * Auto-register `@ai-sdk/otel` so AI SDK 7 emits OpenTelemetry spans into the + * Trigger.dev run trace with no customer setup. + * + * AI SDK 6 emitted spans from `ai` core, so `experimental_telemetry` (set by + * `chat.toStreamTextOptions({ telemetry })`) was enough. v7 moved span emission + * into the separate `@ai-sdk/otel` adapter, so on v7 `experimental_telemetry` + * alone produces nothing until an integration is registered. We register it once + * per worker process at chat.agent run boot. `@ai-sdk/otel` writes to the global + * OpenTelemetry tracer, which is the same provider the Trigger worker installs + * (the `@opentelemetry/api` global is a `globalThis` singleton keyed by major + * version, so the separate copies still share it), so spans land in the trace. + * + * Fully guarded and best-effort — telemetry must never break a run: + * - `registerTelemetry` only exists in v7 `ai` (no-op on v5/v6). + * - `@ai-sdk/otel` is an OPTIONAL peer. The specifier is computed so the task + * bundler doesn't hard-require it (v5/v6 users never install it). + * - We detect an already-registered `@ai-sdk/otel` integration and skip, so a + * customer (or a library they import) that registers it themselves doesn't + * get duplicate spans. `registerTelemetry` is append-only, so without this + * guard a second integration would double every span. + * - To disable our auto-register entirely (e.g. you register `@ai-sdk/otel` + * yourself after this boot, or via a custom integration our detection can't + * see), set the env var `TRIGGER_AI_SDK_OTEL_AUTOREGISTER=0`. + */ +let registration: Promise | null = null; + +/** Registers the AI SDK OTel integration once per process. Safe to call on every run. */ +export function ensureAiSdkTelemetry(): Promise { + if (!registration) { + registration = register(); + } + return registration; +} + +async function register(): Promise { + try { + if (isAutoRegisterDisabled()) { + return; // opted out via TRIGGER_AI_SDK_OTEL_AUTOREGISTER + } + const aiMod: any = await import("ai"); + if (typeof aiMod.registerTelemetry !== "function") { + return; // v5 / v6 — `ai` core emits spans itself, nothing to wire. + } + // Computed specifier keeps the optional peer out of static bundler + // resolution; resolves at runtime only when the customer installed it. + const otelSpecifier = ["@ai-sdk", "otel"].join("/"); + const otelMod: any = await import(otelSpecifier).catch(() => null); + if (typeof otelMod?.OpenTelemetry !== "function") { + return; // optional peer not installed + } + if (hasAiSdkOtelIntegration(otelMod.OpenTelemetry)) { + return; // already registered by the customer or a library they import + } + aiMod.registerTelemetry(new otelMod.OpenTelemetry()); + } catch { + // never throw from telemetry setup + } +} + +function isAutoRegisterDisabled(): boolean { + const value = process.env.TRIGGER_AI_SDK_OTEL_AUTOREGISTER?.toLowerCase(); + return value === "0" || value === "false"; +} + +/** + * True if an `@ai-sdk/otel` integration is already in v7's global telemetry + * registry (`globalThis.AI_SDK_TELEMETRY_INTEGRATIONS`, a documented public + * global that `registerTelemetry` appends to). `instanceof` matches a same-copy + * registration; the constructor-name fallback catches a separate copy of + * `@ai-sdk/otel`. + */ +function hasAiSdkOtelIntegration(OpenTelemetry: any): boolean { + const integrations = (globalThis as any).AI_SDK_TELEMETRY_INTEGRATIONS; + if (!Array.isArray(integrations)) { + return false; + } + return integrations.some( + (integration: any) => + (typeof OpenTelemetry === "function" && integration instanceof OpenTelemetry) || + integration?.constructor?.name === "OpenTelemetry" + ); +} diff --git a/packages/trigger-sdk/src/v3/chat-client.ts b/packages/trigger-sdk/src/v3/chat-client.ts index dc2b2c1f39b..0bbdddcfbea 100644 --- a/packages/trigger-sdk/src/v3/chat-client.ts +++ b/packages/trigger-sdk/src/v3/chat-client.ts @@ -18,7 +18,9 @@ import type { SessionTriggerConfig, Task } from "@trigger.dev/core/v3"; import type { ModelMessage, UIMessage, UIMessageChunk } from "ai"; -import { readUIMessageStream } from "ai"; +// `readUIMessageStream` is a runtime value — via the ESM/CJS shim so the CJS +// build can `require` ESM-only `ai@7` (see ../imports/ai-runtime.ts). +import { readUIMessageStream } from "../imports/ai-runtime.js"; import { apiClientManager, controlSubtype, diff --git a/packages/trigger-sdk/src/v3/chat-server.ts b/packages/trigger-sdk/src/v3/chat-server.ts index ad954e9a9be..95e4fb529cd 100644 --- a/packages/trigger-sdk/src/v3/chat-server.ts +++ b/packages/trigger-sdk/src/v3/chat-server.ts @@ -60,22 +60,47 @@ import { TRIGGER_CONTROL_SUBTYPE, apiClientManager, } from "@trigger.dev/core/v3"; +// Runtime VALUES via the ESM/CJS shim so the CJS build can `require` ESM-only +// `ai@7` (see ../imports/ai-runtime.ts). import { convertToModelMessages, generateId as generateAssistantMessageId, stepCountIs, - type ModelMessage, - type StreamTextResult, - type Tool, - type UIMessage, - type UIMessageChunk, -} from "ai"; +} from "../imports/ai-runtime.js"; +import type { FinishReason, ModelMessage, Tool, UIMessage, UIMessageChunk } from "ai"; import type { ChatInputChunk, ChatTaskWirePayload } from "./ai-shared.js"; +// `StreamTextResult` is defined locally rather than imported from `ai`: its +// generic arity diverged (v6 `StreamTextResult`, v7 +// `StreamTextResult`), so a fixed-arity import +// can't satisfy both majors. We only read these members off the customer's +// result; a real `StreamTextResult` (any version) is assignable to this. +type AnyStreamTextResult = { + readonly finishReason: PromiseLike; + readonly response: PromiseLike<{ messages: ModelMessage[] }>; + toUIMessageStream: (...args: any[]) => any; + toUIMessageStreamResponse: (...args: any[]) => Response; +}; + // --------------------------------------------------------------------------- // Public types // --------------------------------------------------------------------------- +/** + * The options `toStreamTextOptions()` hands back to spread into `streamText`. + * Typed concretely (rather than `Record`) so the `messages` + * AI SDK 7 requires is statically present, and so `streamText` infers its tools + * from the call (rather than collapsing to `never` off a loose spread). + * + * `tools` and `stopWhen` are deliberately omitted: they're wired in at runtime, + * but `streamText` reads them from the runtime value, and leaving them off the + * type keeps it version-agnostic and avoids constraining the tool inference. + */ +export type HeadStartStreamTextOptions = { + messages: ModelMessage[]; + abortSignal: AbortSignal; +}; + export type HeadStartRunArgs> = { /** User messages parsed from the incoming request. */ messages: UIMessage[]; @@ -106,9 +131,7 @@ export type HeadStartChatHelper> = { * `abortSignal` will break the handover protocol. The intent is * that customers spread first, then add only their own keys. */ - toStreamTextOptions = Record>(opts?: { - tools?: TTools; - }): TOpts; + toStreamTextOptions(opts?: { tools?: TTools }): HeadStartStreamTextOptions; /** Lower-level escape hatch with manual `out` / `in` / dispatch primitives. */ session: HeadStartSession; }; @@ -127,13 +150,13 @@ export type HeadStartSession = { * Awaits `result.finishReason` and dispatches `handover` (with the * partial assistant ModelMessages) or `handover-skip`. */ - handoverWhenDone(result: StreamTextResult): Promise; + handoverWhenDone(result: AnyStreamTextResult): Promise; /** * Sugar over `tee` + `handoverWhenDone` + standard SSE response. * Returns a `Response` with `Content-Type: text/event-stream` whose * body is the teed stream. */ - handoverResponse(result: StreamTextResult): Response; + handoverResponse(result: AnyStreamTextResult): Response; /** * Manually dispatch the `handover` signal on `session.in`. * @@ -166,7 +189,7 @@ export type HeadStartHandlerOptions> = { * `...chat.toStreamTextOptions({ tools })` and return the * `StreamTextResult`. */ - run: (args: HeadStartRunArgs) => Promise>; + run: (args: HeadStartRunArgs) => Promise; /** * Seconds the agent run waits for the handover signal before * exiting. Defaults to 60. @@ -443,7 +466,7 @@ async function openHandoverSession(opts: { resolveDecision = resolve; }); - const handoverWhenDone = async (result: StreamTextResult) => { + const handoverWhenDone = async (result: AnyStreamTextResult) => { // Owns idle-timer cleanup via the finally below, so both the // sugar (`handoverResponse`) and the escape-hatch // (`chat.openSession()` → `handle.handoverWhenDone(...)`) clean up @@ -643,7 +666,7 @@ async function openHandoverSession(opts: { }); }; - const handoverResponse = (result: StreamTextResult): Response => { + const handoverResponse = (result: AnyStreamTextResult): Response => { // `generateMessageId` makes the customer's `start` chunk carry // `turnMessageId`, so the browser-side AI SDK keys the assistant // message by it. The agent's post-handover stream emits chunks diff --git a/packages/trigger-sdk/tsconfig.ai-v7.json b/packages/trigger-sdk/tsconfig.ai-v7.json new file mode 100644 index 00000000000..92c79ba1f8f --- /dev/null +++ b/packages/trigger-sdk/tsconfig.ai-v7.json @@ -0,0 +1,17 @@ +{ + // Typechecks the SDK source against AI SDK 7 (the `ai-v7` aliased devDep) + // instead of the v6 build devDep, so CI catches any internal source that only + // compiles against one major. The published dist is still built once against + // v6 — `ai` types in the `.d.ts` resolve to each consumer's own version. + // + // `paths` points at the `.d.ts` file directly (not the package dir): under + // `moduleResolution: NodeNext` the package-dir form silently falls back to the + // real installed `ai`, which would make this pass a no-op. + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "ai": ["./node_modules/ai-v7/dist/index.d.ts"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c5e9afcbc0..1efd8dea94f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1907,6 +1907,9 @@ importers: ai: specifier: ^6.0.0 version: 6.0.3(zod@3.25.76) + ai-v7: + specifier: npm:ai@7.0.0-canary.159 + version: ai@7.0.0-canary.159(zod@3.25.76) defu: specifier: ^6.1.5 version: 6.1.7 @@ -2156,6 +2159,9 @@ importers: packages/trigger-sdk: dependencies: + '@ai-sdk/otel': + specifier: '>=1.0.0-0 <2' + version: 1.0.0-beta.6(zod@3.25.76) '@opentelemetry/api': specifier: 1.9.1 version: 1.9.1 @@ -2214,6 +2220,9 @@ importers: ai: specifier: ^6.0.116 version: 6.0.116(zod@3.25.76) + ai-v7: + specifier: npm:ai@7.0.0-canary.159 + version: ai@7.0.0-canary.159(zod@3.25.76) encoding: specifier: ^0.1.13 version: 0.1.13 @@ -2253,12 +2262,28 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/gateway@4.0.0-beta.30': + resolution: {integrity: sha512-05tWxCb+3SRj5LBY37nRQ8eUdolO5QvKAfsIAUY3zAxg0jc1cBkQNSpZEVwQUR4uFm/Y5fLJ+MIa+6hkol1//A==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/gateway@4.0.0-canary.93': + resolution: {integrity: sha512-/d0d/DfJRmdMJJww/ihrYezgKedJhrOCsREuAuSHmd/kVzFB5p2bBuCZH3EBxKFQ8NLtPJpOR+QJxOUmDLD8PA==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/openai@3.0.41': resolution: {integrity: sha512-IZ42A+FO+vuEQCVNqlnAPYQnnUpUfdJIwn1BEDOBywiEHa23fw7PahxVtlX9zm3/zMvTW4JKPzWyvAgDu+SQ2A==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/otel@1.0.0-beta.6': + resolution: {integrity: sha512-K5VikyO3EKQkNk77ew9oMjM8FInKF+WWar599LmP8rQ0x0iB+P/DVS+h6zQvmecxMNPtQOOyt0uDQFx/AA0DGw==} + engines: {node: '>=18'} + '@ai-sdk/provider-utils@1.0.22': resolution: {integrity: sha512-YHK2rpj++wnLVc9vPGzGFP3Pjeld2MwhKinetA0zKXOoHAT/Jit5O8kZsxcSlJPu9wvcGT1UGZEjZrtO7PfFOQ==} engines: {node: '>=18'} @@ -2286,6 +2311,18 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider-utils@5.0.0-beta.9': + resolution: {integrity: sha512-Ku/S1TV8ibqlweKQHa22eWT9QvKqR4hkl7KMJ0k7kw64Q7XObh12fJUnK4Mh8q+IR5ZeQGm95SLtx++d7FC6XA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@5.0.0-canary.44': + resolution: {integrity: sha512-3h7gp8MEwXACWsW8ZPjzio53ynKq0jTNp4Hwr0BWdychMFzmkRRWyvijhg56JWxye16FU92SqQnfBoe2PN4I2A==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + '@ai-sdk/provider@0.0.26': resolution: {integrity: sha512-dQkfBDs2lTYpKM8389oopPdQgIU007GQyCbuPPrV+K6MtSII3HBfE0stUIMXUb44L+LK1t6GXPP7wjSzjO6uKg==} engines: {node: '>=18'} @@ -2298,6 +2335,14 @@ packages: resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} + '@ai-sdk/provider@4.0.0-beta.5': + resolution: {integrity: sha512-+07aGNCVXEKgGXtAXjjROPcpHMudWfvsm5UvlZ4LtTfjX9cT61x7h3W0pSSpWY/H7doymdm8PmuX6Z8AvP03WQ==} + engines: {node: '>=18'} + + '@ai-sdk/provider@4.0.0-canary.17': + resolution: {integrity: sha512-m/rtalImeIt7deuQGkEHehqlIPOM8Sjb0cF1b1SJ3B6Re+WYgbUkOK9gY2SSro1YO8jYBRgTMPz2qYzPtIzAxQ==} + engines: {node: '>=22'} + '@ai-sdk/react@3.0.170': resolution: {integrity: sha512-YUDn+mK0c8iUz14rCBf1A0zg6SV5b5aSVUz+azF1bdBd1SFXVI19dKYR+PQSpZY+0+z+zs252AAsacUqiO98Kw==} engines: {node: '>=18'} @@ -8773,6 +8818,9 @@ packages: resolution: {integrity: sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==} engines: {node: 18 >=18.20 || 20 || >=22} + '@workflow/serde@4.1.0': + resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} + '@xobotyi/scrollbar-width@1.9.5': resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} @@ -8889,6 +8937,18 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 + ai@7.0.0-beta.60: + resolution: {integrity: sha512-grSKE/9kQtElLCnrhwriTLwXlV+5W8Ljco2KzVc5cY9egK1zwSwVyFQo3L3Al+WVxwvVT6fQjLyEdQpqJHLTrA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ai@7.0.0-canary.159: + resolution: {integrity: sha512-9z8OkZdrISDjS3bguQ/mCYsthmr7mNbB1hTa8cRg8aAp31r1HCxVFqmc+ZXTA0FqhGb8aW46UUcjUuJcwxg5Mw==} + engines: {node: '>=22'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -10513,10 +10573,6 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} - enhanced-resolve@5.22.1: - resolution: {integrity: sha512-6QEuw3zoX1SJQc7b87aBXke/no+mG2bTBgw29gWMQonLmpEkWoCAVkl+M49e48AZlWzxiDzDZzYdp6kobcyLww==} - engines: {node: '>=10.13.0'} - enquirer@2.3.6: resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} engines: {node: '>=8.6'} @@ -11041,6 +11097,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource-parser@3.1.0: + resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} + engines: {node: '>=18.0.0'} + eventsource@3.0.5: resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} engines: {node: '>=18.0.0'} @@ -17091,12 +17151,34 @@ snapshots: '@vercel/oidc': 3.1.0 zod: 3.25.76 + '@ai-sdk/gateway@4.0.0-beta.30(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.0-beta.5 + '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@3.25.76) + '@vercel/oidc': 3.2.0 + zod: 3.25.76 + + '@ai-sdk/gateway@4.0.0-canary.93(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.0-canary.17 + '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@3.25.76) + '@vercel/oidc': 3.2.0 + zod: 3.25.76 + '@ai-sdk/openai@3.0.41(zod@3.25.76)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.19(zod@3.25.76) zod: 3.25.76 + '@ai-sdk/otel@1.0.0-beta.6(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.0-beta.5 + '@opentelemetry/api': 1.9.0 + ai: 7.0.0-beta.60(zod@3.25.76) + transitivePeerDependencies: + - zod + '@ai-sdk/provider-utils@1.0.22(zod@3.25.76)': dependencies: '@ai-sdk/provider': 0.0.26 @@ -17127,6 +17209,21 @@ snapshots: eventsource-parser: 3.0.6 zod: 3.25.76 + '@ai-sdk/provider-utils@5.0.0-beta.9(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.0-beta.5 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 + + '@ai-sdk/provider-utils@5.0.0-canary.44(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 4.0.0-canary.17 + '@standard-schema/spec': 1.1.0 + '@workflow/serde': 4.1.0 + eventsource-parser: 3.1.0 + zod: 3.25.76 + '@ai-sdk/provider@0.0.26': dependencies: json-schema: 0.4.0 @@ -17139,6 +17236,14 @@ snapshots: dependencies: json-schema: 0.4.0 + '@ai-sdk/provider@4.0.0-beta.5': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@4.0.0-canary.17': + dependencies: + json-schema: 0.4.0 + '@ai-sdk/react@3.0.170(react@18.2.0)(zod@3.25.76)': dependencies: '@ai-sdk/provider-utils': 4.0.23(zod@3.25.76) @@ -26129,6 +26234,8 @@ snapshots: '@wolfy1339/lru-cache@11.0.2-patch.1': {} + '@workflow/serde@4.1.0': {} + '@xobotyi/scrollbar-width@1.9.5': {} '@xtuc/ieee754@1.2.0': {} @@ -26232,6 +26339,20 @@ snapshots: '@opentelemetry/api': 1.9.0 zod: 3.25.76 + ai@7.0.0-beta.60(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 4.0.0-beta.30(zod@3.25.76) + '@ai-sdk/provider': 4.0.0-beta.5 + '@ai-sdk/provider-utils': 5.0.0-beta.9(zod@3.25.76) + zod: 3.25.76 + + ai@7.0.0-canary.159(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 4.0.0-canary.93(zod@3.25.76) + '@ai-sdk/provider': 4.0.0-canary.17 + '@ai-sdk/provider-utils': 5.0.0-canary.44(zod@3.25.76) + zod: 3.25.76 + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 @@ -28006,11 +28127,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 - enhanced-resolve@5.22.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - enquirer@2.3.6: dependencies: ansi-colors: 4.1.3 @@ -28802,6 +28918,8 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource-parser@3.1.0: {} + eventsource@3.0.5: dependencies: eventsource-parser: 3.0.0 @@ -35606,7 +35724,7 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.22.1 + enhanced-resolve: 5.18.3 es-module-lexer: 1.7.0 eslint-scope: 5.1.1 events: 3.3.0 From e55fa9f3cdfe62c9480ccc31f15058e1dffe6ade Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Thu, 4 Jun 2026 16:23:31 +0100 Subject: [PATCH 2/2] fix(sdk): propagate AI SDK 7 tool context to task-backed tools On v7 the tool execution context is passed as `context` (v6 used `experimental_context`). createTaskToolExecuteHandler only read `experimental_context`, so task-backed tools (`ai.toolExecute`) lost their context on v7. Read whichever is set and stamp both names into the subtask metadata. --- packages/trigger-sdk/src/v3/ai.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/trigger-sdk/src/v3/ai.ts b/packages/trigger-sdk/src/v3/ai.ts index cba0a09344d..853cf6e69e4 100644 --- a/packages/trigger-sdk/src/v3/ai.ts +++ b/packages/trigger-sdk/src/v3/ai.ts @@ -131,6 +131,8 @@ function toModelMessages(messages: UIMessage[]): Promise { export type ToolCallExecutionOptions = { toolCallId: string; experimental_context?: unknown; + /** v7 name for the user context (`experimental_context` on v6). */ + context?: unknown; /** Chat context — only present when the tool runs inside a chat.agent turn. */ chatId?: string; turn?: number; @@ -907,9 +909,14 @@ function createTaskToolExecuteHandler< const toolMeta: ToolCallExecutionOptions = { toolCallId: toolOpts?.toolCallId ?? "", }; - if (toolOpts?.experimental_context !== undefined) { + // v6 passes user context as `experimental_context`, v7 as `context`. Read + // whichever is set and stamp both so subtasks reading either name work. + const toolContext = toolOpts?.context ?? toolOpts?.experimental_context; + if (toolContext !== undefined) { try { - toolMeta.experimental_context = JSON.parse(JSON.stringify(toolOpts.experimental_context)); + const serialized = JSON.parse(JSON.stringify(toolContext)); + toolMeta.experimental_context = serialized; + toolMeta.context = serialized; } catch { /* non-serializable */ }