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
13 changes: 12 additions & 1 deletion desktop/src/constants/modelCatalog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,20 @@ describe('Claude official model catalog', () => {
expect(ids.indexOf('claude-sonnet-5-5')).toBeLessThan(ids.indexOf('claude-sonnet-5'))
})

it('offers Haiku 5.5 with its default effort and context ahead of Haiku 4.5', () => {
expect(OFFICIAL_MODELS.find(model => model.id === 'claude-haiku-5-5')).toMatchObject({
name: 'Haiku 5.5',
context: '1m',
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
})
const ids = OFFICIAL_MODELS.map(model => model.id)
expect(ids.indexOf('claude-haiku-5-5')).toBeLessThan(ids.indexOf('claude-haiku-4-5'))
})

it('keeps older explicit model selections available', () => {
expect(OFFICIAL_MODELS.map(model => model.id)).toEqual(expect.arrayContaining([
'claude-opus-5', 'claude-opus-4-8', 'claude-fable-5-1', 'claude-sonnet-5',
'claude-opus-5', 'claude-opus-4-8', 'claude-fable-5-1', 'claude-sonnet-5', 'claude-haiku-4-5',
]))
})
})
8 changes: 8 additions & 0 deletions desktop/src/constants/modelCatalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ export const OFFICIAL_MODELS: ModelInfo[] = [
defaultReasoningEffort: 'high',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-5-5',
name: 'Haiku 5.5',
description: 'Fastest for high-volume, latency-sensitive tasks',
context: '1m',
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-4-5',
name: 'Haiku 4.5',
Expand Down
8 changes: 4 additions & 4 deletions src/constants/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,11 @@ export const SYSTEM_PROMPT_DYNAMIC_BOUNDARY =

// @[MODEL LAUNCH]: Update the model family IDs below to the latest in each tier, and the
// matching names in the "most recent Claude models" sentence in computeSimpleEnvInfo.
// The Haiku ID is the pinned snapshot, as in the official Claude Code prompt.
const LATEST_CLAUDE_MODEL_IDS = {
fable: 'claude-fable-5-1',
opus: 'claude-opus-5-5',
sonnet: 'claude-sonnet-5-5',
haiku: 'claude-haiku-4-5-20251001',
haiku: 'claude-haiku-5-5',
}

function getHooksSection(): string {
Expand Down Expand Up @@ -693,7 +692,7 @@ export async function computeSimpleEnvInfo(
knowledgeCutoffMessage,
process.env.USER_TYPE === 'ant' && isUndercover()
? null
: `The most recent Claude models are the Claude 5 family and Haiku 4.5. Model IDs — Fable 5.1: '${LATEST_CLAUDE_MODEL_IDS.fable}', Opus 5.5: '${LATEST_CLAUDE_MODEL_IDS.opus}', Sonnet 5.5: '${LATEST_CLAUDE_MODEL_IDS.sonnet}', Haiku 4.5: '${LATEST_CLAUDE_MODEL_IDS.haiku}'. When building AI applications, default to the latest and most capable Claude models.`,
: `The most recent Claude models are the Claude 5 family. Model IDs — Fable 5.1: '${LATEST_CLAUDE_MODEL_IDS.fable}', Opus 5.5: '${LATEST_CLAUDE_MODEL_IDS.opus}', Sonnet 5.5: '${LATEST_CLAUDE_MODEL_IDS.sonnet}', Haiku 5.5: '${LATEST_CLAUDE_MODEL_IDS.haiku}'. When building AI applications, default to the latest and most capable Claude models.`,
process.env.USER_TYPE === 'ant' && isUndercover()
? null
: `Claude Code is available as a CLI in the terminal, desktop app (Mac/Windows), web app (claude.ai/code), and IDE extensions (VS Code, JetBrains).`,
Expand All @@ -717,7 +716,8 @@ function getKnowledgeCutoff(modelId: string): string | null {
if (
canonical.includes('claude-fable-5-1') ||
canonical.includes('claude-opus-5-5') ||
canonical.includes('claude-sonnet-5-5')
canonical.includes('claude-sonnet-5-5') ||
canonical.includes('claude-haiku-5-5')
) {
return 'June 2026'
} else if (canonical.includes('claude-opus-5')) {
Expand Down
3 changes: 2 additions & 1 deletion src/server/__tests__/e2e/business-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ describe('Business Flow: Models & Effort', () => {

it('should return available fallback models', async () => {
const { data } = await api('GET', '/api/models')
expect(data.models.length).toBe(8)
expect(data.models.length).toBe(9)
const names = data.models.map((m: any) => m.name)
expect(names).toContain('Fable 5.1')
expect(names).toContain('Fable 5')
Expand All @@ -384,6 +384,7 @@ describe('Business Flow: Models & Effort', () => {
expect(names).toContain('Opus 4.8')
expect(names).toContain('Sonnet 5.5')
expect(names).toContain('Sonnet 5')
expect(names).toContain('Haiku 5.5')
expect(names).toContain('Haiku 4.5')
})

Expand Down
2 changes: 1 addition & 1 deletion src/server/__tests__/e2e/full-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ describe('E2E: Full Flow', () => {

it('should list available models', async () => {
const { data } = await api('GET', '/api/models')
expect(data.models.length).toBe(8)
expect(data.models.length).toBe(9)
expect(data.models[0].name).toBe('Fable 5.1')
})

Expand Down
9 changes: 9 additions & 0 deletions src/server/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,14 @@ describe('Models API', () => {
defaultReasoningEffort: 'high',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-5-5',
name: 'Haiku 5.5',
description: 'Fastest for high-volume, latency-sensitive tasks',
context: '1m',
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-4-5',
name: 'Haiku 4.5',
Expand Down Expand Up @@ -1281,6 +1289,7 @@ describe('Models API', () => {
'claude-opus-4-8',
'claude-sonnet-5-5',
'claude-sonnet-5',
'claude-haiku-5-5',
'claude-haiku-4-5',
])
})
Expand Down
8 changes: 8 additions & 0 deletions src/server/api/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ const DEFAULT_MODELS = [
defaultReasoningEffort: 'high',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-5-5',
name: 'Haiku 5.5',
description: 'Fastest for high-volume, latency-sensitive tasks',
context: '1m',
defaultReasoningEffort: 'medium',
supportedReasoningEfforts: ['low', 'medium', 'high', 'xhigh', 'max'],
},
{
id: 'claude-haiku-4-5',
name: 'Haiku 4.5',
Expand Down
6 changes: 4 additions & 2 deletions src/server/services/claudeOfficialRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { hahaOAuthService } from './hahaOAuthService.js'

export const CLAUDE_OFFICIAL_OPUS_MODEL_ID = 'claude-opus-5-5'
export const CLAUDE_OFFICIAL_SONNET_MODEL_ID = 'claude-sonnet-5-5'
export const CLAUDE_OFFICIAL_HAIKU_MODEL_ID = 'claude-haiku-5-5'

export const CLAUDE_OFFICIAL_DEFAULT_MODELS: ModelMapping = {
main: CLAUDE_OFFICIAL_OPUS_MODEL_ID,
fable: 'claude-fable-5-1',
haiku: 'claude-haiku-4-5',
haiku: CLAUDE_OFFICIAL_HAIKU_MODEL_ID,
sonnet: CLAUDE_OFFICIAL_SONNET_MODEL_ID,
opus: CLAUDE_OFFICIAL_OPUS_MODEL_ID,
}
Expand All @@ -17,10 +18,11 @@ const CURRENT_MODEL_ALIASES = new Map<string, string>([
['fable', 'claude-fable-5'],
['opus', CLAUDE_OFFICIAL_OPUS_MODEL_ID],
['sonnet', CLAUDE_OFFICIAL_SONNET_MODEL_ID],
['haiku', 'claude-haiku-4-5'],
['haiku', CLAUDE_OFFICIAL_HAIKU_MODEL_ID],
['claude-fable-5', 'claude-fable-5'],
[CLAUDE_OFFICIAL_OPUS_MODEL_ID, CLAUDE_OFFICIAL_OPUS_MODEL_ID],
[CLAUDE_OFFICIAL_SONNET_MODEL_ID, CLAUDE_OFFICIAL_SONNET_MODEL_ID],
[CLAUDE_OFFICIAL_HAIKU_MODEL_ID, CLAUDE_OFFICIAL_HAIKU_MODEL_ID],
['claude-haiku-4-5', 'claude-haiku-4-5'],
])

Expand Down
3 changes: 2 additions & 1 deletion src/server/services/localIndex/sessionProjector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ import type {
// 9: usage cost rates were corrected (Sonnet 5, Sonnet 5.5, Opus 5.5, fast mode); rebuild the
// persisted per-model dollars.
// 10: project the explicit per-session ChatGPT OAuth Fast selection.
export const SESSION_SUMMARY_PARSER_VERSION = 10
// 11: Haiku 5.5 gained (prompt-length tiered) pricing; rebuild the persisted per-model dollars.
export const SESSION_SUMMARY_PARSER_VERSION = 11

export type SessionSourceCandidate = {
path: string
Expand Down
11 changes: 8 additions & 3 deletions src/services/api/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ import { endQueryProfile, queryCheckpoint } from "src/utils/queryProfiler.js";
import {
modelSupportsAdaptiveThinking,
modelUsesBoundThinking,
modelRejectsManualThinking,
modelRequiresThinking,
modelSupportsThinking,
resolveModelThinkingEnabled,
Expand Down Expand Up @@ -1741,8 +1742,11 @@ async function* queryModel(
retryContext?.maxTokensOverride ||
options.maxOutputTokensOverride ||
getMaxOutputTokensForModel(options.model)
// CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING only selects a manual budget, so it
// cannot apply to models that reject budget_tokens.
const usesAdaptiveThinking =
(modelRequiresThinking(options.model) ||
modelRejectsManualThinking(options.model) ||
!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING)) &&
modelSupportsAdaptiveThinking(options.model)
// Manual thinking needs at least 1024 tokens and room for the answer.
Expand Down Expand Up @@ -1778,11 +1782,12 @@ async function* queryModel(
// The installed SDK's effort type predates xhigh, which configureEffortParams supports.
const configuredEffort = outputConfig.effort as string | null | undefined

// Opus 5 rejects disabled thinking above high effort. Preserve the user's
// explicit thinking choice and use the highest compatible effort instead.
// Opus 5 and Haiku 5.5 reject disabled thinking above high effort. Preserve
// the user's explicit thinking choice and use the highest compatible effort.
const modelId = options.model.toLowerCase()
if (
sendsExplicitDisabledThinking &&
options.model.toLowerCase().includes('claude-opus-5') &&
(modelId.includes('claude-opus-5') || modelId.includes('claude-haiku-5-5')) &&
(configuredEffort === 'xhigh' || configuredEffort === 'max')
) {
outputConfig.effort = 'high'
Expand Down
109 changes: 109 additions & 0 deletions src/services/api/claudeRequiredThinking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -951,6 +951,21 @@ for (const effortValue of ['low', 'high', 'xhigh', 'max'] as const) {
}, 10_000)
}

for (const effortValue of ['medium', 'high', 'xhigh', 'max'] as const) {
test(`Haiku 5.5 caps ${effortValue} effort when thinking is disabled`, async () => {
const { requests } = await captureQueryRequest({
model: 'claude-haiku-5-5',
capabilities: 'thinking,effort,adaptive_thinking,xhigh_effort,max_effort',
effortValue,
})
expect(requests).toHaveLength(1)
expect(requests[0]?.thinking).toEqual({ type: 'disabled' })
expect(requests[0]?.output_config).toEqual({
effort: effortValue === 'xhigh' || effortValue === 'max' ? 'high' : effortValue,
})
}, 10_000)
}

test('Opus 5 preserves max effort with adaptive thinking enabled', async () => {
const { requests } = await captureQueryRequest({
model: 'claude-opus-5',
Expand Down Expand Up @@ -1014,6 +1029,100 @@ for (const disableAdaptive of [false, true]) {
}, 10_000)
}

test('Opus 5.5 keeps adaptive thinking on a provider when optional adaptive thinking is disabled', async () => {
// Generic provider capabilities omit required_thinking; a user-level
// CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING must not turn that into budget thinking.
const provider = {
id: 'provider-modelflare',
presetId: 'custom',
name: 'Modelflare',
apiKey: 'loopback-test-key',
authStrategy: 'auth_token',
baseUrl: 'https://provider.example.invalid',
apiFormat: 'anthropic',
runtimeKind: 'anthropic_compatible',
models: {
main: 'claude-opus-5-5[1m]',
haiku: 'claude-haiku-5-5[1m]',
sonnet: 'claude-sonnet-5-5[1m]',
opus: 'claude-opus-5-5[1m]',
},
} satisfies SavedProvider

const { content, requests } = await captureQueryRequest({
model: 'claude-opus-5-5[1m]',
provider,
configureCapabilityOverrides: false,
thinkingBudget: 2048,
env: { CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING: '1' },
responseFactory: (model, body) => {
if ((body.thinking as { type?: unknown } | undefined)?.type !== 'adaptive') {
return Response.json({
type: 'error',
error: {
type: 'invalid_request_error',
message: 'claude-opus-5-5 requires adaptive thinking; omit thinking or use thinking.type=adaptive and output_config.effort',
},
}, { status: 400 })
}
return new Response(successfulResponse(model), {
headers: { 'content-type': 'text/event-stream' },
})
},
})

expect(content).toEqual([{ type: 'text', text: 'OK' }])
expect(requests).toHaveLength(1)
expect(requests[0]?.thinking).toEqual({ type: 'adaptive' })
}, 10_000)

// Optional adaptive opt-out on a provider-managed environment. Claude models the
// API rejects budget_tokens for keep adaptive; every other model keeps the
// requested manual budget exactly as before.
const MANUAL_BUDGET = { type: 'enabled', budget_tokens: 2048 }
for (const [model, expectedThinking] of [
['claude-opus-5-5', { type: 'adaptive' }],
['claude-sonnet-5-5', { type: 'adaptive' }],
['claude-haiku-5-5', { type: 'adaptive' }],
['claude-fable-5-1', { type: 'adaptive' }],
['claude-opus-5', { type: 'adaptive' }],
['claude-sonnet-5', { type: 'adaptive' }],
['claude-opus-4-8', { type: 'adaptive' }],
['claude-opus-4-7', { type: 'adaptive' }],
['claude-opus-4-6', MANUAL_BUDGET],
['claude-sonnet-4-6', MANUAL_BUDGET],
['deepseek-v4-pro', MANUAL_BUDGET],
['minimax-m3', MANUAL_BUDGET],
['glm-5.3', MANUAL_BUDGET],
['kimi-k3', MANUAL_BUDGET],
['doubao-seed-2-0-pro', MANUAL_BUDGET],
['hunyuan-t1-latest', MANUAL_BUDGET],
] as const) {
test(`${model} thinking with optional adaptive disabled on a provider`, async () => {
const provider = {
id: 'provider-anthropic-compatible',
presetId: 'custom',
name: 'Anthropic compatible',
apiKey: 'loopback-test-key',
authStrategy: 'auth_token',
baseUrl: 'https://provider.example.invalid',
apiFormat: 'anthropic',
runtimeKind: 'anthropic_compatible',
models: { main: model, haiku: model, sonnet: model, opus: model },
} satisfies SavedProvider

const { requests } = await captureQueryRequest({
model,
provider,
configureCapabilityOverrides: false,
thinkingBudget: 2048,
env: { CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING: '1' },
})
expect(requests).toHaveLength(1)
expect(requests[0]?.thinking).toMatchObject(expectedThinking)
}, 10_000)
}

test('Opus 5.5 respects an explicit third-party capability opt-out', async () => {
const { requests } = await captureQueryRequest({
model: 'claude-opus-5-5',
Expand Down
2 changes: 2 additions & 0 deletions src/utils/betas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export function modelSupportsContextManagement(model: string): boolean {
canonical.includes('claude-sonnet-5') ||
canonical.includes('claude-opus-4') ||
canonical.includes('claude-sonnet-4') ||
canonical.includes('claude-haiku-5') ||
canonical.includes('claude-haiku-4')
)
}
Expand All @@ -175,6 +176,7 @@ export function modelSupportsStructuredOutputs(model: string): boolean {
canonical.includes('claude-opus-5') ||
canonical.includes('claude-opus-4-8') ||
canonical.includes('claude-opus-4-7') ||
canonical.includes('claude-haiku-5-5') ||
canonical.includes('claude-haiku-4-5')
)
}
Expand Down
1 change: 1 addition & 0 deletions src/utils/commitAttribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export function sanitizeModelName(shortName: string): string {
if (shortName.includes('sonnet-4-5')) return 'claude-sonnet-4-5'
if (shortName.includes('sonnet-4')) return 'claude-sonnet-4'
if (shortName.includes('sonnet-3-7')) return 'claude-sonnet-3-7'
if (shortName.includes('haiku-5-5')) return 'claude-haiku-5-5'
if (shortName.includes('haiku-4-5')) return 'claude-haiku-4-5'
if (shortName.includes('haiku-3-5')) return 'claude-haiku-3-5'
// Unknown models get a generic name
Expand Down
4 changes: 3 additions & 1 deletion src/utils/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export function modelSupports1M(model: string): boolean {
return (
canonical.includes('claude-fable-5') ||
canonical.includes('claude-sonnet-5') ||
canonical.includes('claude-haiku-5') ||
canonical.includes('claude-opus-5') ||
canonical.includes('claude-opus-4-8') ||
canonical.includes('claude-opus-4-7') ||
Expand Down Expand Up @@ -302,7 +303,8 @@ export function getModelMaxOutputTokens(model: string): {
m.includes('opus-4-8') ||
m.includes('opus-4-7') ||
m.includes('opus-4-6') ||
m.includes('sonnet-5')
m.includes('sonnet-5') ||
m.includes('haiku-5')
) {
defaultTokens = 64_000
upperLimit = 128_000
Expand Down
Loading
Loading