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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ Use `resolveModelString()` from `@automaker/model-resolver` to convert model ali

- `haiku` → `claude-haiku-4-5`
- `sonnet` → `claude-sonnet-4-20250514`
- `opus` → `claude-opus-4-5-20251101`
- `opus` → `claude-opus-4-6`

## Environment Variables

Expand Down
4 changes: 2 additions & 2 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"test:unit": "vitest run tests/unit"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "0.1.76",
"@anthropic-ai/claude-agent-sdk": "0.2.32",
"@automaker/dependency-resolver": "1.0.0",
"@automaker/git-utils": "1.0.0",
"@automaker/model-resolver": "1.0.0",
Expand All @@ -34,7 +34,7 @@
"@automaker/utils": "1.0.0",
"@github/copilot-sdk": "^0.1.16",
"@modelcontextprotocol/sdk": "1.25.2",
"@openai/codex-sdk": "^0.77.0",
"@openai/codex-sdk": "^0.98.0",
"cookie-parser": "1.4.7",
"cors": "2.8.5",
"dotenv": "17.2.3",
Expand Down
18 changes: 17 additions & 1 deletion apps/server/src/lib/sdk-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,27 @@ function buildMcpOptions(config: CreateSdkOptionsConfig): McpOptions {
/**
* Build thinking options for SDK configuration.
* Converts ThinkingLevel to maxThinkingTokens for the Claude SDK.
* For adaptive thinking (Opus 4.6), omits maxThinkingTokens to let the model
* decide its own reasoning depth.
*
* @param thinkingLevel - The thinking level to convert
* @returns Object with maxThinkingTokens if thinking is enabled
* @returns Object with maxThinkingTokens if thinking is enabled with a budget
*/
function buildThinkingOptions(thinkingLevel?: ThinkingLevel): Partial<Options> {
if (!thinkingLevel || thinkingLevel === 'none') {
return {};
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Warning

Major - Logic: Adaptive and none thinking levels produce identical SDK configuration

When thinkingLevel is 'adaptive', buildThinkingOptions returns an empty object {} — the same as when thinking is disabled ('none'). This means 'adaptive' and 'none' produce identical SDK options. The PR description says adaptive thinking means 'just don't set maxThinkingTokens - model uses adaptive by default', which aligns with the implementation. However, there's no way for the SDK to distinguish between 'thinking off' and 'adaptive thinking'. If the SDK requires some indication (e.g., a different parameter) to enable adaptive thinking vs. simply disabling thinking, this could be a bug. Verify with the Claude Agent SDK 0.2.32 documentation that omitting maxThinkingTokens truly enables adaptive thinking rather than disabling thinking entirely.

Problematic code:

if (thinkingLevel === 'adaptive') {
    logger.debug(
      `buildThinkingOptions: thinkingLevel="adaptive" -> no maxThinkingTokens (model decides)`
    );
    return {};
  }

Suggested fix:

// Verify that the Claude Agent SDK 0.2.32 uses adaptive thinking by default
// when maxThinkingTokens is omitted. If the SDK needs an explicit signal,
// this should be updated. For example, if there's a `thinking` option:
//
// if (thinkingLevel === 'adaptive') {
//   return { thinking: 'adaptive' }; // or whatever the SDK expects
// }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Verified against the Claude Agent SDK docs — maxThinkingTokens defaults to undefined. For Opus 4.6, omitting it enables adaptive thinking (the model's default behavior). For other models, omitting it means no thinking.

The behavior is intentionally the same for both 'none' and 'adaptive' returning {} — the difference is at the UI level: Opus 4.6 only shows 'None' and 'Adaptive' options, so users can't accidentally set manual thinking budgets on a model that doesn't support them. When a user picks 'Adaptive', the empty return signals 'use the model default' which IS adaptive thinking for Opus 4.6.

That said, the 'none' case for Opus 4.6 does mean we can't explicitly disable thinking for that model through the SDK. This is a known SDK limitation — there's no thinking: false option. We could set maxThinkingTokens: 0 but it's unclear if the SDK supports that. Leaving as-is for now since the UI guides users correctly.


// Adaptive thinking (Opus 4.6): don't set maxThinkingTokens
// The model will use adaptive thinking by default
if (thinkingLevel === 'adaptive') {
logger.debug(
`buildThinkingOptions: thinkingLevel="adaptive" -> no maxThinkingTokens (model decides)`
);
return {};
}

// Manual budget-based thinking for Haiku/Sonnet
const maxThinkingTokens = getThinkingTokenBudget(thinkingLevel);
logger.debug(
`buildThinkingOptions: thinkingLevel="${thinkingLevel}" -> maxThinkingTokens=${maxThinkingTokens}`
Expand Down
17 changes: 10 additions & 7 deletions apps/server/src/providers/claude-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,11 @@ export class ClaudeProvider extends BaseProvider {
// claudeCompatibleProvider takes precedence over claudeApiProfile
const providerConfig = claudeCompatibleProvider || claudeApiProfile;

// Convert thinking level to token budget
const maxThinkingTokens = getThinkingTokenBudget(thinkingLevel);
// Build thinking configuration
// Adaptive thinking (Opus 4.6): don't set maxThinkingTokens, model uses adaptive by default
// Manual thinking (Haiku/Sonnet): use budget_tokens
const maxThinkingTokens =
thinkingLevel === 'adaptive' ? undefined : getThinkingTokenBudget(thinkingLevel);

// Build Claude SDK options
const sdkOptions: Options = {
Expand Down Expand Up @@ -349,13 +352,13 @@ export class ClaudeProvider extends BaseProvider {
getAvailableModels(): ModelDefinition[] {
const models = [
{
id: 'claude-opus-4-5-20251101',
name: 'Claude Opus 4.5',
modelString: 'claude-opus-4-5-20251101',
id: 'claude-opus-4-6',
name: 'Claude Opus 4.6',
modelString: 'claude-opus-4-6',
provider: 'anthropic',
description: 'Most capable Claude model',
description: 'Most capable Claude model with adaptive thinking',
contextWindow: 200000,
maxOutputTokens: 16000,
maxOutputTokens: 128000,
supportsVision: true,
supportsTools: true,
tier: 'premium' as const,
Expand Down
24 changes: 18 additions & 6 deletions apps/server/src/providers/codex-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,39 @@ const MAX_OUTPUT_16K = 16000;
*/
export const CODEX_MODELS: ModelDefinition[] = [
// ========== Recommended Codex Models ==========
{
id: CODEX_MODEL_MAP.gpt53Codex,
name: 'GPT-5.3-Codex',
modelString: CODEX_MODEL_MAP.gpt53Codex,
provider: 'openai',
description: 'Latest frontier agentic coding model.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
supportsTools: true,
tier: 'premium' as const,
default: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This correctly sets gpt53Codex as the new default model. However, there's a related constant in libs/types/src/model.ts that appears to have been missed. The DEFAULT_MODELS.codex is still set to CODEX_MODEL_MAP.gpt52Codex. Please update it to CODEX_MODEL_MAP.gpt53Codex for consistency across the codebase.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed — updated DEFAULT_MODELS.codex to CODEX_MODEL_MAP.gpt53Codex and updated the comment.

hasReasoning: true,
},
{
id: CODEX_MODEL_MAP.gpt52Codex,
name: 'GPT-5.2-Codex',
modelString: CODEX_MODEL_MAP.gpt52Codex,
provider: 'openai',
description:
'Most advanced agentic coding model for complex software engineering (default for ChatGPT users).',
description: 'Frontier agentic coding model.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
supportsTools: true,
tier: 'premium' as const,
default: true,
hasReasoning: true,
},
{
id: CODEX_MODEL_MAP.gpt51CodexMax,
name: 'GPT-5.1-Codex-Max',
modelString: CODEX_MODEL_MAP.gpt51CodexMax,
provider: 'openai',
description: 'Optimized for long-horizon, agentic coding tasks in Codex.',
description: 'Codex-optimized flagship for deep and fast reasoning.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
Expand All @@ -51,7 +63,7 @@ export const CODEX_MODELS: ModelDefinition[] = [
name: 'GPT-5.1-Codex-Mini',
modelString: CODEX_MODEL_MAP.gpt51CodexMini,
provider: 'openai',
description: 'Smaller, more cost-effective version for faster workflows.',
description: 'Optimized for codex. Cheaper, faster, but less capable.',
contextWindow: CONTEXT_WINDOW_128K,
maxOutputTokens: MAX_OUTPUT_16K,
supportsVision: true,
Expand All @@ -66,7 +78,7 @@ export const CODEX_MODELS: ModelDefinition[] = [
name: 'GPT-5.2',
modelString: CODEX_MODEL_MAP.gpt52,
provider: 'openai',
description: 'Best general agentic model for tasks across industries and domains.',
description: 'Latest frontier model with improvements across knowledge, reasoning and coding.',
contextWindow: CONTEXT_WINDOW_256K,
maxOutputTokens: MAX_OUTPUT_32K,
supportsVision: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/server/src/providers/provider-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class ProviderFactory {
/**
* Get the appropriate provider for a given model ID
*
* @param modelId Model identifier (e.g., "claude-opus-4-5-20251101", "cursor-gpt-4o", "cursor-auto")
* @param modelId Model identifier (e.g., "claude-opus-4-6", "cursor-gpt-4o", "cursor-auto")
* @param options Optional settings
* @param options.throwOnDisconnected Throw error if provider is disconnected (default: true)
* @returns Provider instance for the model
Expand Down
4 changes: 2 additions & 2 deletions apps/server/tests/unit/lib/model-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('model-resolver.ts', () => {

it("should resolve 'opus' alias to full model string", () => {
const result = resolveModelString('opus');
expect(result).toBe('claude-opus-4-5-20251101');
expect(result).toBe('claude-opus-4-6');
expect(consoleSpy.log).toHaveBeenCalledWith(
expect.stringContaining('Migrated legacy ID: "opus" -> "claude-opus"')
);
Expand Down Expand Up @@ -117,7 +117,7 @@ describe('model-resolver.ts', () => {
describe('getEffectiveModel', () => {
it('should prioritize explicit model over session and default', () => {
const result = getEffectiveModel('opus', 'haiku', 'gpt-5.2');
expect(result).toBe('claude-opus-4-5-20251101');
expect(result).toBe('claude-opus-4-6');
});

it('should use session model when explicit is not provided', () => {
Expand Down
24 changes: 24 additions & 0 deletions apps/server/tests/unit/lib/sdk-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,5 +491,29 @@ describe('sdk-options.ts', () => {
expect(options.maxThinkingTokens).toBeUndefined();
});
});

describe('adaptive thinking for Opus 4.6', () => {
it('should not set maxThinkingTokens for adaptive thinking (model decides)', async () => {
const { createAutoModeOptions } = await import('@/lib/sdk-options.js');

const options = createAutoModeOptions({
cwd: '/test/path',
thinkingLevel: 'adaptive',
});

expect(options.maxThinkingTokens).toBeUndefined();
});

it('should not include maxThinkingTokens when thinkingLevel is "none"', async () => {
const { createAutoModeOptions } = await import('@/lib/sdk-options.js');

const options = createAutoModeOptions({
cwd: '/test/path',
thinkingLevel: 'none',
});

expect(options.maxThinkingTokens).toBeUndefined();
});
});
});
});
32 changes: 16 additions & 16 deletions apps/server/tests/unit/providers/claude-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Hello',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -59,7 +59,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test prompt',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test/dir',
systemPrompt: 'You are helpful',
maxTurns: 10,
Expand All @@ -71,7 +71,7 @@ describe('claude-provider.ts', () => {
expect(sdk.query).toHaveBeenCalledWith({
prompt: 'Test prompt',
options: expect.objectContaining({
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
systemPrompt: 'You are helpful',
maxTurns: 10,
cwd: '/test/dir',
Expand All @@ -91,7 +91,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -116,7 +116,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
abortController,
});
Expand Down Expand Up @@ -145,7 +145,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Current message',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
conversationHistory,
sdkSessionId: 'test-session-id',
Expand Down Expand Up @@ -176,7 +176,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: arrayPrompt as any,
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -196,7 +196,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -222,7 +222,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand Down Expand Up @@ -286,7 +286,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -313,7 +313,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand Down Expand Up @@ -341,7 +341,7 @@ describe('claude-provider.ts', () => {

const generator = provider.executeQuery({
prompt: 'Test',
model: 'claude-opus-4-5-20251101',
model: 'claude-opus-4-6',
cwd: '/test',
});

Expand All @@ -366,12 +366,12 @@ describe('claude-provider.ts', () => {
expect(models).toHaveLength(4);
});

it('should include Claude Opus 4.5', () => {
it('should include Claude Opus 4.6', () => {
const models = provider.getAvailableModels();

const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
const opus = models.find((m) => m.id === 'claude-opus-4-6');
expect(opus).toBeDefined();
expect(opus?.name).toBe('Claude Opus 4.5');
expect(opus?.name).toBe('Claude Opus 4.6');
expect(opus?.provider).toBe('anthropic');
});

Expand Down Expand Up @@ -400,7 +400,7 @@ describe('claude-provider.ts', () => {
it('should mark Opus as default', () => {
const models = provider.getAvailableModels();

const opus = models.find((m) => m.id === 'claude-opus-4-5-20251101');
const opus = models.find((m) => m.id === 'claude-opus-4-6');
expect(opus?.default).toBe(true);
});

Expand Down
6 changes: 3 additions & 3 deletions apps/server/tests/unit/providers/provider-factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ describe('provider-factory.ts', () => {

describe('getProviderForModel', () => {
describe('Claude models (claude-* prefix)', () => {
it('should return ClaudeProvider for claude-opus-4-5-20251101', () => {
const provider = ProviderFactory.getProviderForModel('claude-opus-4-5-20251101');
it('should return ClaudeProvider for claude-opus-4-6', () => {
const provider = ProviderFactory.getProviderForModel('claude-opus-4-6');
expect(provider).toBeInstanceOf(ClaudeProvider);
});

Expand All @@ -70,7 +70,7 @@ describe('provider-factory.ts', () => {
});

it('should be case-insensitive for claude models', () => {
const provider = ProviderFactory.getProviderForModel('CLAUDE-OPUS-4-5-20251101');
const provider = ProviderFactory.getProviderForModel('CLAUDE-OPUS-4-6');
expect(provider).toBeInstanceOf(ClaudeProvider);
});
});
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/docs/AGENT_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ The agent is configured with:

```javascript
{
model: "claude-opus-4-5-20251101",
model: "claude-opus-4-6",
maxTurns: 20,
cwd: workingDirectory,
allowedTools: [
Expand Down
Loading