Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 47 additions & 21 deletions .changeset/serious-vans-compare.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,52 @@
"@cloudflare/think": minor
---

Update the published packages to support AI SDK v7.

This release moves the package peer ranges from `ai@6` and `@ai-sdk/*@3` to
`ai@7` and `@ai-sdk/*@4`. Consumers need to update their AI SDK dependencies
when upgrading these packages.

Only the current implementations were migrated. New optimisations and APIs made
available by AI SDK v7, such as broader result-shape audits or stream helper
migrations, are intentionally out of scope for this release.

Top-level API notes:

- `@cloudflare/think` keeps the existing `system`, `onStepFinish`, and
`experimental_telemetry` names where callers already use them, while
translating to AI SDK v7 options internally.
- `@cloudflare/think` now also accepts `TurnConfig.telemetry` and forwards it
ahead of `experimental_telemetry` when present.
- `@cloudflare/think` adapts AI SDK v7 `onToolExecutionEnd` events back into
the existing `ToolCallResultContext` shape. `stepNumber` is now `undefined`
for tool completion callbacks because AI SDK v7 no longer provides that value
on the tool execution end event.
Support both AI SDK v6 and v7.

The `ai` peer range is `ai@^6 || ^7` (and `@ai-sdk/react` is `@^3 || ^4`) across
`agents`, `@cloudflare/ai-chat`, `@cloudflare/codemode`, and `@cloudflare/think`.
Consumers can adopt AI SDK v7 or stay on v6 — no forced AI SDK upgrade when
bumping these packages.

Only the current implementations are covered. New optimisations and APIs made
available by AI SDK v7 (broader result-shape audits, stream helper migrations,
etc.) are intentionally out of scope.

How dual-version support works — `@cloudflare/think` calls the AI SDK through the
option names present in both majors (v7 keeps the v6 names as aliases), and
normalizes the genuine divergences at the boundary:

