/ utils / advisor.ts
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.`