mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
fix(anthropic+bedrock): adaptive thinking + summarized display for claude-opus-4-7+ (#2471)
* fix(anthropic): use thinking.type=adaptive for claude-opus-4-7+
claude-opus-4-7 rejects the legacy thinking shape with a 400:
`thinking.type.enabled` is not supported for this model.
Use `thinking.type.adaptive` and `output_config.effort`.
Detect opus-4 minor version in getModelInfo and emit
{ type: "adaptive" } for >=7, keeping { type: "enabled", budget_tokens }
for opus-4-6 and earlier, sonnet-4, and 3-7-sonnet.
Bumps @langchain/anthropic ^1.0.0 -> ^1.3.29 to pick up the adaptive
ThinkingConfigParam variant and an updated tool-schema typing that lets
BedrockChatModel.convertTools drop its now-redundant Record cast.
Fixes #2361. Replaces stale PR #2362.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(bedrock): use thinking.type=adaptive for claude-opus-4-7+
Same 400 from claude-opus-4-7+ as the direct Anthropic path (fixed in
the previous commit), but the Bedrock provider has its own payload
builder in BedrockChatModel.buildRequestBody that was unaffected.
Add the same minor-version detection inside that payload builder.
Uses an unanchored regex /claude-opus-4-(\d+)/ because Bedrock model
IDs carry a provider/profile prefix (e.g.
"global.anthropic.claude-opus-4-7-20260115-v1:0").
Fixes #2384.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: use real sonnet model IDs
claude-sonnet-4-7 doesn't exist; tests were asserting against a
fabricated ID. Switch the non-opus negative cases to a real
claude-sonnet-4-5 (and keep the existing claude-3-7-sonnet test).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(thinking): set output_config.display=summarized for claude-opus-4-7+
Adaptive thinking on Opus 4.7+ alone restores the request shape but
leaves the UI empty — Anthropic flipped the default for
output_config.display from "summarized" to "omitted" on this model
line, so the API stops emitting visible thinking content blocks.
Pre-4.7 models still default to "summarized" server-side.
Set display: "summarized" alongside thinking: { type: "adaptive" } on
both the direct Anthropic and Bedrock paths so opus-4-7 renders
thinking summaries the same way sonnet 4.6 does today.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(thinking): nest display=summarized inside thinking config
The Anthropic API places `display: 'summarized' | 'omitted'` on the
thinking config itself (ThinkingConfigAdaptive / ThinkingConfigEnabled),
not on a top-level `output_config`. The previous commit set
`outputConfig: { display: "summarized" }` on the ChatAnthropic config and
`output_config: { display: "summarized" }` on the Bedrock request body,
which broke the TS build (the SDK's OutputConfig only exposes
effort/format) and would not have produced the intended server behavior
for the adaptive thinking summarization toggle.
- chatModelManager.ts: emit `thinking: { type: "adaptive", display: "summarized" }`
- BedrockChatModel.ts: same, on the request payload
- BedrockChatModel.test.ts: update assertions to the new shape
* fix(thinking): constrain Opus minor regex so dated snapshots don't match
The previous `^claude-opus-4-(\d+)` (Anthropic) and unanchored
`claude-opus-4-(\d+)` (Bedrock) regex captured the date portion of
snapshot IDs like `claude-opus-4-20250514` as if it were the minor
version, so `usesAdaptiveThinking` evaluated as `true` (20250514 >= 7)
for pre-4.7 Opus models. That would send `thinking: { type: "adaptive" }`
for the legacy 4.0 snapshot and could trigger 400s on REASONING-enabled
runs.
Constrain the minor capture to 1-2 digits followed by `-`/`.` or end of
string. Real Anthropic minor versions are single/double digit; dated
snapshots have an 8-digit date that this regex now rejects.
- src/utils.ts: anchored fix for the Anthropic path
- src/LLMProviders/BedrockChatModel.ts: unanchored fix for Bedrock IDs
- regression tests for dated 4.0/4.1 snapshots in both src/utils.test.ts
and src/LLMProviders/BedrockChatModel.test.ts
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
475d5b9c18
commit
3f12c14444
7 changed files with 187 additions and 89 deletions
101
package-lock.json
generated
101
package-lock.json
generated
|
|
@ -12,7 +12,7 @@
|
|||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@langchain/anthropic": "^1.0.0",
|
||||
"@langchain/anthropic": "^1.3.29",
|
||||
"@langchain/classic": "^1.0.9",
|
||||
"@langchain/core": "^1.1.29",
|
||||
"@langchain/deepseek": "^1.0.0",
|
||||
|
|
@ -123,6 +123,26 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.91.1",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz",
|
||||
"integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"anthropic-ai-sdk": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.26.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
|
||||
|
|
@ -2323,38 +2343,19 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@langchain/anthropic": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-1.0.0.tgz",
|
||||
"integrity": "sha512-Lud/FrkFmXMYW5R9y0FC+RGdgjBBVQ2JAnG3A8E1I4+sqv5JgJttw3HKRpFkyBUSyacs6LMfSn5dbJ6TT9nMiQ==",
|
||||
"version": "1.3.29",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/anthropic/-/anthropic-1.3.29.tgz",
|
||||
"integrity": "sha512-ep1qBIcV07bajsg3fDqMd39rYwoRLOEK/6lk+MCxlm1YB5SRoKKJAZANrblQ/4RYhZJnxf95c6BSQu8VoNbVAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.65.0"
|
||||
"@anthropic-ai/sdk": "^0.91.1",
|
||||
"zod": "^3.25.76 || ^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@langchain/core": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/anthropic/node_modules/@anthropic-ai/sdk": {
|
||||
"version": "0.65.0",
|
||||
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.65.0.tgz",
|
||||
"integrity": "sha512-zIdPOcrCVEI8t3Di40nH4z9EoeyGZfXbYSvWdDLsB/KkaSYMnEgC7gmcgWu83g2NTn1ZTpbMvpdttWDGGIk6zw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "^3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"anthropic-ai-sdk": "bin/cli"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
"@langchain/core": "^1.1.45"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/classic": {
|
||||
|
|
@ -2411,49 +2412,23 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@langchain/core": {
|
||||
"version": "1.1.29",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.29.tgz",
|
||||
"integrity": "sha512-BPoegTtIdZX4gl2kxcSXAlLrrJFl1cxeRsk9DM/wlIuvyPrFwjWqrEK5NwF5diDt5XSArhQxIFaifGAl4F7fgw==",
|
||||
"version": "1.1.46",
|
||||
"resolved": "https://registry.npmjs.org/@langchain/core/-/core-1.1.46.tgz",
|
||||
"integrity": "sha512-i8rDC83BpItxChCw4Lf+6tAr+k+OUcbirc5ZkrhI9ywYWmvxegUljLGOGYvtJNTbEAIFkhYIODPE5QRqyjF6sA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cfworker/json-schema": "^4.0.2",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"camelcase": "6",
|
||||
"decamelize": "1.2.0",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"js-tiktoken": "^1.0.12",
|
||||
"langsmith": ">=0.5.0 <1.0.0",
|
||||
"mustache": "^4.2.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"uuid": "^11.1.0",
|
||||
"zod": "^3.25.76 || ^4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core/node_modules/ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core/node_modules/camelcase": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
|
||||
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@langchain/core/node_modules/p-queue": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||
|
|
@ -6169,6 +6144,12 @@
|
|||
"@sinonjs/commons": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
|
|
@ -8215,14 +8196,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@langchain/anthropic": "^1.0.0",
|
||||
"@langchain/anthropic": "^1.3.29",
|
||||
"@langchain/classic": "^1.0.9",
|
||||
"@langchain/core": "^1.1.29",
|
||||
"@langchain/deepseek": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ type ImageContent = {
|
|||
} | null;
|
||||
|
||||
type RequestBody = {
|
||||
thinking?: { type: string; budget_tokens: number };
|
||||
thinking?:
|
||||
| { type: "enabled"; budget_tokens: number }
|
||||
| { type: "adaptive"; display?: "summarized" | "omitted" };
|
||||
temperature?: number;
|
||||
anthropic_version?: string;
|
||||
messages: Array<{
|
||||
|
|
@ -77,13 +79,15 @@ const buildEventStreamChunk = (payload: string): string => {
|
|||
return Buffer.from(buffer).toString("base64");
|
||||
};
|
||||
|
||||
const createModel = (enableThinking = false): BedrockChatModel =>
|
||||
const createModel = (
|
||||
enableThinking = false,
|
||||
modelId = "anthropic.claude-3-haiku-20240307-v1:0"
|
||||
): BedrockChatModel =>
|
||||
new BedrockChatModel({
|
||||
modelId: "anthropic.claude-3-haiku-20240307-v1:0",
|
||||
modelId,
|
||||
apiKey: "test-key",
|
||||
endpoint: "https://example.com/model/anthropic.claude-3-haiku-20240307-v1%3A0/invoke",
|
||||
streamEndpoint:
|
||||
"https://example.com/model/anthropic.claude-3-haiku-20240307-v1%3A0/invoke-with-response-stream",
|
||||
endpoint: `https://example.com/model/${encodeURIComponent(modelId)}/invoke`,
|
||||
streamEndpoint: `https://example.com/model/${encodeURIComponent(modelId)}/invoke-with-response-stream`,
|
||||
anthropicVersion: "bedrock-2023-05-31",
|
||||
enableThinking,
|
||||
fetchImplementation: jest.fn(),
|
||||
|
|
@ -455,6 +459,68 @@ describe("BedrockChatModel streaming decode", () => {
|
|||
expect(requestBody.temperature).toBe(1);
|
||||
expect(requestBody.thinking).toBeDefined();
|
||||
});
|
||||
|
||||
it("uses adaptive thinking with summarized display for claude-opus-4-7", () => {
|
||||
const model = createModel(true, "anthropic.claude-opus-4-7-20260115-v1:0");
|
||||
const requestBody = asInternal(model).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]);
|
||||
|
||||
expect(requestBody.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||
expect(requestBody.temperature).toBe(1);
|
||||
});
|
||||
|
||||
it("uses adaptive thinking for opus-4-7 cross-region inference profiles", () => {
|
||||
const model = createModel(true, "global.anthropic.claude-opus-4-7-20260115-v1:0");
|
||||
const requestBody = asInternal(model).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]);
|
||||
|
||||
expect(requestBody.thinking).toEqual({ type: "adaptive", display: "summarized" });
|
||||
});
|
||||
|
||||
it("keeps legacy thinking for opus-4-6 and earlier", () => {
|
||||
const model = createModel(true, "anthropic.claude-opus-4-6-20250115-v1:0");
|
||||
const requestBody = asInternal(model).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]);
|
||||
|
||||
expect(requestBody.thinking).toEqual({ type: "enabled", budget_tokens: 2048 });
|
||||
});
|
||||
|
||||
it("keeps legacy thinking for sonnet-4 and 3-7-sonnet", () => {
|
||||
const sonnet45 = createModel(true, "anthropic.claude-sonnet-4-5-20250929-v1:0");
|
||||
expect(
|
||||
asInternal(sonnet45).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]).thinking
|
||||
).toEqual({ type: "enabled", budget_tokens: 2048 });
|
||||
|
||||
const sonnet37 = createModel(true, "anthropic.claude-3-7-sonnet-20250219-v1:0");
|
||||
expect(
|
||||
asInternal(sonnet37).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]).thinking
|
||||
).toEqual({ type: "enabled", budget_tokens: 2048 });
|
||||
});
|
||||
|
||||
it("keeps legacy thinking for dated Opus 4.0 snapshot IDs", () => {
|
||||
// anthropic.claude-opus-4-20250514-v1:0 is the dated snapshot of Opus 4.0, not 4.20250514.
|
||||
const opus40 = createModel(true, "anthropic.claude-opus-4-20250514-v1:0");
|
||||
expect(
|
||||
asInternal(opus40).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]).thinking
|
||||
).toEqual({ type: "enabled", budget_tokens: 2048 });
|
||||
|
||||
// anthropic.claude-opus-4-1-20250805-v1:0 is dated 4.1, not adaptive.
|
||||
const opus41 = createModel(true, "anthropic.claude-opus-4-1-20250805-v1:0");
|
||||
expect(
|
||||
asInternal(opus41).buildRequestBody([
|
||||
{ role: "user", content: "test", getType: () => "human" },
|
||||
]).thinking
|
||||
).toEqual({ type: "enabled", budget_tokens: 2048 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("vision support", () => {
|
||||
|
|
|
|||
|
|
@ -177,13 +177,7 @@ export class BedrockChatModel extends BaseChatModel<BedrockChatModelCallOptions>
|
|||
return tools.map((tool) => {
|
||||
let inputSchema: Record<string, unknown> = { type: "object", properties: {} };
|
||||
if (tool.schema) {
|
||||
// Use LangChain's schema conversion utilities. The result is cast via `unknown`
|
||||
// because `toJsonSchema` returns a `JsonSchema7Type` union without an index
|
||||
// signature, which TypeScript cannot directly assign to `Record<string, unknown>`.
|
||||
const converted: unknown = isInteropZodSchema(tool.schema)
|
||||
? toJsonSchema(tool.schema)
|
||||
: tool.schema;
|
||||
inputSchema = converted as Record<string, unknown>;
|
||||
inputSchema = isInteropZodSchema(tool.schema) ? toJsonSchema(tool.schema) : tool.schema;
|
||||
}
|
||||
return {
|
||||
name: tool.name,
|
||||
|
|
@ -1479,12 +1473,19 @@ export class BedrockChatModel extends BaseChatModel<BedrockChatModelCallOptions>
|
|||
// Handle thinking mode for Claude models
|
||||
// Only enable if user has explicitly enabled REASONING capability for this model
|
||||
if (this.enableThinking) {
|
||||
// Enable thinking mode for Claude models on Bedrock
|
||||
// This allows the model to generate reasoning tokens
|
||||
payload.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: 2048,
|
||||
};
|
||||
// claude-opus-4-7+ rejects { type: "enabled", budget_tokens } with a 400 and requires
|
||||
// { type: "adaptive" }. Unanchored match because Bedrock IDs include provider/profile
|
||||
// prefixes (e.g. "global.anthropic.claude-opus-4-7-20260115-v1:0"). Constrain the minor
|
||||
// to 1-2 digits followed by a delimiter so dated snapshot IDs like
|
||||
// "claude-opus-4-20250514-v1:0" aren't misread as Opus 4.20250514.
|
||||
const opusMinorMatch = this.modelName.match(/claude-opus-4-(\d{1,2})(?:[-.]|$)/);
|
||||
const usesAdaptiveThinking = opusMinorMatch ? parseInt(opusMinorMatch[1], 10) >= 7 : false;
|
||||
// Opus 4.7+ defaults thinking.display to "omitted" so thinking summaries
|
||||
// never reach the UI; force "summarized" for the adaptive branch. Pre-4.7
|
||||
// models default to "summarized" server-side.
|
||||
payload.thinking = usesAdaptiveThinking
|
||||
? { type: "adaptive", display: "summarized" }
|
||||
: { type: "enabled", budget_tokens: 2048 };
|
||||
// When thinking is enabled, temperature must be 1
|
||||
// https://docs.claude.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking
|
||||
payload.temperature = 1;
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ export default class ChatModelManager {
|
|||
|
||||
const modelName = customModel.name;
|
||||
const modelInfo = getModelInfo(modelName);
|
||||
const { isThinkingEnabled } = modelInfo;
|
||||
const { isThinkingEnabled, usesAdaptiveThinking } = modelInfo;
|
||||
const resolvedTemperature = this.getTemperatureForModel(modelInfo, customModel, settings);
|
||||
const maxTokens = customModel.maxTokens ?? settings.maxTokens;
|
||||
|
||||
|
|
@ -248,10 +248,15 @@ export default class ChatModelManager {
|
|||
fetch: customModel.enableCors ? safeFetch : undefined,
|
||||
},
|
||||
...(isThinkingEnabled && {
|
||||
thinking: {
|
||||
type: "enabled",
|
||||
budget_tokens: ChatModelManager.ANTHROPIC_THINKING_BUDGET_TOKENS,
|
||||
},
|
||||
// Opus 4.7+ defaults thinking.display to "omitted" so thinking summaries
|
||||
// never reach the UI; force "summarized" for the adaptive branch. Pre-4.7
|
||||
// models default to "summarized" server-side and don't need this.
|
||||
thinking: usesAdaptiveThinking
|
||||
? { type: "adaptive" as const, display: "summarized" as const }
|
||||
: {
|
||||
type: "enabled" as const,
|
||||
budget_tokens: ChatModelManager.ANTHROPIC_THINKING_BUDGET_TOKENS,
|
||||
},
|
||||
}),
|
||||
},
|
||||
[ChatModelProviders.AZURE_OPENAI]: await (async (): Promise<Record<string, unknown>> => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
extractNoteFiles,
|
||||
extractTemplateNoteFiles,
|
||||
formatDateTime,
|
||||
getModelInfo,
|
||||
getNotesFromPath,
|
||||
getNotesFromTags,
|
||||
getUtf8ByteLength,
|
||||
|
|
@ -941,3 +942,46 @@ describe("stringToFormattedDateTime", () => {
|
|||
expect(result.epoch).toBeLessThanOrEqual(after);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getModelInfo", () => {
|
||||
it("flags claude-opus-4-7 as adaptive thinking", () => {
|
||||
const info = getModelInfo("claude-opus-4-7");
|
||||
expect(info.isThinkingEnabled).toBe(true);
|
||||
expect(info.usesAdaptiveThinking).toBe(true);
|
||||
});
|
||||
|
||||
it("flags claude-opus-4-8 and higher as adaptive thinking", () => {
|
||||
expect(getModelInfo("claude-opus-4-8").usesAdaptiveThinking).toBe(true);
|
||||
expect(getModelInfo("claude-opus-4-12").usesAdaptiveThinking).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps claude-opus-4-6 and earlier on legacy thinking", () => {
|
||||
const six = getModelInfo("claude-opus-4-6");
|
||||
expect(six.isThinkingEnabled).toBe(true);
|
||||
expect(six.usesAdaptiveThinking).toBe(false);
|
||||
|
||||
const zero = getModelInfo("claude-opus-4-0");
|
||||
expect(zero.isThinkingEnabled).toBe(true);
|
||||
expect(zero.usesAdaptiveThinking).toBe(false);
|
||||
});
|
||||
|
||||
it("does not affect other thinking-enabled families", () => {
|
||||
expect(getModelInfo("claude-sonnet-4-5").usesAdaptiveThinking).toBe(false);
|
||||
expect(getModelInfo("claude-3-7-sonnet-20250219").usesAdaptiveThinking).toBe(false);
|
||||
});
|
||||
|
||||
it("does not match unversioned claude-opus-4 prefix", () => {
|
||||
const bare = getModelInfo("claude-opus-4");
|
||||
expect(bare.isThinkingEnabled).toBe(true);
|
||||
expect(bare.usesAdaptiveThinking).toBe(false);
|
||||
});
|
||||
|
||||
it("does not treat dated snapshot IDs as adaptive thinking minors", () => {
|
||||
// claude-opus-4-20250514 is the dated snapshot of Opus 4.0, not Opus 4.20250514.
|
||||
expect(getModelInfo("claude-opus-4-20250514").usesAdaptiveThinking).toBe(false);
|
||||
// claude-opus-4-1-20250805 is dated 4.1.
|
||||
expect(getModelInfo("claude-opus-4-1-20250805").usesAdaptiveThinking).toBe(false);
|
||||
// Dated 4.7 still matches because the minor is delimited by "-".
|
||||
expect(getModelInfo("claude-opus-4-7-20260115").usesAdaptiveThinking).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1063,6 +1063,7 @@ export interface ModelInfo {
|
|||
isOSeries: boolean;
|
||||
isGPT5: boolean;
|
||||
isThinkingEnabled: boolean;
|
||||
usesAdaptiveThinking: boolean;
|
||||
}
|
||||
|
||||
export function getModelInfo(model: BaseChatModel | string): ModelInfo {
|
||||
|
|
@ -1077,10 +1078,18 @@ export function getModelInfo(model: BaseChatModel | string): ModelInfo {
|
|||
modelName.startsWith("claude-sonnet-4") ||
|
||||
modelName.startsWith("claude-opus-4");
|
||||
|
||||
// claude-opus-4-7 and later reject the legacy { type: "enabled", budget_tokens } shape
|
||||
// with a 400 and require { type: "adaptive" }. Detect by minor version on the opus-4 line.
|
||||
// Constrain the minor to 1-2 digits followed by a delimiter or end-of-string so dated
|
||||
// snapshot IDs (e.g. "claude-opus-4-20250514") aren't misread as Opus 4.20250514.
|
||||
const opusMinorMatch = modelName.match(/^claude-opus-4-(\d{1,2})(?:[-.]|$)/);
|
||||
const usesAdaptiveThinking = opusMinorMatch ? parseInt(opusMinorMatch[1], 10) >= 7 : false;
|
||||
|
||||
return {
|
||||
isOSeries,
|
||||
isGPT5,
|
||||
isThinkingEnabled,
|
||||
usesAdaptiveThinking,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue