advisor.ts
1 import type { BetaUsage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' 2 import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' 3 import { shouldIncludeFirstPartyOnlyBetas } from './betas.js' 4 import { isEnvTruthy } from './envUtils.js' 5 import { getInitialSettings } from './settings/settings.js' 6 7 // The SDK does not yet have types for advisor blocks. 8 // TODO(hackyon): Migrate to the real anthropic SDK types when this feature ships publicly 9 export type AdvisorServerToolUseBlock = { 10 type: 'server_tool_use' 11 id: string 12 name: 'advisor' 13 input: { [key: string]: unknown } 14 } 15 16 export type AdvisorToolResultBlock = { 17 type: 'advisor_tool_result' 18 tool_use_id: string 19 content: 20 | { 21 type: 'advisor_result' 22 text: string 23 } 24 | { 25 type: 'advisor_redacted_result' 26 encrypted_content: string 27 } 28 | { 29 type: 'advisor_tool_result_error' 30 error_code: string 31 } 32 } 33 34 export type AdvisorBlock = AdvisorServerToolUseBlock | AdvisorToolResultBlock 35 36 export function isAdvisorBlock(param: { 37 type: string 38 name?: string 39 }): param is AdvisorBlock { 40 return ( 41 param.type === 'advisor_tool_result' || 42 (param.type === 'server_tool_use' && param.name === 'advisor') 43 ) 44 } 45 46 type AdvisorConfig = { 47 enabled?: boolean 48 canUserConfigure?: boolean 49 baseModel?: string 50 advisorModel?: string 51 } 52 53 function getAdvisorConfig(): AdvisorConfig { 54 return getFeatureValue_CACHED_MAY_BE_STALE<AdvisorConfig>( 55 'tengu_sage_compass', 56 {}, 57 ) 58 } 59 60 export function isAdvisorEnabled(): boolean { 61 if (isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_ADVISOR_TOOL)) { 62 return false 63 } 64 // The advisor beta header is first-party only (Bedrock/Vertex 400 on it). 65 if (!shouldIncludeFirstPartyOnlyBetas()) { 66 return false 67 } 68 return getAdvisorConfig().enabled ?? false 69 } 70 71 export function canUserConfigureAdvisor(): boolean { 72 return isAdvisorEnabled() && (getAdvisorConfig().canUserConfigure ?? false) 73 } 74 75 export function getExperimentAdvisorModels(): 76 | { baseModel: string; advisorModel: string } 77 | undefined { 78 const config = getAdvisorConfig() 79 return isAdvisorEnabled() && 80 !canUserConfigureAdvisor() && 81 config.baseModel && 82 config.advisorModel 83 ? { baseModel: config.baseModel, advisorModel: config.advisorModel } 84 : undefined 85 } 86 87 // @[MODEL LAUNCH]: Add the new model if it supports the advisor tool. 88 // Checks whether the main loop model supports calling the advisor tool. 89 export function modelSupportsAdvisor(model: string): boolean { 90 const m = model.toLowerCase() 91 return ( 92 m.includes('opus-4-6') || 93 m.includes('sonnet-4-6') || 94 process.env.USER_TYPE === 'ant' 95 ) 96 } 97 98 // @[MODEL LAUNCH]: Add the new model if it can serve as an advisor model. 99 export function isValidAdvisorModel(model: string): boolean { 100 const m = model.toLowerCase() 101 return ( 102 m.includes('opus-4-6') || 103 m.includes('sonnet-4-6') || 104 process.env.USER_TYPE === 'ant' 105 ) 106 } 107 108 export function getInitialAdvisorSetting(): string | undefined { 109 if (!isAdvisorEnabled()) { 110 return undefined 111 } 112 return getInitialSettings().advisorModel 113 } 114 115 export function getAdvisorUsage( 116 usage: BetaUsage, 117 ): Array<BetaUsage & { model: string }> { 118 const iterations = usage.iterations as 119 | Array<{ type: string }> 120 | null 121 | undefined 122 if (!iterations) { 123 return [] 124 } 125 return iterations.filter( 126 it => it.type === 'advisor_message', 127 ) as unknown as Array<BetaUsage & { model: string }> 128 } 129 130 export const ADVISOR_TOOL_INSTRUCTIONS = `# Advisor Tool 131 132 You have access to an \`advisor\` tool backed by a stronger reviewer model. It takes NO parameters -- when you call it, your entire conversation history is automatically forwarded. The advisor sees the task, every tool call you've made, every result you've seen. 133 134 Call advisor BEFORE substantive work -- before writing code, before committing to an interpretation, before building on an assumption. If the task requires orientation first (finding files, reading code, seeing what's there), do that, then call advisor. Orientation is not substantive work. Writing, editing, and declaring an answer are. 135 136 Also call advisor: 137 - When you believe the task is complete. BEFORE this call, make your deliverable durable: write the file, stage the change, save the result. The advisor call takes time; if the session ends during it, a durable result persists and an unwritten one doesn't. 138 - When stuck -- errors recurring, approach not converging, results that don't fit. 139 - When considering a change of approach. 140 141 On tasks longer than a few steps, call advisor at least once before committing to an approach and once before declaring done. On short reactive tasks where the next action is dictated by tool output you just read, you don't need to keep calling -- the advisor adds most of its value on the first call, before the approach crystallizes. 142 143 Give the advice serious weight. If you follow a step and it fails empirically, or you have primary-source evidence that contradicts a specific claim (the file says X, the code does Y), adapt. A passing self-test is not evidence the advice is wrong -- it's evidence your test doesn't check what the advice is checking. 144 145 If you've already retrieved data pointing one way and the advisor points another: don't silently switch. Surface the conflict in one more advisor call -- "I found X, you suggest Y, which constraint breaks the tie?" The advisor saw your evidence but may have underweighted it; a reconcile call is cheaper than committing to the wrong branch.`