/ src / utils / messages / systemInit.ts
systemInit.ts
 1  import { feature } from 'bun:bundle'
 2  import { randomUUID } from 'crypto'
 3  import { getSdkBetas, getSessionId } from 'src/bootstrap/state.js'
 4  import { DEFAULT_OUTPUT_STYLE_NAME } from 'src/constants/outputStyles.js'
 5  import type {
 6    ApiKeySource,
 7    PermissionMode,
 8    SDKMessage,
 9  } from 'src/entrypoints/agentSdkTypes.js'
10  import {
11    AGENT_TOOL_NAME,
12    LEGACY_AGENT_TOOL_NAME,
13  } from 'src/tools/AgentTool/constants.js'
14  import { getAnthropicApiKeyWithSource } from '../auth.js'
15  import { getCwd } from '../cwd.js'
16  import { getFastModeState } from '../fastMode.js'
17  import { getSettings_DEPRECATED } from '../settings/settings.js'
18  
19  // TODO(next-minor): remove this translation once SDK consumers have migrated
20  // to the 'Agent' tool name. The wire name was renamed Task → Agent in #19647,
21  // but emitting the new name in init/result events broke SDK consumers on a
22  // patch-level release. Keep emitting 'Task' until the next minor.
23  export function sdkCompatToolName(name: string): string {
24    return name === AGENT_TOOL_NAME ? LEGACY_AGENT_TOOL_NAME : name
25  }
26  
27  type CommandLike = { name: string; userInvocable?: boolean }
28  
29  export type SystemInitInputs = {
30    tools: ReadonlyArray<{ name: string }>
31    mcpClients: ReadonlyArray<{ name: string; type: string }>
32    model: string
33    permissionMode: PermissionMode
34    commands: ReadonlyArray<CommandLike>
35    agents: ReadonlyArray<{ agentType: string }>
36    skills: ReadonlyArray<CommandLike>
37    plugins: ReadonlyArray<{ name: string; path: string; source: string }>
38    fastMode: boolean | undefined
39  }
40  
41  /**
42   * Build the `system/init` SDKMessage — the first message on the SDK stream
43   * carrying session metadata (cwd, tools, model, commands, etc.) that remote
44   * clients use to render pickers and gate UI.
45   *
46   * Called from two paths that must produce identical shapes:
47   *   - QueryEngine (spawn-bridge / print-mode / SDK) — yielded as the first
48   *     stream message per query turn
49   *   - useReplBridge (REPL Remote Control) — sent via writeSdkMessages() on
50   *     bridge connect, since REPL uses query() directly and never hits the
51   *     QueryEngine SDKMessage layer
52   */
53  export function buildSystemInitMessage(inputs: SystemInitInputs): SDKMessage {
54    const settings = getSettings_DEPRECATED()
55    const outputStyle = settings?.outputStyle ?? DEFAULT_OUTPUT_STYLE_NAME
56  
57    const initMessage: SDKMessage = {
58      type: 'system',
59      subtype: 'init',
60      cwd: getCwd(),
61      session_id: getSessionId(),
62      tools: inputs.tools.map(tool => sdkCompatToolName(tool.name)),
63      mcp_servers: inputs.mcpClients.map(client => ({
64        name: client.name,
65        status: client.type,
66      })),
67      model: inputs.model,
68      permissionMode: inputs.permissionMode,
69      slash_commands: inputs.commands
70        .filter(c => c.userInvocable !== false)
71        .map(c => c.name),
72      apiKeySource: getAnthropicApiKeyWithSource().source as ApiKeySource,
73      betas: getSdkBetas(),
74      claude_code_version: MACRO.VERSION,
75      output_style: outputStyle,
76      agents: inputs.agents.map(agent => agent.agentType),
77      skills: inputs.skills
78        .filter(s => s.userInvocable !== false)
79        .map(skill => skill.name),
80      plugins: inputs.plugins.map(plugin => ({
81        name: plugin.name,
82        path: plugin.path,
83        source: plugin.source,
84      })),
85      uuid: randomUUID(),
86    }
87    // Hidden from public SDK types — ant-only UDS messaging socket path
88    if (feature('UDS_INBOX')) {
89      /* eslint-disable @typescript-eslint/no-require-imports */
90      ;(initMessage as Record<string, unknown>).messaging_socket_path =
91        require('../udsMessaging.js').getUdsMessagingSocketPath()
92      /* eslint-enable @typescript-eslint/no-require-imports */
93    }
94    initMessage.fast_mode_state = getFastModeState(inputs.model, inputs.fastMode)
95    return initMessage
96  }