Skip to content
Open
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
36 changes: 35 additions & 1 deletion src/lib/__tests__/agent-interface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down
19 changes: 18 additions & 1 deletion src/lib/agent/agent-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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, string> = {}): 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
Expand Down Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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';
/**
Expand Down
Loading