mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-08 22:34:00 +00:00
fix(anthropic): resolve thinking as disabled when legacy budget is below 1024 (#101415)
* fix(anthropic): resolve thinking as disabled when legacy budget is below 1024 When adjustMaxTokensForThinking collapses the thinking budget below the Anthropic minimum (1024), option resolution now sets thinkingEnabled to false instead of always forcing it to true. This keeps every downstream consumer (payload, replay, temperature, tool-choice) consistent — they all see the same disabled state instead of an enabled flag with a missing or API-rejected thinking block. || → ?? in both builders is defensive: the resolution layer already prevents invalid budgets from reaching the builder through the normal path, but ?? preserves an explicit zero when the builder is called directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(anthropic): guard raw streamAnthropic builder path against sub-minimum budgets Add budget guards in both Anthropic payload builders so direct streamAnthropic and bundled-plugin callers (e.g. Mantle) that bypass option resolution also get the disabled-state rule instead of producing API-rejected { type: "enabled", budget_tokens: < 1024 } requests. Add proof-anthropic-thinking-budget.mts driving real production functions with terminal output and negative control. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix(anthropic): normalize legacy thinking budgets Co-authored-by: Pick-cat <huang.ting3@xydigit.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -214,6 +214,26 @@ describe("createMantleAnthropicStreamFn", () => {
|
||||
expect(streamOptions.effort).toBe("low");
|
||||
});
|
||||
|
||||
it("disables legacy thinking when the adjusted budget is below 1024", () => {
|
||||
const model = createTestModel({
|
||||
id: "anthropic.claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
reasoning: true,
|
||||
maxTokens: 1500,
|
||||
});
|
||||
const deps = createTestDeps();
|
||||
deps.stream.mockReturnValue({ kind: "anthropic-stream" } as never);
|
||||
|
||||
void createMantleAnthropicStreamFn(deps)(
|
||||
model,
|
||||
{ messages: [] },
|
||||
{ apiKey: "bedrock-bearer-token", reasoning: "low" },
|
||||
);
|
||||
|
||||
expect(firstStreamOptions(deps)).toMatchObject({ maxTokens: 1500, thinkingEnabled: false });
|
||||
expect(firstStreamOptions(deps)).not.toHaveProperty("thinkingBudgetTokens");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ reasoning: undefined, effort: "high" },
|
||||
{ reasoning: "off" as const, effort: "low" },
|
||||
|
||||
@@ -210,13 +210,18 @@ export function createMantleAnthropicStreamFn(deps?: {
|
||||
reasoning,
|
||||
options?.thinkingBudgets,
|
||||
);
|
||||
const adaptiveThinking = requiresClaudeMythosAdaptiveThinking(model);
|
||||
const thinkingEnabled = adaptiveThinking || adjusted.thinkingBudget >= 1024;
|
||||
return streamFn(model as Model<"anthropic-messages">, context, {
|
||||
...base,
|
||||
client: streamClient,
|
||||
maxTokens: adjusted.maxTokens,
|
||||
thinkingEnabled: true,
|
||||
...(requiresClaudeMythosAdaptiveThinking(model) ? { effort: reasoning } : {}),
|
||||
thinkingBudgetTokens: adjusted.thinkingBudget,
|
||||
thinkingEnabled,
|
||||
...(adaptiveThinking
|
||||
? { effort: reasoning }
|
||||
: thinkingEnabled
|
||||
? { thinkingBudgetTokens: adjusted.thinkingBudget }
|
||||
: {}),
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -204,6 +204,24 @@ describe("Bedrock thinking effort mapping", () => {
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ reasoning: "minimal" as const, maxTokens: 1024 },
|
||||
{ reasoning: "low" as const, maxTokens: 1500 },
|
||||
])(
|
||||
"disables legacy thinking when $reasoning exceeds the $maxTokens token cap",
|
||||
({ reasoning, maxTokens }) => {
|
||||
const model = bedrockModel({
|
||||
id: "anthropic.claude-haiku-4-5-v1:0",
|
||||
name: "Claude Haiku 4.5",
|
||||
maxTokens,
|
||||
});
|
||||
const options = testing.resolveSimpleBedrockOptions(model, { reasoning });
|
||||
|
||||
expect(options).toMatchObject({ maxTokens, reasoning: "off" });
|
||||
expect(testing.buildAdditionalModelRequestFields(model, options)).toBeUndefined();
|
||||
},
|
||||
);
|
||||
|
||||
it("uses the model maxTokens cap for adaptive Claude thinking requests", () => {
|
||||
const model = bedrockModel({
|
||||
id: "us.anthropic.claude-opus-4-8",
|
||||
|
||||
@@ -438,6 +438,14 @@ function resolveSimpleBedrockOptions(
|
||||
options.thinkingBudgets,
|
||||
);
|
||||
|
||||
if (adjusted.thinkingBudget < 1024) {
|
||||
return {
|
||||
...base,
|
||||
maxTokens: adjusted.maxTokens,
|
||||
reasoning: "off",
|
||||
} satisfies BedrockOptions;
|
||||
}
|
||||
|
||||
return {
|
||||
...base,
|
||||
maxTokens: adjusted.maxTokens,
|
||||
|
||||
@@ -371,6 +371,25 @@ describe("createAnthropicVertexStreamFn", () => {
|
||||
expect(transportOptions.effort).toBe("max");
|
||||
});
|
||||
|
||||
it("disables manual thinking when the configured budget is below 1024", () => {
|
||||
const { deps, streamAnthropicMock } = createStreamDeps();
|
||||
const streamFn = createAnthropicVertexStreamFn("vertex-project", "us-east5", undefined, deps);
|
||||
const model = makeModel({ id: "claude-haiku-4-5", maxTokens: 8192 });
|
||||
|
||||
void streamFn(
|
||||
model,
|
||||
{ messages: [] },
|
||||
{
|
||||
reasoning: "low",
|
||||
thinkingBudgets: { low: 512 },
|
||||
},
|
||||
);
|
||||
|
||||
const transportOptions = streamTransportOptions(streamAnthropicMock);
|
||||
expect(transportOptions.thinkingEnabled).toBe(false);
|
||||
expect(transportOptions).not.toHaveProperty("thinkingBudgetTokens");
|
||||
});
|
||||
|
||||
it("preserves native max reasoning for Sonnet 4.6", () => {
|
||||
const { deps, streamAnthropicMock } = createStreamDeps();
|
||||
const streamFn = createAnthropicVertexStreamFn("vertex-project", "us-east5", undefined, deps);
|
||||
|
||||
@@ -200,12 +200,17 @@ export function createAnthropicVertexStreamFn(
|
||||
contractModelId,
|
||||
) as AnthropicVertexEffort;
|
||||
} else {
|
||||
opts.thinkingEnabled = true;
|
||||
const budgets = options?.thinkingBudgets;
|
||||
opts.thinkingBudgetTokens =
|
||||
const thinkingBudgetTokens =
|
||||
(budgets && reasoning in budgets
|
||||
? budgets[reasoning as keyof typeof budgets]
|
||||
: undefined) ?? 10000;
|
||||
const requestMaxTokens = opts.maxTokens ?? transportModel.maxTokens;
|
||||
opts.thinkingEnabled =
|
||||
thinkingBudgetTokens >= 1024 && thinkingBudgetTokens < requestMaxTokens;
|
||||
if (opts.thinkingEnabled) {
|
||||
opts.thinkingBudgetTokens = thinkingBudgetTokens;
|
||||
}
|
||||
}
|
||||
} else if (mandatoryAdaptiveThinking) {
|
||||
opts.thinkingEnabled = true;
|
||||
|
||||
@@ -1648,6 +1648,98 @@ describe("Anthropic provider", () => {
|
||||
expect((capturedPayload as { output_config?: unknown }).output_config).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves thinking as disabled when the legacy budget collapses below 1024", async () => {
|
||||
// reasoning:true so the builder enters the thinking block, but an id that
|
||||
// does not match the adaptive-thinking regex so the budget-based path is used.
|
||||
const model = makeAnthropicModel({
|
||||
id: "claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
reasoning: true,
|
||||
maxTokens: 1024,
|
||||
});
|
||||
let capturedPayload: unknown;
|
||||
const stream = streamSimpleAnthropic(
|
||||
model,
|
||||
{ messages: [{ role: "user", content: "hello", timestamp: 0 }] },
|
||||
{
|
||||
apiKey: "sk-ant-provider",
|
||||
reasoning: "minimal",
|
||||
onPayload: (payload) => {
|
||||
capturedPayload = payload;
|
||||
throw new Error("stop before network");
|
||||
},
|
||||
},
|
||||
);
|
||||
await stream.result();
|
||||
expect((capturedPayload as { thinking?: unknown }).thinking).toEqual({ type: "disabled" });
|
||||
});
|
||||
|
||||
it("resolves thinking as disabled when the legacy budget is positive but sub-minimum", async () => {
|
||||
const model = makeAnthropicModel({
|
||||
id: "claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
reasoning: true,
|
||||
maxTokens: 1500,
|
||||
});
|
||||
let capturedPayload: unknown;
|
||||
const stream = streamSimpleAnthropic(
|
||||
model,
|
||||
{ messages: [{ role: "user", content: "hello", timestamp: 0 }] },
|
||||
{
|
||||
apiKey: "sk-ant-provider",
|
||||
reasoning: "low",
|
||||
onPayload: (payload) => {
|
||||
capturedPayload = payload;
|
||||
throw new Error("stop before network");
|
||||
},
|
||||
},
|
||||
);
|
||||
await stream.result();
|
||||
expect((capturedPayload as { thinking?: unknown }).thinking).toEqual({ type: "disabled" });
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ budgetTokens: 512, maxTokens: 8192 },
|
||||
{ budgetTokens: 1024, maxTokens: 1024 },
|
||||
])(
|
||||
"normalizes raw manual thinking budget $budgetTokens below max $maxTokens",
|
||||
async ({ budgetTokens, maxTokens }) => {
|
||||
const model = makeAnthropicModel({
|
||||
id: "claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
maxTokens: 8192,
|
||||
});
|
||||
let capturedPayload: unknown;
|
||||
const stream = streamAnthropic(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello", timestamp: 0 }],
|
||||
tools: [{ name: "lookup", description: "Lookup", parameters: { type: "object" } }],
|
||||
},
|
||||
{
|
||||
apiKey: "sk-ant-provider",
|
||||
maxTokens,
|
||||
temperature: 0.2,
|
||||
thinkingEnabled: true,
|
||||
thinkingBudgetTokens: budgetTokens,
|
||||
toolChoice: "any",
|
||||
onPayload: (payload) => {
|
||||
capturedPayload = payload;
|
||||
throw new Error("stop before network");
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
await stream.result();
|
||||
|
||||
expect(capturedPayload).toMatchObject({
|
||||
thinking: { type: "disabled" },
|
||||
temperature: 0.2,
|
||||
tool_choice: { type: "any" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["claude-opus-4-8", "claude-mythos-preview"])(
|
||||
"restores default sampling for %s after payload hooks",
|
||||
async (modelId) => {
|
||||
|
||||
@@ -231,6 +231,7 @@ export type AnthropicThinkingDisplay = "summarized" | "omitted";
|
||||
|
||||
const FINE_GRAINED_TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
||||
const INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
||||
const ANTHROPIC_MIN_THINKING_BUDGET_TOKENS = 1024;
|
||||
|
||||
function getAnthropicCompat(model: Model<"anthropic-messages">): Required<AnthropicMessagesCompat> {
|
||||
// Auto-detect session affinity and cache control support from provider
|
||||
@@ -504,6 +505,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
) => {
|
||||
const stream = new AssistantMessageEventStream();
|
||||
const requestContext = prepareClaudeSonnet5RequestContext(model, context);
|
||||
const requestOptions = normalizeAnthropicThinkingOptions(model, options);
|
||||
|
||||
void (async () => {
|
||||
const output: AssistantMessage = {
|
||||
@@ -527,7 +529,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
// to expose until the terminal stop reason is known.
|
||||
const refusalBuffer = usesClaudeStreamingRefusalContract(model)
|
||||
? createDeferredEventBuffer<AssistantMessageEvent>(stream, () =>
|
||||
notifyLlmRequestActivity(options?.signal),
|
||||
notifyLlmRequestActivity(requestOptions?.signal),
|
||||
)
|
||||
: undefined;
|
||||
const eventSink = refusalBuffer ?? stream;
|
||||
@@ -544,11 +546,11 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
// caller-owned headers.
|
||||
let serverSideFallback = false;
|
||||
|
||||
if (options?.client) {
|
||||
client = options.client;
|
||||
if (requestOptions?.client) {
|
||||
client = requestOptions.client;
|
||||
isOAuth = false;
|
||||
} else {
|
||||
const apiKey = options?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
||||
const apiKey = requestOptions?.apiKey ?? getEnvApiKey(model.provider) ?? "";
|
||||
|
||||
let copilotDynamicHeaders: Record<string, string> | undefined;
|
||||
if (model.provider === "github-copilot") {
|
||||
@@ -559,16 +561,16 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
});
|
||||
}
|
||||
|
||||
const cacheRetention = options?.cacheRetention ?? resolveCacheRetention();
|
||||
const cacheSessionId = cacheRetention === "none" ? undefined : options?.sessionId;
|
||||
const cacheRetention = requestOptions?.cacheRetention ?? resolveCacheRetention();
|
||||
const cacheSessionId = cacheRetention === "none" ? undefined : requestOptions?.sessionId;
|
||||
|
||||
const created = createClient(
|
||||
model,
|
||||
apiKey,
|
||||
options?.thinkingEnabled === true,
|
||||
options?.interleavedThinking ?? true,
|
||||
requestOptions?.thinkingEnabled === true,
|
||||
requestOptions?.interleavedThinking ?? true,
|
||||
shouldUseFineGrainedToolStreamingBeta(model, requestContext),
|
||||
options?.headers,
|
||||
requestOptions?.headers,
|
||||
copilotDynamicHeaders,
|
||||
cacheSessionId,
|
||||
);
|
||||
@@ -576,23 +578,31 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
isOAuth = created.isOAuthToken;
|
||||
serverSideFallback = created.serverSideFallback;
|
||||
}
|
||||
const builtParams = buildParams(model, requestContext, isOAuth, options, serverSideFallback);
|
||||
const builtParams = buildParams(
|
||||
model,
|
||||
requestContext,
|
||||
isOAuth,
|
||||
requestOptions,
|
||||
serverSideFallback,
|
||||
);
|
||||
let params = builtParams.params;
|
||||
const toolProjection = builtParams.toolProjection;
|
||||
const nextParams = await options?.onPayload?.(params, model);
|
||||
const nextParams = await requestOptions?.onPayload?.(params, model);
|
||||
if (nextParams !== undefined) {
|
||||
params = nextParams as MessageCreateParamsStreaming;
|
||||
}
|
||||
applyClaudeRequestContract(params as unknown as Record<string, unknown>, model);
|
||||
const requestOptions = {
|
||||
...(options?.signal ? { signal: options.signal } : {}),
|
||||
...(options?.timeoutMs !== undefined ? { timeout: options.timeoutMs } : {}),
|
||||
...(options?.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),
|
||||
const sdkRequestOptions = {
|
||||
...(requestOptions?.signal ? { signal: requestOptions.signal } : {}),
|
||||
...(requestOptions?.timeoutMs !== undefined ? { timeout: requestOptions.timeoutMs } : {}),
|
||||
...(requestOptions?.maxRetries !== undefined
|
||||
? { maxRetries: requestOptions.maxRetries }
|
||||
: {}),
|
||||
};
|
||||
const response = await client.messages
|
||||
.create({ ...params, stream: true }, requestOptions)
|
||||
.create({ ...params, stream: true }, sdkRequestOptions)
|
||||
.asResponse();
|
||||
await options?.onResponse?.(
|
||||
await requestOptions?.onResponse?.(
|
||||
{ status: response.status, headers: headersToRecord(response.headers) },
|
||||
model,
|
||||
);
|
||||
@@ -605,7 +615,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
|
||||
for await (const event of iterateAnthropicEvents(
|
||||
response,
|
||||
options?.signal,
|
||||
requestOptions?.signal,
|
||||
refusalBuffer !== undefined,
|
||||
)) {
|
||||
if (event.type === "message_start") {
|
||||
@@ -904,7 +914,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
}
|
||||
}
|
||||
|
||||
if (options?.signal?.aborted) {
|
||||
if (requestOptions?.signal?.aborted) {
|
||||
throw new Error("Request was aborted");
|
||||
}
|
||||
|
||||
@@ -925,7 +935,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
|
||||
refusalBuffer.discard();
|
||||
output.content = [];
|
||||
}
|
||||
output.stopReason = options?.signal?.aborted ? "aborted" : "error";
|
||||
output.stopReason = requestOptions?.signal?.aborted ? "aborted" : "error";
|
||||
output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
|
||||
stream.push({ type: "error", reason: output.stopReason, error: output });
|
||||
stream.end();
|
||||
@@ -955,6 +965,25 @@ function supportsAdaptiveThinking(model: Model<"anthropic-messages">): boolean {
|
||||
return supportsClaudeAdaptiveThinking(model);
|
||||
}
|
||||
|
||||
function normalizeAnthropicThinkingOptions(
|
||||
model: Model<"anthropic-messages">,
|
||||
options: AnthropicOptions | undefined,
|
||||
): AnthropicOptions | undefined {
|
||||
if (options?.thinkingEnabled !== true || supportsAdaptiveThinking(model)) {
|
||||
return options;
|
||||
}
|
||||
|
||||
const budgetTokens = options.thinkingBudgetTokens ?? ANTHROPIC_MIN_THINKING_BUDGET_TOKENS;
|
||||
const maxTokens = options.maxTokens ?? model.maxTokens;
|
||||
if (budgetTokens >= ANTHROPIC_MIN_THINKING_BUDGET_TOKENS && budgetTokens < maxTokens) {
|
||||
return options;
|
||||
}
|
||||
|
||||
// Manual thinking is one request-wide mode: replay, sampling, tool choice,
|
||||
// headers, and payload construction must all observe the disabled state.
|
||||
return { ...options, thinkingEnabled: false, thinkingBudgetTokens: undefined };
|
||||
}
|
||||
|
||||
function supportsNativeXhighEffort(model: Model<"anthropic-messages">): boolean {
|
||||
return supportsClaudeNativeXhighEffort(model);
|
||||
}
|
||||
@@ -1070,11 +1099,14 @@ export const streamSimpleAnthropic: StreamFunction<
|
||||
options?.thinkingBudgets,
|
||||
);
|
||||
|
||||
// Sub-minimum budgets (< 1024) resolve to thinking disabled so downstream
|
||||
// consumers (payload, replay, temperature, tool-choice) see consistent state.
|
||||
const thinkingEnabled = adjusted.thinkingBudget >= 1024;
|
||||
return streamAnthropic(model, context, {
|
||||
...base,
|
||||
maxTokens: adjusted.maxTokens,
|
||||
thinkingEnabled: true,
|
||||
thinkingBudgetTokens: adjusted.thinkingBudget,
|
||||
thinkingEnabled,
|
||||
thinkingBudgetTokens: thinkingEnabled ? adjusted.thinkingBudget : undefined,
|
||||
} satisfies AnthropicOptions);
|
||||
};
|
||||
|
||||
@@ -1349,10 +1381,10 @@ function buildParams(
|
||||
: { effort };
|
||||
}
|
||||
} else {
|
||||
// Budget-based thinking for older models
|
||||
// Budget-based thinking for older models.
|
||||
params.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: options?.thinkingBudgetTokens || 1024,
|
||||
budget_tokens: options?.thinkingBudgetTokens ?? ANTHROPIC_MIN_THINKING_BUDGET_TOKENS,
|
||||
display,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3349,6 +3349,54 @@ describe("anthropic transport stream", () => {
|
||||
expect(payload.output_config).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves thinking as disabled when the legacy budget collapses to zero", async () => {
|
||||
// reasoning:true so the builder enters the thinking block, but an id that
|
||||
// does not match the adaptive-thinking regex so the budget-based path is used.
|
||||
const model = makeAnthropicTransportModel({
|
||||
id: "claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
reasoning: true,
|
||||
maxTokens: 1024,
|
||||
});
|
||||
|
||||
await runTransportStream(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
apiKey: "test-token",
|
||||
reasoning: "minimal",
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
|
||||
const payload = latestAnthropicRequest().payload;
|
||||
expect(payload.thinking).toEqual({ type: "disabled" });
|
||||
});
|
||||
|
||||
it("resolves thinking as disabled when the legacy budget is positive but sub-minimum", async () => {
|
||||
const model = makeAnthropicTransportModel({
|
||||
id: "claude-haiku-4-5",
|
||||
name: "Claude Haiku 4.5",
|
||||
reasoning: true,
|
||||
maxTokens: 1500,
|
||||
});
|
||||
|
||||
await runTransportStream(
|
||||
model,
|
||||
{
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
} as AnthropicStreamContext,
|
||||
{
|
||||
apiKey: "test-token",
|
||||
reasoning: "low",
|
||||
} as AnthropicStreamOptions,
|
||||
);
|
||||
|
||||
const payload = latestAnthropicRequest().payload;
|
||||
expect(payload.thinking).toEqual({ type: "disabled" });
|
||||
});
|
||||
|
||||
it("honors provider effort restrictions for transport runs", async () => {
|
||||
const model = makeAnthropicTransportModel({
|
||||
id: "claude-opus-4.7-1m-internal",
|
||||
|
||||
@@ -1091,7 +1091,7 @@ function buildAnthropicParams(
|
||||
} else {
|
||||
params.thinking = {
|
||||
type: "enabled",
|
||||
budget_tokens: options?.thinkingBudgetTokens || 1024,
|
||||
budget_tokens: options?.thinkingBudgetTokens ?? 1024,
|
||||
};
|
||||
}
|
||||
} else if (options?.thinkingEnabled === false) {
|
||||
@@ -1178,9 +1178,12 @@ function resolveAnthropicTransportOptions(
|
||||
reasoningLevel: reasoning,
|
||||
customBudgets: options?.thinkingBudgets,
|
||||
});
|
||||
// Sub-minimum budgets (< 1024) resolve to thinking disabled so downstream
|
||||
// consumers (payload, replay, temperature, tool-choice) see consistent state.
|
||||
const thinkingEnabled = adjusted.thinkingBudget >= 1024;
|
||||
resolved.maxTokens = adjusted.maxTokens;
|
||||
resolved.thinkingEnabled = true;
|
||||
resolved.thinkingBudgetTokens = adjusted.thinkingBudget;
|
||||
resolved.thinkingEnabled = thinkingEnabled;
|
||||
resolved.thinkingBudgetTokens = thinkingEnabled ? adjusted.thinkingBudget : undefined;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user