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
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Caveat: if you run multiple git worktrees (e.g. conductor workspaces), only one
- `TEST_MODELS` - Run tests only for specific models (comma-separated list of `provider/model-id` pairs)
Example: `TEST_MODELS="openai/gpt-4o-mini,anthropic/claude-3-5-sonnet-20241022" pnpm test:e2e`
This is useful for quick testing as the full e2e suite can take too long with all models.
`TEST_MODELS` always overrides provider mappings marked with `test: "skip"`. For example, `TEST_MODELS="anthropic/claude-opus-4-6"` will include that Anthropic mapping even if it is skipped by default, so metadata-driven e2e assertions such as `reasoningOutput` still apply.
- `FULL_MODE` - Include free models in tests (default: only paid models)
- `LOG_MODE` - Enable detailed logging of responses

Expand Down
80 changes: 80 additions & 0 deletions packages/actions/src/prepare-request-body.adaptive.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, test } from "vitest";

import { prepareRequestBody } from "./prepare-request-body.js";

interface AdaptiveThinkingBody {
thinking?: { type: "adaptive" | "enabled"; budget_tokens?: number };
output_config?: {
effort?: "low" | "medium" | "high" | "xhigh" | "max";
};
}

// Regression test for adaptive thinking. Anthropic models with
// `reasoningMode: "adaptive"` (Opus 4.6/4.7/4.8) must build
// `thinking: { type: "adaptive" }` rather than the legacy
// `thinking: { type: "enabled", budget_tokens }` form when reasoning is
// requested, with depth conveyed via `output_config.effort`.

async function buildAnthropicBody(
internalModel: string,
opts: {
reasoning_effort?: "low" | "medium" | "high" | "xhigh";
reasoning_max_tokens?: number;
},
): Promise<AdaptiveThinkingBody> {
return (await prepareRequestBody(
"anthropic",
internalModel,
null,
internalModel,
[
{
role: "user",
content: "Explain why the sum of two even numbers is always even.",
},
],
false, // stream
undefined, // temperature
undefined, // max_tokens
undefined, // top_p
undefined, // frequency_penalty
undefined, // presence_penalty
undefined, // response_format
undefined, // tools
undefined, // tool_choice
opts.reasoning_effort, // reasoning_effort
true, // supportsReasoning
false, // isProd
20, // maxImageSizeMB
null, // userPlan
undefined, // sensitive_word_check
undefined, // image_config
undefined, // effort
undefined, // imageGenerations
undefined, // webSearchTool
opts.reasoning_max_tokens, // reasoning_max_tokens
)) as AdaptiveThinkingBody;
}

describe("prepareRequestBody - adaptive thinking (Opus 4.6/4.7/4.8)", () => {
for (const model of [
"claude-opus-4-6",
"claude-opus-4-7",
"claude-opus-4-8",
]) {
test(`${model} builds thinking: { type: "adaptive" } with effort`, async () => {
const body = await buildAnthropicBody(model, {
reasoning_effort: "high",
});
expect(body.thinking).toEqual({ type: "adaptive" });
expect(body.output_config?.effort).toBe("high");
});
}

test("Opus 4.6 routes reasoning.max_tokens to adaptive (drops budget_tokens)", async () => {
const body = await buildAnthropicBody("claude-opus-4-6", {
reasoning_max_tokens: 8000,
});
expect(body.thinking).toEqual({ type: "adaptive" });
});
});
9 changes: 6 additions & 3 deletions packages/models/src/models/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,8 @@ export const anthropicModels = [
contextSize: 1000000,
maxOutput: 128000,
reasoning: true,
reasoningMaxTokens: true,
reasoningMode: "adaptive",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark adaptive Opus 4.6 reasoning output as omitted

Adaptive thinking can skip emitting reasoning text, but leaving reasoningOutput undefined means the model metadata says reasoning output is expected by default (see ProviderModelMapping docs), and the gateway reasoning e2e checks use that flag. The existing Opus 4.7/4.8 adaptive mappings set reasoningOutput: "omit" for this reason; without the same setting here, 4.6 requests/tests that inspect the metadata can fail whenever the model chooses not to produce a thinking block.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve effort when enabling adaptive 4.6

For Anthropic/Vertex Opus 4.6 requests that combine reasoning_effort/effort with response_format: { type: "json_schema" }, this new adaptive mapping makes prepareRequestBody put the requested depth in requestBody.output_config.effort, but the later JSON-schema branch replaces output_config with only format. Before this change, 4.6 used thinking: { type: "enabled", budget_tokens } and did not depend on output_config.effort, so structured-output calls now silently lose the requested reasoning depth; merge the JSON-schema format into the existing output_config instead of overwriting it.

Useful? React with 👍 / 👎.

reasoningOutput: "omit",
streaming: true,
vision: true,
tools: true,
Expand All @@ -985,7 +986,8 @@ export const anthropicModels = [
contextSize: 1000000,
maxOutput: 128000,
reasoning: true,
reasoningMaxTokens: true,
reasoningMode: "adaptive",
reasoningOutput: "omit",
streaming: true,
vision: true,
tools: true,
Expand All @@ -1003,7 +1005,8 @@ export const anthropicModels = [
contextSize: 1000000,
maxOutput: 128000,
reasoning: true,
reasoningMaxTokens: true,
reasoningMode: "adaptive",
reasoningOutput: "omit",
streaming: true,
vision: true,
tools: true,
Expand Down
Loading