source dump of claude code
at main 387 lines 12 kB view raw
1import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs' 2import type { UUID } from 'crypto' 3import type React from 'react' 4import type { PermissionResult } from '../entrypoints/agentSdkTypes.js' 5import type { Key } from '../ink.js' 6import type { PastedContent } from '../utils/config.js' 7import type { ImageDimensions } from '../utils/imageResizer.js' 8import type { TextHighlight } from '../utils/textHighlighting.js' 9import type { AgentId } from './ids.js' 10import type { AssistantMessage, MessageOrigin } from './message.js' 11 12/** 13 * Inline ghost text for mid-input command autocomplete 14 */ 15export type InlineGhostText = { 16 /** The ghost text to display (e.g., "mit" for /commit) */ 17 readonly text: string 18 /** The full command name (e.g., "commit") */ 19 readonly fullCommand: string 20 /** Position in the input where the ghost text should appear */ 21 readonly insertPosition: number 22} 23 24/** 25 * Base props for text input components 26 */ 27export type BaseTextInputProps = { 28 /** 29 * Optional callback for handling history navigation on up arrow at start of input 30 */ 31 readonly onHistoryUp?: () => void 32 33 /** 34 * Optional callback for handling history navigation on down arrow at end of input 35 */ 36 readonly onHistoryDown?: () => void 37 38 /** 39 * Text to display when `value` is empty. 40 */ 41 readonly placeholder?: string 42 43 /** 44 * Allow multi-line input via line ending with backslash (default: `true`) 45 */ 46 readonly multiline?: boolean 47 48 /** 49 * Listen to user's input. Useful in case there are multiple input components 50 * at the same time and input must be "routed" to a specific component. 51 */ 52 readonly focus?: boolean 53 54 /** 55 * Replace all chars and mask the value. Useful for password inputs. 56 */ 57 readonly mask?: string 58 59 /** 60 * Whether to show cursor and allow navigation inside text input with arrow keys. 61 */ 62 readonly showCursor?: boolean 63 64 /** 65 * Highlight pasted text 66 */ 67 readonly highlightPastedText?: boolean 68 69 /** 70 * Value to display in a text input. 71 */ 72 readonly value: string 73 74 /** 75 * Function to call when value updates. 76 */ 77 readonly onChange: (value: string) => void 78 79 /** 80 * Function to call when `Enter` is pressed, where first argument is a value of the input. 81 */ 82 readonly onSubmit?: (value: string) => void 83 84 /** 85 * Function to call when Ctrl+C is pressed to exit. 86 */ 87 readonly onExit?: () => void 88 89 /** 90 * Optional callback to show exit message 91 */ 92 readonly onExitMessage?: (show: boolean, key?: string) => void 93 94 /** 95 * Optional callback to show custom message 96 */ 97 // readonly onMessage?: (show: boolean, message?: string) => void 98 99 /** 100 * Optional callback to reset history position 101 */ 102 readonly onHistoryReset?: () => void 103 104 /** 105 * Optional callback when input is cleared (e.g., double-escape) 106 */ 107 readonly onClearInput?: () => void 108 109 /** 110 * Number of columns to wrap text at 111 */ 112 readonly columns: number 113 114 /** 115 * Maximum visible lines for the input viewport. When the wrapped input 116 * exceeds this many lines, only lines around the cursor are rendered. 117 */ 118 readonly maxVisibleLines?: number 119 120 /** 121 * Optional callback when an image is pasted 122 */ 123 readonly onImagePaste?: ( 124 base64Image: string, 125 mediaType?: string, 126 filename?: string, 127 dimensions?: ImageDimensions, 128 sourcePath?: string, 129 ) => void 130 131 /** 132 * Optional callback when a large text (over 800 chars) is pasted 133 */ 134 readonly onPaste?: (text: string) => void 135 136 /** 137 * Callback when the pasting state changes 138 */ 139 readonly onIsPastingChange?: (isPasting: boolean) => void 140 141 /** 142 * Whether to disable cursor movement for up/down arrow keys 143 */ 144 readonly disableCursorMovementForUpDownKeys?: boolean 145 146 /** 147 * Skip the text-level double-press escape handler. Set this when a 148 * keybinding context (e.g. Autocomplete) owns escape — the keybinding's 149 * stopImmediatePropagation can't shield the text input because child 150 * effects register useInput listeners before parent effects. 151 */ 152 readonly disableEscapeDoublePress?: boolean 153 154 /** 155 * The offset of the cursor within the text 156 */ 157 readonly cursorOffset: number 158 159 /** 160 * Callback to set the offset of the cursor 161 */ 162 onChangeCursorOffset: (offset: number) => void 163 164 /** 165 * Optional hint text to display after command input 166 * Used for showing available arguments for commands 167 */ 168 readonly argumentHint?: string 169 170 /** 171 * Optional callback for undo functionality 172 */ 173 readonly onUndo?: () => void 174 175 /** 176 * Whether to render the text with dim color 177 */ 178 readonly dimColor?: boolean 179 180 /** 181 * Optional text highlights for search results or other highlighting 182 */ 183 readonly highlights?: TextHighlight[] 184 185 /** 186 * Optional custom React element to render as placeholder. 187 * When provided, overrides the standard `placeholder` string rendering. 188 */ 189 readonly placeholderElement?: React.ReactNode 190 191 /** 192 * Optional inline ghost text for mid-input command autocomplete 193 */ 194 readonly inlineGhostText?: InlineGhostText 195 196 /** 197 * Optional filter applied to raw input before key routing. Return the 198 * (possibly transformed) input string; returning '' for a non-empty 199 * input drops the event. 200 */ 201 readonly inputFilter?: (input: string, key: Key) => string 202} 203 204/** 205 * Extended props for VimTextInput 206 */ 207export type VimTextInputProps = BaseTextInputProps & { 208 /** 209 * Initial vim mode to use 210 */ 211 readonly initialMode?: VimMode 212 213 /** 214 * Optional callback for mode changes 215 */ 216 readonly onModeChange?: (mode: VimMode) => void 217} 218 219/** 220 * Vim editor modes 221 */ 222export type VimMode = 'INSERT' | 'NORMAL' 223 224/** 225 * Common properties for input hook results 226 */ 227export type BaseInputState = { 228 onInput: (input: string, key: Key) => void 229 renderedValue: string 230 offset: number 231 setOffset: (offset: number) => void 232 /** Cursor line (0-indexed) within the rendered text, accounting for wrapping. */ 233 cursorLine: number 234 /** Cursor column (display-width) within the current line. */ 235 cursorColumn: number 236 /** Character offset in the full text where the viewport starts (0 when no windowing). */ 237 viewportCharOffset: number 238 /** Character offset in the full text where the viewport ends (text.length when no windowing). */ 239 viewportCharEnd: number 240 241 // For paste handling 242 isPasting?: boolean 243 pasteState?: { 244 chunks: string[] 245 timeoutId: ReturnType<typeof setTimeout> | null 246 } 247} 248 249/** 250 * State for text input 251 */ 252export type TextInputState = BaseInputState 253 254/** 255 * State for vim input with mode 256 */ 257export type VimInputState = BaseInputState & { 258 mode: VimMode 259 setMode: (mode: VimMode) => void 260} 261 262/** 263 * Input modes for the prompt 264 */ 265export type PromptInputMode = 266 | 'bash' 267 | 'prompt' 268 | 'orphaned-permission' 269 | 'task-notification' 270 271export type EditablePromptInputMode = Exclude< 272 PromptInputMode, 273 `${string}-notification` 274> 275 276/** 277 * Queue priority levels. Same semantics in both normal and proactive mode. 278 * 279 * - `now` — Interrupt and send immediately. Aborts any in-flight tool 280 * call (equivalent to Esc + send). Consumers (print.ts, 281 * REPL.tsx) subscribe to queue changes and abort when they 282 * see a 'now' command. 283 * - `next` — Mid-turn drain. Let the current tool call finish, then 284 * send this message between the tool result and the next API 285 * round-trip. Wakes an in-progress SleepTool call. 286 * - `later` — End-of-turn drain. Wait for the current turn to finish, 287 * then process as a new query. Wakes an in-progress SleepTool 288 * call (query.ts upgrades the drain threshold after sleep so 289 * the message is attached to the same turn). 290 * 291 * The SleepTool is only available in proactive mode, so "wakes SleepTool" 292 * is a no-op in normal mode. 293 */ 294export type QueuePriority = 'now' | 'next' | 'later' 295 296/** 297 * Queued command type 298 */ 299export type QueuedCommand = { 300 value: string | Array<ContentBlockParam> 301 mode: PromptInputMode 302 /** Defaults to the priority implied by `mode` when enqueued. */ 303 priority?: QueuePriority 304 uuid?: UUID 305 orphanedPermission?: OrphanedPermission 306 /** Raw pasted contents including images. Images are resized at execution time. */ 307 pastedContents?: Record<number, PastedContent> 308 /** 309 * The input string before [Pasted text #N] placeholders were expanded. 310 * Used for ultraplan keyword detection so pasted content containing the 311 * keyword does not trigger a CCR session. Falls back to `value` when 312 * unset (bridge/UDS/MCP sources have no paste expansion). 313 */ 314 preExpansionValue?: string 315 /** 316 * When true, the input is treated as plain text even if it starts with `/`. 317 * Used for remotely-received messages (e.g. bridge/CCR) that should not 318 * trigger local slash commands or skills. 319 */ 320 skipSlashCommands?: boolean 321 /** 322 * When true, slash commands are dispatched but filtered through 323 * isBridgeSafeCommand() — 'local-jsx' and terminal-only commands return 324 * a helpful error instead of executing. Set by the Remote Control bridge 325 * inbound path so mobile/web clients can run skills and benign commands 326 * without re-exposing the PR #19134 bug (/model popping the local picker). 327 */ 328 bridgeOrigin?: boolean 329 /** 330 * When true, the resulting UserMessage gets `isMeta: true` — hidden in the 331 * transcript UI but visible to the model. Used by system-generated prompts 332 * (proactive ticks, teammate messages, resource updates) that route through 333 * the queue instead of calling `onQuery` directly. 334 */ 335 isMeta?: boolean 336 /** 337 * Provenance of this command. Stamped onto the resulting UserMessage so the 338 * transcript records origin structurally (not just via XML tags in content). 339 * undefined = human (keyboard). 340 */ 341 origin?: MessageOrigin 342 /** 343 * Workload tag threaded through to cc_workload= in the billing-header 344 * attribution block. The queue is the async boundary between the cron 345 * scheduler firing and the turn actually running — a user prompt can slip 346 * in between — so the tag rides on the QueuedCommand itself and is only 347 * hoisted into bootstrap state when THIS command is dequeued. 348 */ 349 workload?: string 350 /** 351 * Agent that should receive this notification. Undefined = main thread. 352 * Subagents run in-process and share the module-level command queue; the 353 * drain gate in query.ts filters by this field so a subagent's background 354 * task notifications don't leak into the coordinator's context (PR #18453 355 * unified the queue but lost the isolation the dual-queue accidentally had). 356 */ 357 agentId?: AgentId 358} 359 360/** 361 * Type guard for image PastedContent with non-empty data. Empty-content 362 * images (e.g. from a 0-byte file drag) yield empty base64 strings that 363 * the API rejects with `image cannot be empty`. Use this at every site 364 * that converts PastedContent → ImageBlockParam so the filter and the 365 * ID list stay in sync. 366 */ 367export function isValidImagePaste(c: PastedContent): boolean { 368 return c.type === 'image' && c.content.length > 0 369} 370 371/** Extract image paste IDs from a QueuedCommand's pastedContents. */ 372export function getImagePasteIds( 373 pastedContents: Record<number, PastedContent> | undefined, 374): number[] | undefined { 375 if (!pastedContents) { 376 return undefined 377 } 378 const ids = Object.values(pastedContents) 379 .filter(isValidImagePaste) 380 .map(c => c.id) 381 return ids.length > 0 ? ids : undefined 382} 383 384export type OrphanedPermission = { 385 permissionResult: PermissionResult 386 assistantMessage: AssistantMessage 387}