- Uses `stepCountIs`, `system`, `experimental_telemetry`, `onStepFinish`, and
`experimental_onToolCallFinish` (in v7 this alias resolves to
`onToolExecutionEnd` and fires once).
- The tool-execution-finished event is normalized across majors: v6's
`{ success, output, error, durationMs, stepNumber }` and v7's
`{ toolOutput, toolExecutionMs }` collapse to one `ToolCallResultContext`.
`stepNumber` is `undefined` under v7 (the v7 event no longer provides it).
- The UI message stream is built via the result's `toUIMessageStream()` method
(present in both majors); the standalone `toUIMessageStream({ stream })`
helper and `result.stream` are v7-only.
- The workspace read tool emits `{ type: "file-data", data, mediaType, filename }`
model output, accepted by both majors (v7's newer `{ type: "file", data: {
type: "data", data } }` shape does not exist in v6).

Public API notes:

- `@cloudflare/think` keeps `system`, `onStepFinish`, and `experimental_telemetry`
where callers already use them, and also accepts `TurnConfig.telemetry`
(forwarded ahead of `experimental_telemetry` when present).
- `@cloudflare/ai-chat` updates the `AIChatAgent.onChatMessage` callback type
from `StreamTextOnFinishCallback` to `GenerateTextOnFinishCallback`.

Verified against both `ai@6` and `ai@7`: `@cloudflare/think` type-checks with
zero errors and its full workers test suite passes under each major.

Known limitations:

- `workers-ai-provider` (Think's default model provider) is a fixed dependency
targeting one provider-spec generation. Consumers on `ai@6` who rely on
Think's built-in default model may hit a provider-version mismatch; passing
their own `LanguageModel` avoids this.
- `chat@4.31.0` currently declares an `ai@^6` peer and does not yet advertise
v7 support; tracked separately.
- CI should exercise both an `ai@6` and an `ai@7` resolution to guard the matrix.
61 changes: 61 additions & 0 deletions .github/workflows/ai-sdk-compat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: AI SDK compat

# `@cloudflare/think` (and the packages it builds on) declare an
# `ai@^6 || ^7` peer range. This matrix installs and exercises each AI SDK
# major so neither can silently regress: a single installed `ai` version
# resolves across `agents`, `@cloudflare/ai-chat`, `@cloudflare/codemode`, and
# `@cloudflare/think`, then Think is type-checked and its workers tests run.

on:
pull_request:
paths:
- "packages/agents/**"
- "packages/ai-chat/**"
- "packages/codemode/**"
- "packages/think/**"
- "scripts/use-ai-sdk-major.mjs"
- ".github/workflows/ai-sdk-compat.yml"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

env:
NX_PARALLEL: "2"

jobs:
think-compat:
name: think (ai@${{ matrix.ai-major }})
timeout-minutes: 20
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
ai-major: ["6", "7"]
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
filter: tree:0

- uses: ./.github/actions/install

# Build Think and its workspace dependencies against the installed
# (lockfile / v7) AI SDK, mirroring the published dist that consumers
# actually resolve against their own `ai`.
- run: pnpm exec nx run @cloudflare/think:build

# For the v6 leg, repin the AI SDK deps to v6 and re-resolve so a single
# `ai@6` is shared across the workspace packages, then re-run against the
# already-built (v7) dist — the real consumer-on-v6 scenario.
- name: Pin AI SDK to v6
if: matrix.ai-major == '6'
run: |
node scripts/use-ai-sdk-major.mjs 6
pnpm install --no-frozen-lockfile

- name: Typecheck @cloudflare/think
run: cd packages/think && pnpm exec tsc --noEmit -p tsconfig.json

- name: Test @cloudflare/think (workers)
run: cd packages/think && pnpm run test:workers
4 changes: 2 additions & 2 deletions packages/agents/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@
"zod": "^4.4.3"
},
"peerDependencies": {
"@ai-sdk/react": "^4.0.0",
"@ai-sdk/react": "^3.0.0 || ^4.0.0",
"@tanstack/ai": ">=0.10.2 <1.0.0",
"@x402/core": "^2.0.0",
"@x402/evm": "^2.0.0",
"ai": "^7.0.0",
"ai": "^6.0.0 || ^7.0.0",
"chat": "^4.29.0",
"just-bash": "^3.0.0",
"react": "^19.0.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/ai-chat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
"zod": "^4.4.3"
},
"peerDependencies": {
"@ai-sdk/react": "^4.0.0",
"@ai-sdk/react": "^3.0.0 || ^4.0.0",
"agents": ">=0.17.1 <1.0.0",
"ai": "^7.0.0",
"ai": "^6.0.0 || ^7.0.0",
"react": "^19.0.0",
"zod": "^4.0.0"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/codemode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.25.0",
"@tanstack/ai": ">=0.8.0 <1.0.0",
"ai": "^7.0.0",
"ai": "^6.0.0 || ^7.0.0",
"zod": "^4.0.0"
},
"peerDependenciesMeta": {
Expand Down
4 changes: 2 additions & 2 deletions packages/think/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@
"zod": "^4.4.3"
},
"peerDependencies": {
"@ai-sdk/react": "^4.0.0",
"@ai-sdk/react": "^3.0.0 || ^4.0.0",
"@chat-adapter/telegram": "^4.29.0",
"agents": ">=0.17.1 <1.0.0",
"ai": "^7.0.0",
"ai": "^6.0.0 || ^7.0.0",
"react": "^19.0.0",
"vite": ">=6 <9",
"zod": "^4.0.0"
Expand Down
25 changes: 0 additions & 25 deletions packages/think/src/tests/agents/think-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,6 @@ export class ThinkTestAgent extends Think {
private _turnConfigOverride: TurnConfig | null = null;
private _stepConfigOverride: StepConfig | null = null;
private _beforeStepAsyncDelayMs = 0;
private _telemetryEvents: string[] = [];
private _lastModelCallSettings: CapturedModelCallSettings | null = null;
private _reasoningResponse: { response: string; reasoning: string } | null =
null;
Expand Down Expand Up @@ -848,26 +847,6 @@ export class ThinkTestAgent extends Think {
this._turnConfigOverride = { output: Output.text(), activeTools: [] };
}

async setTurnConfigTelemetry(): Promise<void> {
this._telemetryEvents = [];
this._turnConfigOverride = {
// AI SDK v7: `experimental_telemetry` renamed to `telemetry`;
// `metadata` was removed from TelemetryOptions. functionId remains.
telemetry: {
isEnabled: true,
functionId: "think-test-turn",
integrations: {
onStart: (event) => {
this._telemetryEvents.push(`start:${event.functionId ?? ""}`);
},
onEnd: (event) => {
this._telemetryEvents.push(`finish:${event.functionId ?? ""}`);
}
}
}
};
}

/**
* Sets a per-turn `experimental_transform` that upper-cases every `text-delta`
* part flowing through the stream. The transform is constructed inside the DO
Expand Down Expand Up @@ -969,10 +948,6 @@ export class ThinkTestAgent extends Think {
return this._stepLog;
}

async getTelemetryEvents(): Promise<string[]> {
return this._telemetryEvents;
}

async getLastModelCallSettings(): Promise<CapturedModelCallSettings | null> {
return this._lastModelCallSettings;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/think/src/tests/assistant-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ describe("assistant tools — read", () => {
};
expect(modelOutput.type).toBe("content");
expect(modelOutput.value).toContainEqual({
type: "file",
data: { type: "data", data: "iVBORw0KGgo=" },
type: "file-data",
data: "iVBORw0KGgo=",
mediaType: "image/png",
filename: "screenshot"
});
Expand Down Expand Up @@ -152,8 +152,8 @@ describe("assistant tools — read", () => {
};
expect(modelOutput.type).toBe("content");
expect(modelOutput.value).toContainEqual({
type: "file",
data: { type: "data", data: "JVBERi0xLjQK" },
type: "file-data",
data: "JVBERi0xLjQK",
mediaType: "application/pdf",
filename: "doc"
});
Expand Down
18 changes: 2 additions & 16 deletions packages/think/src/tests/think-session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,20 +372,6 @@ describe("Think — core", () => {
expect(turnLog[0].toolNames).not.toContain("ignoredRuntimeTool");
});

it("should forward turn telemetry to the AI SDK", async () => {
const agent = await freshAgent("chat-telemetry");

await agent.setTurnConfigTelemetry();
const result = await agent.testChat("Trace this turn");

expect(result.done).toBe(true);
// AI SDK v7 removed TelemetryOptions.metadata; functionId remains.
await expect(agent.getTelemetryEvents()).resolves.toEqual([
"start:think-test-turn",
"finish:think-test-turn"
]);
});

it("should build assistant message with text parts", async () => {
const agent = await freshAgent("chat-parts");
await agent.testChat("Hello!");
Expand Down Expand Up @@ -1687,8 +1673,8 @@ describe("Think — model message conversion", () => {
?.content?.find((part) => part.output?.type === "content")?.output;

expect(toolResult?.value).toContainEqual({
type: "file",
data: { type: "data", data: "iVBORw0KGgo=" },
type: "file-data",
data: "iVBORw0KGgo=",
mediaType: "image/png",
filename: "screenshot"
});
Expand Down
Loading
Loading