From 58a0d34f5a8c9d122d4ca7bbfa53265313bcac77 Mon Sep 17 00:00:00 2001 From: Edwin Lim Date: Tue, 30 Jun 2026 12:34:47 -0700 Subject: [PATCH] sonnet-5 flag --- src/lib/__tests__/agent-interface.test.ts | 36 ++++++++++++++++++++++- src/lib/agent/agent-interface.ts | 19 +++++++++++- src/lib/constants.ts | 15 ++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/src/lib/__tests__/agent-interface.test.ts b/src/lib/__tests__/agent-interface.test.ts index 02ead3ce..830a6d18 100644 --- a/src/lib/__tests__/agent-interface.test.ts +++ b/src/lib/__tests__/agent-interface.test.ts @@ -5,9 +5,15 @@ import { runAgent, createStopHook, isWarlockDisabled, + resolveAgentModel, buildAuthErrorContext, } from '@lib/agent/agent-interface'; -import { WIZARD_WARLOCK_DISABLED_FLAG_KEY } from '@lib/constants'; +import { + DEFAULT_AGENT_MODEL, + SONNET_5_MODEL, + WIZARD_SONNET_5_FLAG_KEY, + WIZARD_WARLOCK_DISABLED_FLAG_KEY, +} from '@lib/constants'; import { AgentOutputSignals } from '@lib/agent/output-signals'; import type { WizardRunOptions } from '@utils/types'; import type { SpinnerHandle } from '@ui'; @@ -539,6 +545,34 @@ describe('isWarlockDisabled (kill switch)', () => { }); }); +describe('resolveAgentModel (sonnet-5 opt-in)', () => { + // Fail-safe: an unset or fetch-failed flag map must NOT shift everyone + // onto Sonnet 5. The default has to be the explicit DEFAULT_AGENT_MODEL. + it('stays on the default with no flags or an empty flag map', () => { + expect(resolveAgentModel()).toBe(DEFAULT_AGENT_MODEL); + expect(resolveAgentModel({})).toBe(DEFAULT_AGENT_MODEL); + }); + + it('stays on the default when the flag is anything other than "true"', () => { + expect(resolveAgentModel({ [WIZARD_SONNET_5_FLAG_KEY]: 'false' })).toBe( + DEFAULT_AGENT_MODEL, + ); + // Case-sensitive match: only the literal 'true' opts in. + expect(resolveAgentModel({ [WIZARD_SONNET_5_FLAG_KEY]: 'True' })).toBe( + DEFAULT_AGENT_MODEL, + ); + expect(resolveAgentModel({ 'some-other-flag': 'true' })).toBe( + DEFAULT_AGENT_MODEL, + ); + }); + + it('opts into Sonnet 5 when the flag resolves to "true"', () => { + expect(resolveAgentModel({ [WIZARD_SONNET_5_FLAG_KEY]: 'true' })).toBe( + SONNET_5_MODEL, + ); + }); +}); + describe('buildAuthErrorContext', () => { const GATEWAY = 'https://gateway.us.posthog.com/wizard'; let home: string; diff --git a/src/lib/agent/agent-interface.ts b/src/lib/agent/agent-interface.ts index 40fe979a..6c5154e6 100644 --- a/src/lib/agent/agent-interface.ts +++ b/src/lib/agent/agent-interface.ts @@ -15,9 +15,11 @@ import { WIZARD_REMARK_EVENT_NAME, POSTHOG_PROPERTY_HEADER_PREFIX, WIZARD_ORCHESTRATOR_FLAG_KEY, + WIZARD_SONNET_5_FLAG_KEY, WIZARD_USER_AGENT, WIZARD_WARLOCK_DISABLED_FLAG_KEY, DEFAULT_AGENT_MODEL, + SONNET_5_MODEL, } from '@lib/constants'; import { type AdditionalFeature, @@ -349,6 +351,21 @@ export function isOrchestratorEnabled( return flags[WIZARD_ORCHESTRATOR_FLAG_KEY] === 'true'; } +/** + * Pick the model the main agent runner should use this run. Defaults to + * `DEFAULT_AGENT_MODEL` (Sonnet 4.6); flips to `SONNET_5_MODEL` only when + * the `wizard-sonnet-5` flag resolves to the literal string 'true'. A + * missing flag, an empty flag map (the safe default returned when the flag + * fetch fails), or any other value stays on the default — Sonnet 5 must + * be an explicit opt-in so a flag-fetch failure can't shift everyone onto + * a different model. + */ +export function resolveAgentModel(flags: Record = {}): string { + return flags[WIZARD_SONNET_5_FLAG_KEY] === 'true' + ? SONNET_5_MODEL + : DEFAULT_AGENT_MODEL; +} + /** * Build env for the SDK subprocess: process.env plus ANTHROPIC_CUSTOM_HEADERS, which always * includes `x-posthog-use-bedrock-fallback: true` so the LLM gateway falls back to Bedrock on @@ -711,7 +728,7 @@ export async function initializeAgent( // Bare model IDs (no `anthropic/` prefix) so the LLM gateway's Bedrock // fallback can match map_to_bedrock_model()'s strict lookup. - const model = config.modelOverride ?? DEFAULT_AGENT_MODEL; + const model = config.modelOverride ?? resolveAgentModel(config.wizardFlags); const agentRunConfig: AgentRunConfig = { workingDirectory: config.workingDirectory, diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 152b8dbf..45f18f6f 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -12,6 +12,13 @@ import { VERSION } from './version'; */ export const DEFAULT_AGENT_MODEL = 'claude-sonnet-4-6'; +/** + * Newer Sonnet, opt-in via the `wizard-sonnet-5` feature flag (see + * `resolveAgentModel` in `agent-interface.ts`). Kept separate from + * `DEFAULT_AGENT_MODEL` so the off-by-default behavior is obvious. + */ +export const SONNET_5_MODEL = 'claude-sonnet-5'; + /** * Cheaper, faster model for mechanical agent work (e.g. repo classification * during source-map detection). Passed via AgentConfig.modelOverride. @@ -187,6 +194,14 @@ export const WIZARD_INTERACTION_EVENT_NAME = 'wizard interaction'; export const WIZARD_REMARK_EVENT_NAME = 'wizard remark'; /** Boolean feature flag that routes a run to the experimental orchestrator runner. */ export const WIZARD_ORCHESTRATOR_FLAG_KEY = 'wizard-orchestrator'; +/** + * Boolean feature flag that opts the main agent runner into Sonnet 5 + * (`SONNET_5_MODEL`). When false or missing, the main runner uses + * `DEFAULT_AGENT_MODEL` (Sonnet 4.6). Only the main runner reads this — the + * MCP suggested-prompts screen and the orchestrator task default stay on + * `DEFAULT_AGENT_MODEL` regardless. + */ +export const WIZARD_SONNET_5_FLAG_KEY = 'wizard-sonnet-5'; /** Feature flag key that gates the intro-screen "Tools" menu. */ export const WIZARD_TOOLS_MENU_FLAG_KEY = 'wizard-tools-menu'; /**