source dump of claude code
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}