Skip to content
Draft
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
2 changes: 1 addition & 1 deletion examples/agents/56-rag-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const DOCUMENTS = [
text:
"Agent Configuration. Agents are defined with a name, model, instructions, " +
"and tools. The model field uses the format 'provider/model_name', e.g. " +
"'openai/gpt-4o' or 'anthropic/claude-sonnet-4-20250514'. Instructions can be " +
"'openai/gpt-4o' or 'anthropic/claude-sonnet-5'. Instructions can be " +
'a string or a PromptTemplate referencing a stored prompt. Tools can be ' +
'@tool-decorated Python functions, http_tool for REST APIs, mcp_tool for ' +
'MCP servers, or agent_tool to wrap another agent as a callable tool. ' +
Expand Down
2 changes: 1 addition & 1 deletion examples/agents/58-scatter-gather.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const searchKnowledgeBase = tool(

export const researcher = new Agent({
name: 'researcher',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a country analyst. You will be given the name of a country. ' +
'Use the search_knowledge_base tool ONCE to research that country, then ' +
Expand Down
4 changes: 2 additions & 2 deletions examples/agents/59-coding-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Agent, AgentRuntime } from '@io-orkes/conductor-javascript/agents';

export const qaTester = new Agent({
name: 'qa_tester',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a meticulous QA engineer. Review the code written by the ' +
'coder for correctness, edge cases, and bugs. Write and execute test ' +
Expand All @@ -36,7 +36,7 @@ export const qaTester = new Agent({

export const coder = new Agent({
name: 'coder',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are an expert Python developer. Write clean, well-structured ' +
'Python code to solve the given problem. Always execute your code to ' +
Expand Down
8 changes: 4 additions & 4 deletions examples/agents/60-github-coding-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ const qaTools = [readFile, listFiles];

export const githubAgent = new Agent({
name: 'github_agent',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a GitHub operations specialist. You handle all git and GitHub CLI interactions.\n\n' +
'IMPORTANT: Read the conversation history carefully. If the conversation already contains ' +
Expand All @@ -283,7 +283,7 @@ export const githubAgent = new Agent({

export const coderAgent = new Agent({
name: 'coder',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are an expert developer. Write clean, well-structured code.\n\n' +
'WHEN YOU RECEIVE A TASK:\n' +
Expand All @@ -303,7 +303,7 @@ export const coderAgent = new Agent({

export const qaTester = new Agent({
name: 'qa_tester',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a meticulous QA engineer. Review the code written by the coder.\n\n' +
'1. Use read_file to read the code that was written\n' +
Expand All @@ -323,7 +323,7 @@ export const qaTester = new Agent({

export const codingTeam = new Agent({
name: 'coding_team',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a coding team coordinator. Delegate the incoming request ' +
'to github_agent to get started. Call transfer_to_github_agent now.',
Expand Down
8 changes: 4 additions & 4 deletions examples/agents/60a-github-coding-agent-simple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const WORK_DIR = `/tmp/codingexamples-${randomBytes(4).toString('hex')}`;

export const githubAgent = new Agent({
name: 'github_agent',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a GitHub operations specialist.\n\n' +
`Repo: ${REPO}\nWork dir: ${WORK_DIR}\n\n` +
Expand All @@ -45,7 +45,7 @@ export const githubAgent = new Agent({

export const coder = new Agent({
name: 'coder',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are an expert developer.\n\n' +
`The repo is cloned at ${WORK_DIR}.\n\n` +
Expand All @@ -65,7 +65,7 @@ export const coder = new Agent({

export const qaTester = new Agent({
name: 'qa_tester',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a meticulous QA engineer.\n\n' +
`The repo is at ${WORK_DIR}. You can read files with:\n cat ${WORK_DIR}/src/main.py\n\n` +
Expand All @@ -83,7 +83,7 @@ export const qaTester = new Agent({

export const codingTeam = new Agent({
name: 'coding_team',
model: 'anthropic/claude-sonnet-4-20250514',
model: 'anthropic/claude-sonnet-5',
instructions:
'You are a coding team coordinator. Delegate to github_agent. ' +
'Call transfer_to_github_agent now.',
Expand Down
2 changes: 1 addition & 1 deletion examples/agents/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The `CONDUCTOR_AGENT_LLM_MODEL` variable uses the `provider/model-name` format.
| Provider | Model string | API key env var |
|----------|-------------|-----------------|
| OpenAI | `anthropic/claude-sonnet-4-6` (default) | `OPENAI_API_KEY` |
| Anthropic | `anthropic/claude-sonnet-4-20250514` | `ANTHROPIC_API_KEY` |
| Anthropic | `anthropic/claude-sonnet-5` | `ANTHROPIC_API_KEY` |
| Google Gemini | `google_gemini/gemini-2.0-flash` | `GOOGLE_GEMINI_API_KEY` |
| AWS Bedrock | `aws_bedrock/...` | AWS credentials |
| Azure OpenAI | `azure_openai/...` | Azure credentials |
Expand Down
2 changes: 1 addition & 1 deletion examples/agents/adk/35-rag-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const DOCUMENTS: Document[] = [
text:
"Agent Configuration. Agents are defined with a name, model, instructions, " +
"and tools. The model field uses the format 'provider/model_name', e.g. " +
"'openai/gpt-4o' or 'anthropic/claude-sonnet-4-20250514'. Instructions can be " +
"'openai/gpt-4o' or 'anthropic/claude-sonnet-5'. Instructions can be " +
'a string or a PromptTemplate referencing a stored prompt. Tools can be ' +
'@tool-decorated Python functions, http_tool for REST APIs, mcp_tool for ' +
'MCP servers, or agent_tool to wrap another agent as a callable tool. ' +
Expand Down
58 changes: 57 additions & 1 deletion src/agents/__tests__/serializer.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it, expect } from "@jest/globals";
import { z } from "zod";
import { AgentConfigSerializer } from "../serializer.js";
import { AgentConfigSerializer, budgetToEffort } from "../serializer.js";
import { Agent, PromptTemplate, scatterGather } from "../agent.js";
import type { CallbackHandler, TerminationCondition, HandoffCondition } from "../agent.js";
import {
Expand Down Expand Up @@ -592,6 +592,62 @@ describe("serializeAgent() — thinkingConfig", () => {
});
expect(config).not.toHaveProperty("thinkingBudgetTokens");
});

it("keeps legacy thinkingConfig for Sonnet 4.x models", () => {
const a = new Agent({
name: "test",
model: "anthropic/claude-sonnet-4-6",
thinkingBudgetTokens: 2048,
});
const config = serializer.serializeAgent(a);

expect(config.thinkingConfig).toEqual({ enabled: true, budgetTokens: 2048 });
expect(config).not.toHaveProperty("reasoningEffort");
});

it("translates budget to reasoningEffort for Claude Sonnet 5 (rejects legacy enabled shape)", () => {
const a = new Agent({
name: "test",
model: "anthropic/claude-sonnet-5",
thinkingBudgetTokens: 4096,
});
const config = serializer.serializeAgent(a);

expect(config).not.toHaveProperty("thinkingConfig");
expect(config.reasoningEffort).toBe("medium");
});

it("matches future Sonnet 5.x minors without a code change", () => {
const a = new Agent({
name: "test",
model: "anthropic/claude-sonnet-5-1",
thinkingBudgetTokens: 40_000,
});
const config = serializer.serializeAgent(a);

expect(config).not.toHaveProperty("thinkingConfig");
expect(config.reasoningEffort).toBe("xhigh");
});

it("does not override an explicit reasoningEffort on Sonnet 5", () => {
const a = new Agent({
name: "test",
model: "anthropic/claude-sonnet-5",
thinkingBudgetTokens: 4096,
reasoningEffort: "high",
});
const config = serializer.serializeAgent(a);

expect(config).not.toHaveProperty("thinkingConfig");
expect(config.reasoningEffort).toBe("high");
});

it("maps budget tiers to effort like the server does for Opus", () => {
expect(budgetToEffort(1024)).toBe("low");
expect(budgetToEffort(4096)).toBe("medium");
expect(budgetToEffort(16_384)).toBe("high");
expect(budgetToEffort(64_000)).toBe("xhigh");
});
});

// ── reasoningEffort / contextWindowBudget / maskedFields ──
Expand Down
54 changes: 49 additions & 5 deletions src/agents/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,27 @@ export class AgentConfigSerializer {
// includeContents
if (agent.includeContents) config.includeContents = agent.includeContents;

// thinkingBudgetTokens → thinkingConfig
// thinkingBudgetTokens → thinkingConfig.
//
// Exception: Claude Sonnet 5+ rejects the legacy Anthropic
// ``thinking.type: "enabled" + budget_tokens`` shape with HTTP 400
// (adaptive thinking replaced it), and the server's Anthropic provider
// (≤ 3.32.x) still renders thinkingConfig as that legacy shape for
// Sonnet-line models. Sonnet 5 runs adaptive thinking by default when no
// thinking param is sent, so for those models the budget is translated
// client-side into ``reasoningEffort`` (server → output_config.effort)
// instead of thinkingConfig — same tiers as the server uses for Opus.
if (agent.thinkingBudgetTokens !== undefined) {
config.thinkingConfig = {
enabled: true,
budgetTokens: agent.thinkingBudgetTokens,
};
if (requiresAdaptiveThinking(agent.model)) {
if (config.reasoningEffort === undefined) {
config.reasoningEffort = budgetToEffort(agent.thinkingBudgetTokens);
}
} else {
config.thinkingConfig = {
enabled: true,
budgetTokens: agent.thinkingBudgetTokens,
};
}
}

// requiredTools
Expand Down Expand Up @@ -528,3 +543,32 @@ export class AgentConfigSerializer {
return { taskName: `${agentName}_gate` };
}
}

/**
* Anthropic model lines that reject the legacy `thinking.type: "enabled"` +
* `budget_tokens` shape (HTTP 400) AND are mistranslated by the server's
* Anthropic provider (≤ 3.32.x), whose adaptive-model matrix covers the
* Opus / Fable / Mythos lines but not Sonnet 5. Matched as a substring so
* future Sonnet 5.x minors work without a code change; `sonnet-5` (not the
* bare line name) because Sonnet 4.6-and-earlier still accept enabled.
*/
const CLIENT_ADAPTIVE_LINES = ["sonnet-5"];

/** True if `model` needs the client-side adaptive-thinking translation. */
export function requiresAdaptiveThinking(model: string | undefined): boolean {
if (!model) return false;
const m = model.toLowerCase();
return CLIENT_ADAPTIVE_LINES.some((line) => m.includes(line));
}

/**
* Map a legacy thinking-token budget onto an adaptive `effort` tier. Same
* thresholds as the server's Anthropic provider uses for Opus
* (AnthropicChatModel.budgetToEffort): bigger budgets → more thorough thinking.
*/
export function budgetToEffort(budget: number): string {
if (budget < 4_000) return "low";
if (budget < 16_000) return "medium";
if (budget < 32_000) return "high";
return "xhigh";
}
Loading