From 4c4d787b1aa52ec7abd9000af7a71620382e47cb Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Thu, 5 Feb 2026 21:14:11 +0100 Subject: [PATCH] feat(ai): add adaptive thinking support for Claude Opus 4.6 - Add adaptive thinking mode (type: 'adaptive') for Opus 4.6+ - Add effort parameter ('low', 'medium', 'high', 'max') for adaptive thinking - thinkingEnabled now auto-detects: adaptive for 4.6+, budget-based for older - streamSimple/completeSimple map ThinkingLevel to effort levels for Opus 4.6 - Add tests for Opus 4.6 adaptive thinking and GPT-5.3 Codex - Update @anthropic-ai/sdk to 0.73.0 - Update @aws-sdk/client-bedrock-runtime to 3.983.0 - Update @google/genai to 1.40.0 - Remove fast-xml-parser override (no longer needed) --- package-lock.json | 115 ++++++++++++++++++++++--- package.json | 1 - packages/ai/CHANGELOG.md | 14 +++ packages/ai/package.json | 6 +- packages/ai/src/providers/anthropic.ts | 78 ++++++++++++++++- packages/ai/test/stream.test.ts | 64 ++++++++++++++ 6 files changed, 260 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6930e71..02d4e002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,9 +63,9 @@ } }, "node_modules/@anthropic-ai/sdk": { - "version": "0.71.2", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", - "integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==", + "version": "0.73.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.73.0.tgz", + "integrity": "sha512-URURVzhxXGJDGUGFunIOtBlSl7KWvZiAAKY/ttTkZAkXT9bTPqdk2eK0b8qqSxXpikh3QKPnPYpiyX98zf5ebw==", "license": "MIT", "dependencies": { "json-schema-to-ts": "^3.1.1" @@ -1536,19 +1536,20 @@ } }, "node_modules/@google/genai": { - "version": "1.34.0", - "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.34.0.tgz", - "integrity": "sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.40.0.tgz", + "integrity": "sha512-fhIww8smT0QYRX78qWOiz/nIQhHMF5wXOrlXvj33HBrz3vKDBb+wibLcEmTA+L9dmPD4KmfNr7UF3LDQVTXNjA==", "license": "Apache-2.0", "dependencies": { "google-auth-library": "^10.3.0", + "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "engines": { "node": ">=20.0.0" }, "peerDependencies": { - "@modelcontextprotocol/sdk": "^1.24.0" + "@modelcontextprotocol/sdk": "^1.25.2" }, "peerDependenciesMeta": { "@modelcontextprotocol/sdk": { @@ -2602,6 +2603,70 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", @@ -6411,6 +6476,12 @@ "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "license": "MIT" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -7059,6 +7130,30 @@ "node": ">= 4" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -8470,9 +8565,9 @@ "version": "0.52.0", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "0.71.2", - "@aws-sdk/client-bedrock-runtime": "^3.966.0", - "@google/genai": "1.34.0", + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", diff --git a/package.json b/package.json index a292961f..2200a9eb 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,6 @@ "get-east-asian-width": "^1.4.0" }, "overrides": { - "fast-xml-parser": "5.3.4", "rimraf": "6.1.2", "gaxios": { "rimraf": "6.1.2" diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index f6ce23f7..aaaf3e59 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,20 @@ ## [Unreleased] +### Added + +- Added adaptive thinking support for Claude Opus 4.6 with effort levels (`low`, `medium`, `high`, `max`) +- Added `effort` option to `AnthropicOptions` for controlling adaptive thinking depth +- `thinkingEnabled` now automatically uses adaptive thinking for Opus 4.6+ models and budget-based thinking for older models +- `streamSimple`/`completeSimple` automatically map `ThinkingLevel` to effort levels for Opus 4.6 + +### Changed + +- Updated `@anthropic-ai/sdk` to 0.73.0 +- Updated `@aws-sdk/client-bedrock-runtime` to 3.983.0 +- Updated `@google/genai` to 1.40.0 +- Removed `fast-xml-parser` override (no longer needed) + ## [0.52.0] - 2026-02-05 ### Added diff --git a/packages/ai/package.json b/packages/ai/package.json index 1e7a38d8..37c8cf5a 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -22,9 +22,9 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@anthropic-ai/sdk": "0.71.2", - "@aws-sdk/client-bedrock-runtime": "^3.966.0", - "@google/genai": "1.34.0", + "@anthropic-ai/sdk": "^0.73.0", + "@aws-sdk/client-bedrock-runtime": "^3.983.0", + "@google/genai": "^1.40.0", "@mistralai/mistralai": "1.10.0", "@sinclair/typebox": "^0.34.41", "ajv": "^8.17.1", diff --git a/packages/ai/src/providers/anthropic.ts b/packages/ai/src/providers/anthropic.ts index dd1ab70b..0a52a316 100644 --- a/packages/ai/src/providers/anthropic.ts +++ b/packages/ai/src/providers/anthropic.ts @@ -151,9 +151,30 @@ function convertContentBlocks(content: (TextContent | ImageContent)[]): return blocks; } +export type AnthropicEffort = "low" | "medium" | "high" | "max"; + export interface AnthropicOptions extends StreamOptions { + /** + * Enable extended thinking. + * For Opus 4.6+: uses adaptive thinking (Claude decides when/how much to think). + * For older models: uses budget-based thinking with thinkingBudgetTokens. + */ thinkingEnabled?: boolean; + /** + * Token budget for extended thinking (older models only). + * Ignored for Opus 4.6+ which uses adaptive thinking. + */ thinkingBudgetTokens?: number; + /** + * Effort level for adaptive thinking (Opus 4.6+ only). + * Controls how much thinking Claude allocates: + * - "max": Always thinks with no constraints + * - "high": Always thinks, deep reasoning (default) + * - "medium": Moderate thinking, may skip for simple queries + * - "low": Minimal thinking, skips for simple tasks + * Ignored for older models. + */ + effort?: AnthropicEffort; interleavedThinking?: boolean; toolChoice?: "auto" | "any" | "none" | { type: "tool"; name: string }; } @@ -377,6 +398,34 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti return stream; }; +/** + * Check if a model supports adaptive thinking (Opus 4.6+) + */ +function supportsAdaptiveThinking(modelId: string): boolean { + // Opus 4.6 model IDs (with or without date suffix) + return modelId.includes("opus-4-6") || modelId.includes("opus-4.6"); +} + +/** + * Map ThinkingLevel to Anthropic effort levels for adaptive thinking + */ +function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): AnthropicEffort { + switch (level) { + case "minimal": + return "low"; + case "low": + return "low"; + case "medium": + return "medium"; + case "high": + return "high"; + case "xhigh": + return "max"; + default: + return "high"; + } +} + export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleStreamOptions> = ( model: Model<"anthropic-messages">, context: Context, @@ -392,6 +441,17 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS return streamAnthropic(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions); } + // For Opus 4.6+: use adaptive thinking with effort level + // For older models: use budget-based thinking + if (supportsAdaptiveThinking(model.id)) { + const effort = mapThinkingLevelToEffort(options.reasoning); + return streamAnthropic(model, context, { + ...base, + thinkingEnabled: true, + effort, + } satisfies AnthropicOptions); + } + const adjusted = adjustMaxTokensForThinking( base.maxTokens || 0, model.maxTokens, @@ -517,11 +577,21 @@ function buildParams( params.tools = convertTools(context.tools, isOAuthToken); } + // Configure thinking mode: adaptive (Opus 4.6+) or budget-based (older models) if (options?.thinkingEnabled && model.reasoning) { - params.thinking = { - type: "enabled", - budget_tokens: options.thinkingBudgetTokens || 1024, - }; + if (supportsAdaptiveThinking(model.id)) { + // Adaptive thinking: Claude decides when and how much to think + params.thinking = { type: "adaptive" }; + if (options.effort) { + params.output_config = { effort: options.effort }; + } + } else { + // Budget-based thinking for older models + params.thinking = { + type: "enabled", + budget_tokens: options.thinkingBudgetTokens || 1024, + }; + } } if (options?.toolChoice) { diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index 6b70550f..6d86b40f 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -922,6 +922,42 @@ describe("Generate E2E Tests", () => { }); }); + describe("Anthropic OAuth Provider (claude-opus-4-6 with adaptive thinking)", () => { + const model = getModel("anthropic", "claude-opus-4-6"); + + it.skipIf(!anthropicOAuthToken)("should complete basic text generation", { retry: 3 }, async () => { + await basicTextGeneration(model, { apiKey: anthropicOAuthToken }); + }); + + it.skipIf(!anthropicOAuthToken)("should handle tool calling", { retry: 3 }, async () => { + await handleToolCall(model, { apiKey: anthropicOAuthToken }); + }); + + it.skipIf(!anthropicOAuthToken)("should handle streaming", { retry: 3 }, async () => { + await handleStreaming(model, { apiKey: anthropicOAuthToken }); + }); + + it.skipIf(!anthropicOAuthToken)("should handle adaptive thinking with effort high", { retry: 3 }, async () => { + await handleThinking(model, { apiKey: anthropicOAuthToken, thinkingEnabled: true, effort: "high" }); + }); + + it.skipIf(!anthropicOAuthToken)("should handle adaptive thinking with effort medium", { retry: 3 }, async () => { + await handleThinking(model, { apiKey: anthropicOAuthToken, thinkingEnabled: true, effort: "medium" }); + }); + + it.skipIf(!anthropicOAuthToken)( + "should handle multi-turn with adaptive thinking and tools", + { retry: 3 }, + async () => { + await multiTurn(model, { apiKey: anthropicOAuthToken, thinkingEnabled: true, effort: "high" }); + }, + ); + + it.skipIf(!anthropicOAuthToken)("should handle image input", { retry: 3 }, async () => { + await handleImage(model, { apiKey: anthropicOAuthToken }); + }); + }); + describe("GitHub Copilot Provider (gpt-4o via OpenAI Completions)", () => { const llm = getModel("github-copilot", "gpt-4o"); @@ -1098,6 +1134,34 @@ describe("Generate E2E Tests", () => { }); }); + describe("OpenAI Codex Provider (gpt-5.3-codex)", () => { + const llm = getModel("openai-codex", "gpt-5.3-codex"); + + it.skipIf(!openaiCodexToken)("should complete basic text generation", { retry: 3 }, async () => { + await basicTextGeneration(llm, { apiKey: openaiCodexToken }); + }); + + it.skipIf(!openaiCodexToken)("should handle tool calling", { retry: 3 }, async () => { + await handleToolCall(llm, { apiKey: openaiCodexToken }); + }); + + it.skipIf(!openaiCodexToken)("should handle streaming", { retry: 3 }, async () => { + await handleStreaming(llm, { apiKey: openaiCodexToken }); + }); + + it.skipIf(!openaiCodexToken)("should handle thinking with reasoningEffort high", { retry: 3 }, async () => { + await handleThinking(llm, { apiKey: openaiCodexToken, reasoningEffort: "high" }); + }); + + it.skipIf(!openaiCodexToken)("should handle multi-turn with thinking and tools", { retry: 3 }, async () => { + await multiTurn(llm, { apiKey: openaiCodexToken, reasoningEffort: "high" }); + }); + + it.skipIf(!openaiCodexToken)("should handle image input", { retry: 3 }, async () => { + await handleImage(llm, { apiKey: openaiCodexToken }); + }); + }); + describe.skipIf(!hasBedrockCredentials())("Amazon Bedrock Provider (claude-sonnet-4-5)", () => { const llm = getModel("amazon-bedrock", "global.anthropic.claude-sonnet-4-5-20250929-v1:0");