From 300fb115a17aa7030e80183e68b792defd1519bf Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Tue, 22 Sep 2026 12:31:44 -0700 Subject: [PATCH] feat: opus 5.5 (#3930) * feat: opus 5.5 * fix: bad typing --- .../providers/claude/ClaudeProvider.test.ts | 39 +++++++++++++++++++ .../providers/claude/ClaudeProvider.ts | 3 ++ .../ai-chat/providers/claude/models.ts | 37 +++++++++++++++--- .../services/profile/ProfileService.ts | 4 -- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.test.ts b/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.test.ts index 6f0051e71..c6ba39e5f 100644 --- a/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.test.ts +++ b/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.test.ts @@ -829,6 +829,29 @@ describe('ClaudeProvider.complete request shape', () => { expect('temperature' in args).toBe(false); }); + it('forwards reasoning_effort as adaptive thinking + output_config effort on opus 5.5', async () => { + const { provider } = makeProvider(); + messagesCreateMock.mockResolvedValueOnce(baseResponse); + + await withTestActor(() => + provider.complete({ + model: 'claude-opus-5-5', + messages: [{ role: 'user', content: 'hi' }], + reasoning_effort: 'low', + temperature: 0.5, + } as never), + ); + + const [args] = messagesCreateMock.mock.calls[0]!; + // Opus 5.5 rejects disabled thinking, `budget_tokens`, and sampling params. + expect(args.thinking).toEqual({ + type: 'adaptive', + display: 'summarized', + }); + expect(args.output_config).toEqual({ effort: 'low' }); + expect('temperature' in args).toBe(false); + }); + it('builds an enabled thinking budget from reasoning_effort on older Sonnet models', async () => { const { provider } = makeProvider(); messagesCreateMock.mockResolvedValueOnce(baseResponse); @@ -898,6 +921,22 @@ describe('ClaudeProvider model resolution', () => { ); }); + it('routes the bare claude-opus alias to opus 5.5 rather than opus 5', async () => { + const { provider } = makeProvider(); + messagesCreateMock.mockResolvedValueOnce(baseResponse); + + await withTestActor(() => + provider.complete({ + model: 'claude-opus', + messages: [{ role: 'user', content: 'hi' }], + }), + ); + + expect(messagesCreateMock.mock.calls[0]![0].model).toBe( + 'claude-opus-5-5', + ); + }); + it('falls back to the default model when given an unknown id', async () => { const { provider } = makeProvider(); messagesCreateMock.mockResolvedValueOnce(baseResponse); diff --git a/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.ts b/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.ts index 70406de31..8b61360ad 100644 --- a/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.ts +++ b/src/backend/drivers/ai-chat/providers/claude/ClaudeProvider.ts @@ -367,6 +367,7 @@ export class ClaudeProvider implements IChatProvider { 'claude-opus-4-7', 'claude-opus-4-8', 'claude-opus-5', + 'claude-opus-5-5', ].includes(modelUsed.id); const resolvedTemperature = omitsTemperature ? undefined @@ -377,6 +378,7 @@ export class ClaudeProvider implements IChatProvider { 'claude-fable-5-1', 'claude-fable-5', 'claude-sonnet-5', + 'claude-opus-5-5', 'claude-opus-5', 'claude-opus-4-8', 'claude-opus-4-7', @@ -812,6 +814,7 @@ export class ClaudeProvider implements IChatProvider { if ( modelId === 'claude-fable-5-1' || modelId === 'claude-fable-5' || + modelId === 'claude-opus-5-5' || modelId === 'claude-opus-5' || modelId === 'claude-opus-4-8' || modelId === 'claude-opus-4-7' diff --git a/src/backend/drivers/ai-chat/providers/claude/models.ts b/src/backend/drivers/ai-chat/providers/claude/models.ts index a54fc38cf..d43ef9007 100644 --- a/src/backend/drivers/ai-chat/providers/claude/models.ts +++ b/src/backend/drivers/ai-chat/providers/claude/models.ts @@ -102,6 +102,36 @@ export const CLAUDE_MODELS: IChatModel[] = [ context: 1000000, max_tokens: 64000, }, + { + puterId: 'anthropic:anthropic/claude-opus-5-5', + id: 'claude-opus-5-5', + modalities: { input: ['text', 'image', 'pdf'], output: ['text'] }, + open_weights: false, + tool_call: true, + knowledge: '2026-06', + release_date: '2026-09-22', + aliases: [ + 'claude-opus', + 'claude-opus-latest', + 'claude-opus-5-5-latest', + 'claude-opus-5.5', + 'anthropic/claude-opus-5-5', + ], + name: 'Claude Opus 5.5', + costs_currency: 'usd-cents', + input_cost_key: 'input_tokens', + output_cost_key: 'output_tokens', + costs: { + tokens: 1_000_000, + input_tokens: 400, + ephemeral_5m_input_tokens: 400 * 1.25, + ephemeral_1h_input_tokens: 400 * 2, + cache_read_input_tokens: 400 * 0.1, + output_tokens: 2000, + }, + context: 1000000, + max_tokens: 128000, + }, { puterId: 'anthropic:anthropic/claude-opus-5', id: 'claude-opus-5', @@ -110,12 +140,7 @@ export const CLAUDE_MODELS: IChatModel[] = [ tool_call: true, knowledge: '2026-05', release_date: '2026-07-24', - aliases: [ - 'claude-opus', - 'claude-opus-latest', - 'claude-opus-5-latest', - 'anthropic/claude-opus-5', - ], + aliases: ['claude-opus-5-latest', 'anthropic/claude-opus-5'], name: 'Claude Opus 5', costs_currency: 'usd-cents', input_cost_key: 'input_tokens', diff --git a/src/backend/services/profile/ProfileService.ts b/src/backend/services/profile/ProfileService.ts index 4300063b8..e0e018005 100644 --- a/src/backend/services/profile/ProfileService.ts +++ b/src/backend/services/profile/ProfileService.ts @@ -21,9 +21,7 @@ import { Readable } from 'node:stream'; import { makeActor } from '../../core/actor.js'; import { HttpError } from '../../core/http/HttpError.js'; import type { UserRow } from '../../stores/user/UserStore.js'; -import type { LayerInstances } from '../../types'; import { isUniqueViolation } from '../../util/dbError.js'; -import type { puterServices } from '../index'; import { actorHasSubscription } from '../metering/enforcement.js'; import { PuterService } from '../types.js'; @@ -123,8 +121,6 @@ const validateField = (field: ProfileField, value: unknown): string | null => { * hosting middleware asks before streaming any file of that site. */ export class ProfileService extends PuterService { - declare protected services: LayerInstances; - #ownerUserId: number | null = null; #dirReady: Promise | null = null;