pi-ultracode 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,61 +9,211 @@
9
9
  * allowlist), and an alternate cwd for git-worktree isolation.
10
10
  */
11
11
 
12
+ import * as path from "node:path";
13
+ import * as PiCodingAgent from "@earendil-works/pi-coding-agent";
12
14
  import {
13
15
  createAgentSession,
14
16
  createCodingTools,
15
17
  getAgentDir,
16
18
  SessionManager,
17
19
  SettingsManager,
20
+ VERSION as PI_VERSION,
21
+ type CreateAgentSessionOptions,
18
22
  type ToolDefinition,
19
23
  } from "@earendil-works/pi-coding-agent";
20
24
  import type { AssistantMessage, TextContent } from "@earendil-works/pi-ai";
21
25
  import type { Static, TSchema } from "typebox";
22
26
  import { createStructuredOutputTool, type StructuredOutputCapture } from "./structured-output.ts";
27
+ import {
28
+ DISPLAY_INPUT_LIMIT,
29
+ displayOneLine,
30
+ redactCommand,
31
+ safeCommandPreview,
32
+ safeDisplayText,
33
+ safeTranscriptText,
34
+ truncateDisplay,
35
+ } from "./display-text.ts";
23
36
  import { jsonSchemaToTypeBox } from "./json-schema.ts";
37
+ import {
38
+ LEGACY_ULTRACODE_THINKING_LEVEL,
39
+ ULTRACODE_THINKING_LEVEL,
40
+ isThinkingLevel,
41
+ piVersionSupportsMaxThinking,
42
+ type ThinkingLevel,
43
+ } from "../thinking.ts";
24
44
  import type { AgentTypeDef } from "./agent-types.ts";
25
45
 
26
- export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
46
+ export type { ThinkingLevel } from "../thinking.ts";
27
47
 
28
48
  /** A minimal structural view of a Pi model (avoids importing the heavy generic type). */
29
49
  export interface ModelLike {
30
50
  provider: string;
31
51
  id: string;
32
52
  name?: string;
53
+ /** Extended levels are supported only when Pi exposes a non-null mapping. */
54
+ thinkingLevelMap?: Partial<Record<ThinkingLevel, string | null>>;
33
55
  }
34
56
 
35
57
  export interface ModelRegistryLike {
36
58
  getAvailable(): ModelLike[];
59
+ /** Legacy public projection retained for consumers of this structural type. */
37
60
  getAll?(): ModelLike[];
61
+ getRegisteredProviderIds?(): readonly string[];
62
+ getRegisteredProviderConfig?(provider: string): unknown;
63
+ getProviderAuthStatus?(provider: string): {
64
+ configured: boolean;
65
+ source?: string;
66
+ };
67
+ getApiKeyForProvider?(provider: string): Promise<string | undefined>;
68
+ }
69
+
70
+ /** Structural ModelRuntime seam that remains loadable on pre-0.80.8 Pi. */
71
+ export interface ModelRuntimeLike {
72
+ getModel?(provider: string, modelId: string): ModelLike | undefined;
73
+ registerProvider?(provider: string, config: any): void;
74
+ refresh?(options?: { allowNetwork?: boolean }): Promise<unknown>;
75
+ setRuntimeApiKey?(provider: string, apiKey: string): Promise<void>;
76
+ }
77
+
78
+ export interface ModelRuntimeCreateOptions {
79
+ authPath: string;
80
+ modelsPath: string;
81
+ /** Child sessions reuse the parent's catalog snapshot and must not refresh remotely. */
82
+ allowModelNetwork: false;
83
+ }
84
+
85
+ export type ModelRuntimeFactory = (
86
+ options: ModelRuntimeCreateOptions,
87
+ ) => Promise<ModelRuntimeLike | undefined>;
88
+
89
+ export interface AgentTurnUsage {
90
+ inputTokens: number;
91
+ outputTokens: number;
92
+ cacheReadTokens: number;
93
+ cacheWriteTokens: number;
94
+ totalTokens: number;
95
+ cost: number;
38
96
  }
39
97
 
40
98
  export interface AgentUsage {
99
+ /** Optional on injected legacy runners; production runners always provide it. */
100
+ inputTokens?: number;
41
101
  outputTokens: number;
102
+ /** Compact token use: input + output, excluding cache traffic. */
42
103
  totalTokens: number;
104
+ cacheReadTokens?: number;
105
+ cacheWriteTokens?: number;
43
106
  cost: number;
107
+ /** Completed assistant messages. */
108
+ turns?: number;
109
+ /** Tool executions that reached tool_execution_start. */
110
+ toolUses?: number;
111
+ retries?: number;
112
+ compactions?: number;
44
113
  }
45
114
 
46
115
  export interface AgentRunResult {
47
116
  value: unknown;
48
117
  usage: AgentUsage;
118
+ /** Model id and effort actually applied by the child session. */
119
+ modelId?: string;
120
+ effort?: ThinkingLevel;
49
121
  /** cwd the agent actually ran in (differs from the shared cwd under worktree isolation). */
50
122
  cwd: string;
51
123
  }
52
124
 
125
+ /** Detailed child-session events consumed by the private transcript store. */
126
+ export type AgentTelemetryEvent =
127
+ | { kind: "model_requested"; modelId?: string; effort?: ThinkingLevel }
128
+ | { kind: "model_resolved"; modelId?: string; effort: ThinkingLevel }
129
+ | { kind: "turn_start"; turnIndex?: number }
130
+ | { kind: "text_delta"; delta: string }
131
+ | { kind: "message_end"; text: string; usage?: AgentTurnUsage; error?: string }
132
+ | { kind: "thinking_start" | "thinking_end" }
133
+ | { kind: "tool_start"; toolCallId: string; toolName: string; toolArgs?: string }
134
+ | {
135
+ kind: "tool_end";
136
+ toolCallId: string;
137
+ toolName: string;
138
+ isError: boolean;
139
+ resultPreview?: string;
140
+ }
141
+ | { kind: "retry"; state: "start" | "end"; detail: string }
142
+ | { kind: "compaction"; state: "start" | "end"; detail: string }
143
+ | {
144
+ kind: "run_error";
145
+ error: string;
146
+ usage: AgentUsage;
147
+ modelId?: string;
148
+ effort?: ThinkingLevel;
149
+ };
150
+
151
+ export interface AgentSessionLike {
152
+ thinkingLevel: ThinkingLevel;
153
+ model?: ModelLike;
154
+ supportsThinking(): boolean;
155
+ prompt(
156
+ prompt: string,
157
+ options?: { preflightResult?: (success: boolean) => void },
158
+ ): Promise<void>;
159
+ abort(): Promise<void>;
160
+ subscribe(listener: (event: unknown) => void): () => void;
161
+ dispose(): void;
162
+ messages: unknown[];
163
+ getSessionStats?(): unknown;
164
+ }
165
+
166
+ export type AgentSessionCreateOptions = Omit<
167
+ CreateAgentSessionOptions,
168
+ "model" | "modelRegistry" | "modelRuntime"
169
+ > & {
170
+ model?: ModelLike;
171
+ /** Legacy Pi option, selected only when ModelRuntime is unavailable. */
172
+ modelRegistry?: ModelRegistryLike;
173
+ /** Pi 0.80.8+ canonical model/auth runtime. */
174
+ modelRuntime?: ModelRuntimeLike;
175
+ };
176
+
177
+ export type AgentSessionFactory = (
178
+ options: AgentSessionCreateOptions,
179
+ ) => Promise<{ session: AgentSessionLike }>;
180
+
53
181
  export interface WorkflowAgentRunnerOptions {
54
182
  cwd: string;
183
+ /** Synchronous extension facade used only for model selection and state replay. */
55
184
  modelRegistry?: ModelRegistryLike;
185
+ /** Canonical runtime to share across child sessions when supplied by an SDK host. */
186
+ modelRuntime?: ModelRuntimeLike;
56
187
  /** Default model used when an agent() call does not override it. */
57
188
  model?: ModelLike;
58
189
  /** Default thinking level for subagents. */
59
190
  thinkingLevel?: ThinkingLevel;
191
+ /** Test seam for session construction and initialization races. */
192
+ createSession?: AgentSessionFactory;
193
+ /** Test/compatibility seam for async ModelRuntime initialization. */
194
+ createModelRuntime?: ModelRuntimeFactory;
195
+ /** Override runtime feature detection for pre-max Pi compatibility tests. */
196
+ supportsMaxThinking?: boolean;
60
197
  }
61
198
 
62
- /** Normalized activity signal forwarded from inside a running subagent. */
63
- export interface AgentActivityInput {
64
- kind: "text" | "thinking" | "tool";
65
- detail?: string;
66
- }
199
+ /** Normalized, valid-by-construction activity signals from a running subagent. */
200
+ export type AgentActivityInput =
201
+ | { kind: "waiting" | "retry" | "compaction"; detail: string }
202
+ | { kind: "thinking"; detail: string }
203
+ | {
204
+ kind: "text";
205
+ detail: "responding";
206
+ /** @deprecated Standard runners never emit raw assistant text. */
207
+ streamDelta?: string;
208
+ }
209
+ | {
210
+ kind: "tool";
211
+ detail: string;
212
+ toolCallId: string;
213
+ toolName: string;
214
+ toolArgs?: string;
215
+ toolState: "start" | "update" | "end";
216
+ };
67
217
 
68
218
  export interface AgentRunCall {
69
219
  prompt: string;
@@ -76,21 +226,34 @@ export interface AgentRunCall {
76
226
  agentTypeDef?: AgentTypeDef;
77
227
  /** Override cwd (worktree). */
78
228
  cwd?: string;
79
- /** Live activity stream from the subagent (text deltas / tool calls). */
229
+ /** Safe compact activity stream used by the inline workflow status. */
80
230
  onActivity?: (event: AgentActivityInput) => void;
231
+ /** Private detailed stream used by the task transcript store. */
232
+ onTelemetry?: (event: AgentTelemetryEvent) => void;
81
233
  }
82
234
 
83
235
  export class WorkflowAgentRunner {
84
236
  private readonly baseCwd: string;
85
237
  private readonly modelRegistry?: ModelRegistryLike;
238
+ private readonly providedModelRuntime?: ModelRuntimeLike;
86
239
  private readonly defaultModel?: ModelLike;
87
240
  private readonly defaultThinking?: ThinkingLevel;
241
+ private readonly createSession: AgentSessionFactory;
242
+ private readonly createModelRuntime?: ModelRuntimeFactory;
243
+ private modelRuntimePromise?: Promise<ModelRuntimeLike | undefined>;
244
+ private readonly runtimeSupportsMaxThinking: boolean;
88
245
 
89
246
  constructor(options: WorkflowAgentRunnerOptions) {
90
247
  this.baseCwd = options.cwd;
91
248
  this.modelRegistry = options.modelRegistry;
249
+ this.providedModelRuntime = options.modelRuntime;
92
250
  this.defaultModel = options.model;
93
251
  this.defaultThinking = options.thinkingLevel;
252
+ const usesPiSessionFactory = options.createSession === undefined;
253
+ this.createSession = options.createSession ?? (createAgentSession as unknown as AgentSessionFactory);
254
+ this.createModelRuntime = options.createModelRuntime
255
+ ?? (options.modelRuntime !== undefined || !usesPiSessionFactory ? undefined : createPiModelRuntime);
256
+ this.runtimeSupportsMaxThinking = options.supportsMaxThinking ?? piVersionSupportsMaxThinking(PI_VERSION);
94
257
  }
95
258
 
96
259
  async run(call: AgentRunCall): Promise<AgentRunResult> {
@@ -113,39 +276,147 @@ export class WorkflowAgentRunner {
113
276
  if (toolAllowlist) toolAllowlist.push("structured_output");
114
277
  }
115
278
 
116
- const { model, thinkingLevel } = this.resolveModel(call.modelPattern, call.agentTypeDef);
117
-
279
+ const selection = this.resolveModel(call.modelPattern, call.agentTypeDef);
280
+ const thinkingLevel = selection.thinkingLevel;
118
281
  const agentDir = getAgentDir();
119
- const { session } = await createAgentSession({
282
+
283
+ let modelRuntime: ModelRuntimeLike | undefined;
284
+ let model: ModelLike | undefined;
285
+ try {
286
+ modelRuntime = await waitForSharedInitialization(
287
+ this.getModelRuntime(agentDir),
288
+ call.signal,
289
+ );
290
+ if (call.signal?.aborted) throw abortedError();
291
+ model = selection.model && modelRuntime?.getModel
292
+ ? modelRuntime.getModel(selection.model.provider, selection.model.id) ?? selection.model
293
+ : selection.model;
294
+ } catch (error) {
295
+ safeEmitTelemetry(call.onTelemetry, {
296
+ kind: "run_error",
297
+ error: safeDisplayText(errorText(error), 512),
298
+ usage: emptyAgentUsage(),
299
+ });
300
+ throw error;
301
+ }
302
+
303
+ const createSession = (level: ThinkingLevel | undefined) => this.createSession({
120
304
  cwd,
121
305
  agentDir,
122
306
  sessionManager: SessionManager.inMemory(cwd),
123
307
  settingsManager: SettingsManager.create(cwd, agentDir),
124
308
  customTools,
125
309
  ...(model ? { model: model as any } : {}),
126
- ...(thinkingLevel ? { thinkingLevel } : {}),
310
+ ...(level ? { thinkingLevel: level as any } : {}),
127
311
  ...(toolAllowlist ? { tools: toolAllowlist } : {}),
128
- ...(this.modelRegistry ? { modelRegistry: this.modelRegistry as any } : {}),
312
+ ...(modelRuntime
313
+ ? { modelRuntime }
314
+ : this.modelRegistry
315
+ ? { modelRegistry: this.modelRegistry as any }
316
+ : {}),
317
+ });
318
+
319
+ const sessionThinking = resolveSessionThinkingLevel(thinkingLevel, model);
320
+ safeEmitTelemetry(call.onTelemetry, {
321
+ kind: "model_requested",
322
+ modelId: model?.id,
323
+ effort: thinkingLevel,
324
+ });
325
+
326
+ let created: { session: AgentSessionLike };
327
+ try {
328
+ created = await createSession(sessionThinking);
329
+ if (call.signal?.aborted) {
330
+ disposeQuietly(created.session);
331
+ throw abortedError();
332
+ }
333
+ const selectedModel = created.session.model;
334
+ const selectedModelAdvertisesMax = (model ?? selectedModel)?.thinkingLevelMap?.max != null;
335
+ if (
336
+ thinkingLevel === ULTRACODE_THINKING_LEVEL &&
337
+ sessionThinking === ULTRACODE_THINKING_LEVEL &&
338
+ created.session.thinkingLevel !== ULTRACODE_THINKING_LEVEL &&
339
+ created.session.supportsThinking() &&
340
+ (!this.runtimeSupportsMaxThinking || selectedModelAdvertisesMax)
341
+ ) {
342
+ // A legacy runtime may clamp an unknown `max` to medium/high instead of
343
+ // off. Recreate with xhigh; never mutate the user's global default effort.
344
+ disposeQuietly(created.session);
345
+ created = await createSession(LEGACY_ULTRACODE_THINKING_LEVEL);
346
+ if (call.signal?.aborted) {
347
+ disposeQuietly(created.session);
348
+ throw abortedError();
349
+ }
350
+ }
351
+ } catch (error) {
352
+ safeEmitTelemetry(call.onTelemetry, {
353
+ kind: "run_error",
354
+ error: safeDisplayText(errorText(error), 512),
355
+ usage: emptyAgentUsage(),
356
+ });
357
+ throw error;
358
+ }
359
+ const { session } = created;
360
+ const actualModelId = session.model?.id ?? model?.id;
361
+ const actualEffort = session.thinkingLevel;
362
+ const telemetryCounters = { retries: 0, compactions: 0, turns: 0, toolUses: 0, observing: false };
363
+ safeEmitTelemetry(call.onTelemetry, {
364
+ kind: "model_resolved",
365
+ modelId: actualModelId,
366
+ effort: actualEffort,
129
367
  });
130
368
 
131
369
  let removeAbort: (() => void) | undefined;
132
370
  let unsubscribe: (() => void) | undefined;
371
+ let aborting: Promise<void> | undefined;
372
+ let hasPrimaryError = false;
133
373
  try {
134
374
  if (call.signal) {
135
- const onAbort = () => void session.abort();
375
+ const onAbort = () => {
376
+ // Attach the rejection handler synchronously so a failing abort can
377
+ // never become an unhandled rejection while prompt() is unwinding.
378
+ aborting = session.abort().catch(() => {});
379
+ };
136
380
  call.signal.addEventListener("abort", onAbort, { once: true });
137
381
  removeAbort = () => call.signal?.removeEventListener("abort", onAbort);
382
+ // AbortSignal does not replay an abort that happened before listener
383
+ // registration, so close the initialization race explicitly.
384
+ if (call.signal.aborted) {
385
+ onAbort();
386
+ await aborting;
387
+ throw abortedError();
388
+ }
138
389
  }
139
390
 
140
- // Forward live activity (text deltas / tool calls) so the workflow
141
- // snapshot can show per-agent progress and detect stuck subagents.
142
- if (call.onActivity) {
143
- const onActivity = call.onActivity;
144
- unsubscribe = session.subscribe((event) => forwardActivity(event, onActivity));
391
+ // One subscription feeds both compact status and the private transcript
392
+ // stream. Telemetry callbacks are isolated so observability can never
393
+ // change the child run's outcome.
394
+ if (call.onActivity || call.onTelemetry) {
395
+ telemetryCounters.observing = true;
396
+ unsubscribe = session.subscribe((event: unknown) => {
397
+ const sessionEvent = event as { type?: unknown; message?: { role?: unknown } };
398
+ const eventType = sessionEvent?.type;
399
+ if (eventType === "message_end" && sessionEvent.message?.role === "assistant") telemetryCounters.turns++;
400
+ if (eventType === "tool_execution_start") telemetryCounters.toolUses++;
401
+ if (eventType === "auto_retry_start") telemetryCounters.retries++;
402
+ if (eventType === "compaction_start") telemetryCounters.compactions++;
403
+ if (call.onActivity) forwardActivity(event, call.onActivity);
404
+ if (call.onTelemetry) forwardTelemetry(event, call.onTelemetry);
405
+ });
145
406
  }
146
407
 
147
- await session.prompt(this.buildPrompt(call, Boolean(call.schema)));
148
- if (call.signal?.aborted) throw new Error("Subagent was aborted");
408
+ await session.prompt(this.buildPrompt(call, Boolean(call.schema)), {
409
+ // Pi invokes this after async input/before_agent_start preflight and
410
+ // immediately before _runAgentPrompt(). Throwing here closes the window
411
+ // where abort() sees an idle session and therefore cannot stop streaming.
412
+ preflightResult: () => {
413
+ if (call.signal?.aborted) throw abortedError();
414
+ },
415
+ });
416
+ if (call.signal?.aborted) {
417
+ await aborting;
418
+ throw abortedError();
419
+ }
149
420
 
150
421
  let value: unknown;
151
422
  if (call.schema) {
@@ -155,11 +426,46 @@ export class WorkflowAgentRunner {
155
426
  value = lastAssistantText(session.messages as unknown[]);
156
427
  }
157
428
 
158
- return { value, usage: readUsage(session), cwd };
429
+ return {
430
+ value,
431
+ usage: readUsage(session, telemetryCounters),
432
+ modelId: actualModelId,
433
+ effort: actualEffort,
434
+ cwd,
435
+ };
436
+ } catch (error) {
437
+ hasPrimaryError = true;
438
+ safeEmitTelemetry(call.onTelemetry, {
439
+ kind: "run_error",
440
+ error: safeDisplayText(errorText(error), 512),
441
+ usage: readUsage(session, telemetryCounters),
442
+ modelId: actualModelId,
443
+ effort: actualEffort,
444
+ });
445
+ throw error;
159
446
  } finally {
160
- removeAbort?.();
161
- unsubscribe?.();
162
- session.dispose();
447
+ let hasCleanupError = false;
448
+ let cleanupError: unknown;
449
+ const cleanup = (fn: (() => void) | undefined) => {
450
+ if (!fn) return;
451
+ try {
452
+ fn();
453
+ } catch (error) {
454
+ if (!hasCleanupError) {
455
+ hasCleanupError = true;
456
+ cleanupError = error;
457
+ }
458
+ }
459
+ };
460
+ cleanup(removeAbort);
461
+ // If cancellation started while prompt() was failing, let AgentSession
462
+ // finish abort teardown before disposing resources underneath it.
463
+ await aborting;
464
+ cleanup(unsubscribe);
465
+ cleanup(() => session.dispose());
466
+ // Cleanup must always run to completion. Its first failure is surfaced only
467
+ // when there is no prompt/abort failure (including reject(undefined)).
468
+ if (!hasPrimaryError && hasCleanupError) throw cleanupError;
163
469
  }
164
470
  }
165
471
 
@@ -186,6 +492,61 @@ export class WorkflowAgentRunner {
186
492
  return parts.filter(Boolean).join("\n\n");
187
493
  }
188
494
 
495
+ private getModelRuntime(agentDir: string): Promise<ModelRuntimeLike | undefined> {
496
+ if (this.providedModelRuntime) return Promise.resolve(this.providedModelRuntime);
497
+ if (!this.createModelRuntime) return Promise.resolve(undefined);
498
+ if (!this.modelRuntimePromise) {
499
+ // Cache the in-flight promise so parallel agent() calls never initialize
500
+ // separate runtimes or race provider/auth replay. Clear only this failed
501
+ // attempt so a later serial agent can retry transient initialization errors.
502
+ const pending = this.initializeModelRuntime(agentDir);
503
+ this.modelRuntimePromise = pending;
504
+ void pending.catch(() => {
505
+ if (this.modelRuntimePromise === pending) this.modelRuntimePromise = undefined;
506
+ });
507
+ }
508
+ return this.modelRuntimePromise;
509
+ }
510
+
511
+ private async initializeModelRuntime(agentDir: string): Promise<ModelRuntimeLike | undefined> {
512
+ const runtime = await this.createModelRuntime?.({
513
+ authPath: path.join(agentDir, "auth.json"),
514
+ modelsPath: path.join(agentDir, "models.json"),
515
+ allowModelNetwork: false,
516
+ });
517
+ if (!runtime) return undefined;
518
+
519
+ const registeredProviderIds = this.modelRegistry?.getRegisteredProviderIds?.() ?? [];
520
+ let providersChanged = false;
521
+ for (const provider of registeredProviderIds) {
522
+ const config = this.modelRegistry?.getRegisteredProviderConfig?.(provider);
523
+ if (config === undefined || !runtime.registerProvider) continue;
524
+ runtime.registerProvider(provider, config);
525
+ providersChanged = true;
526
+ }
527
+ if (providersChanged && runtime.refresh) {
528
+ await runtime.refresh({ allowNetwork: false });
529
+ }
530
+
531
+ // Only replay CLI/SDK runtime overrides. Stored credentials, OAuth, env,
532
+ // and models.json auth must stay owned by the new runtime so they can refresh.
533
+ if (
534
+ runtime.setRuntimeApiKey
535
+ && this.modelRegistry?.getProviderAuthStatus
536
+ && this.modelRegistry.getApiKeyForProvider
537
+ ) {
538
+ const providers = new Set(this.modelRegistry.getAvailable().map((model) => model.provider));
539
+ for (const provider of registeredProviderIds) providers.add(provider);
540
+ for (const provider of providers) {
541
+ if (this.modelRegistry.getProviderAuthStatus(provider).source !== "runtime") continue;
542
+ const apiKey = await this.modelRegistry.getApiKeyForProvider(provider);
543
+ if (apiKey) await runtime.setRuntimeApiKey(provider, apiKey);
544
+ }
545
+ }
546
+
547
+ return runtime;
548
+ }
549
+
189
550
  private resolveModel(
190
551
  pattern: string | undefined,
191
552
  role: AgentTypeDef | undefined,
@@ -201,20 +562,56 @@ export class WorkflowAgentRunner {
201
562
  }
202
563
  }
203
564
 
204
- const THINKING_LEVELS = new Set<ThinkingLevel>(["off", "minimal", "low", "medium", "high", "xhigh"]);
565
+ interface ModelRuntimeConstructorLike {
566
+ create(options: ModelRuntimeCreateOptions): Promise<ModelRuntimeLike>;
567
+ }
568
+
569
+ /** Capability detection avoids importing a named export that Pi 0.80.7 lacks. */
570
+ async function createPiModelRuntime(
571
+ options: ModelRuntimeCreateOptions,
572
+ ): Promise<ModelRuntimeLike | undefined> {
573
+ const runtimeClass = (PiCodingAgent as unknown as {
574
+ ModelRuntime?: ModelRuntimeConstructorLike;
575
+ }).ModelRuntime;
576
+ return runtimeClass?.create ? runtimeClass.create(options) : undefined;
577
+ }
205
578
 
206
579
  export function splitThinkingSuffix(pattern: string): { base: string; thinking?: ThinkingLevel } {
207
580
  const idx = pattern.lastIndexOf(":");
208
581
  if (idx === -1) return { base: pattern };
209
582
  const raw = pattern.slice(idx + 1).trim();
210
- const suffix = raw as ThinkingLevel;
211
- if (THINKING_LEVELS.has(suffix)) return { base: pattern.slice(0, idx).trim(), thinking: suffix };
583
+ if (isThinkingLevel(raw)) return { base: pattern.slice(0, idx).trim(), thinking: raw };
212
584
  // Trailing colon with no/invalid suffix (e.g. "sonnet:"): strip it so the base
213
585
  // is still matchable instead of silently falling back to the default model.
214
586
  if (raw === "") return { base: pattern.slice(0, idx).trim() };
215
587
  return { base: pattern };
216
588
  }
217
589
 
590
+ /**
591
+ * Avoid sending an unknown `max` value to runtimes/models that do not advertise
592
+ * it. Requesting xhigh is equivalent to max for those models because Pi clamps
593
+ * xhigh to their strongest supported level.
594
+ */
595
+ export function resolveSessionThinkingLevel(
596
+ requested: ThinkingLevel | undefined,
597
+ model: ModelLike | undefined,
598
+ ): ThinkingLevel | undefined {
599
+ if (requested !== ULTRACODE_THINKING_LEVEL || !model) return requested;
600
+ // Known models without max can skip a wasted session. For an unknown/default
601
+ // model, pass max first; the post-create compatibility check retries xhigh on
602
+ // pre-max Pi regardless of whether it clamped to off, medium, or high.
603
+ return model.thinkingLevelMap?.max == null
604
+ ? LEGACY_ULTRACODE_THINKING_LEVEL
605
+ : ULTRACODE_THINKING_LEVEL;
606
+ }
607
+
608
+ function matchExactModelIn(models: ModelLike[] | undefined, pattern: string): ModelLike | undefined {
609
+ if (!models || !pattern.trim()) return undefined;
610
+ const lower = pattern.trim().toLowerCase();
611
+ return models.find((m) => `${m.provider}/${m.id}`.toLowerCase() === lower)
612
+ ?? models.find((m) => m.id.toLowerCase() === lower);
613
+ }
614
+
218
615
  /** Match a model pattern against a registry list: exact provider/id, then exact id, then substring. */
219
616
  export function matchModelIn(models: ModelLike[] | undefined, pattern: string): ModelLike | undefined {
220
617
  if (!models) return undefined;
@@ -223,10 +620,8 @@ export function matchModelIn(models: ModelLike[] | undefined, pattern: string):
223
620
  if (!pattern.trim()) return undefined;
224
621
  const lower = pattern.trim().toLowerCase();
225
622
  const slash = lower.includes("/");
226
- // 1. exact provider/id, 2. exact id, 3. substring on id / provider/id / name.
227
623
  return (
228
- models.find((m) => `${m.provider}/${m.id}`.toLowerCase() === lower) ??
229
- models.find((m) => m.id.toLowerCase() === lower) ??
624
+ matchExactModelIn(models, pattern) ??
230
625
  models.find((m) =>
231
626
  slash
232
627
  ? `${m.provider}/${m.id}`.toLowerCase().includes(lower)
@@ -256,36 +651,285 @@ export function resolveModelSelection(args: {
256
651
  if (!effectivePattern) {
257
652
  return { model: defaultModel, thinkingLevel: roleThinking ?? defaultThinking };
258
653
  }
654
+ // A literal model id wins before suffix parsing, so ids/tags such as
655
+ // `ollama/coder:max` remain addressable. Parse :level only without an exact hit.
656
+ const exactModel = matchExactModelIn(models, effectivePattern)
657
+ ?? matchExactModelIn(defaultModel ? [defaultModel] : undefined, effectivePattern);
658
+ if (exactModel) {
659
+ return { model: exactModel, thinkingLevel: roleThinking ?? defaultThinking };
660
+ }
259
661
  const { base, thinking } = splitThinkingSuffix(effectivePattern);
260
662
  const model = base ? matchModelIn(models, base) ?? defaultModel : defaultModel;
261
663
  return { model, thinkingLevel: thinking ?? roleThinking ?? defaultThinking };
262
664
  }
263
665
 
264
- function readUsage(session: any): AgentUsage {
666
+ function waitForSharedInitialization<T>(
667
+ pending: Promise<T>,
668
+ signal: AbortSignal | undefined,
669
+ ): Promise<T> {
670
+ if (!signal) return pending;
671
+ if (signal.aborted) return Promise.reject(abortedError());
672
+
673
+ return new Promise<T>((resolve, reject) => {
674
+ let settled = false;
675
+ const finish = (callback: () => void) => {
676
+ if (settled) return;
677
+ settled = true;
678
+ signal.removeEventListener("abort", onAbort);
679
+ callback();
680
+ };
681
+ const onAbort = () => finish(() => reject(abortedError()));
682
+ signal.addEventListener("abort", onAbort, { once: true });
683
+ pending.then(
684
+ (value) => finish(() => resolve(value)),
685
+ (error) => finish(() => reject(error)),
686
+ );
687
+ });
688
+ }
689
+
690
+ function abortedError(): Error {
691
+ return new Error("Subagent was aborted");
692
+ }
693
+
694
+ function disposeQuietly(session: { dispose(): void }): void {
695
+ try {
696
+ session.dispose();
697
+ } catch {
698
+ // A failed provisional-session cleanup must not block compatibility fallback.
699
+ }
700
+ }
701
+
702
+ function emptyAgentUsage(): AgentUsage {
703
+ return {
704
+ inputTokens: 0,
705
+ outputTokens: 0,
706
+ cacheReadTokens: 0,
707
+ cacheWriteTokens: 0,
708
+ totalTokens: 0,
709
+ cost: 0,
710
+ turns: 0,
711
+ toolUses: 0,
712
+ retries: 0,
713
+ compactions: 0,
714
+ };
715
+ }
716
+
717
+ function errorText(error: unknown): string {
718
+ return error instanceof Error ? error.message : String(error);
719
+ }
720
+
721
+ function safeEmitTelemetry(
722
+ listener: ((event: AgentTelemetryEvent) => void) | undefined,
723
+ event: AgentTelemetryEvent,
724
+ ): void {
725
+ if (!listener) return;
726
+ try {
727
+ listener(event);
728
+ } catch {
729
+ // Detailed observability is best-effort and must never affect execution.
730
+ }
731
+ }
732
+
733
+ function normalizeTurnUsage(value: unknown): AgentTurnUsage | undefined {
734
+ if (!value || typeof value !== "object") return undefined;
735
+ const usage = value as any;
736
+ const inputTokens = finiteNumber(usage.input) ?? finiteNumber(usage.inputTokens) ?? 0;
737
+ const outputTokens = finiteNumber(usage.output) ?? finiteNumber(usage.outputTokens) ?? 0;
738
+ return {
739
+ inputTokens,
740
+ outputTokens,
741
+ cacheReadTokens: finiteNumber(usage.cacheRead) ?? 0,
742
+ cacheWriteTokens: finiteNumber(usage.cacheWrite) ?? 0,
743
+ // Compact task stats intentionally exclude cache traffic from token use.
744
+ totalTokens: inputTokens + outputTokens,
745
+ cost: finiteNumber(usage.cost?.total) ?? finiteNumber(usage.cost) ?? 0,
746
+ };
747
+ }
748
+
749
+ function assistantText(message: any): string {
750
+ if (!Array.isArray(message?.content)) return "";
751
+ return message.content
752
+ .filter((part: any) => part?.type === "text" && typeof part.text === "string")
753
+ .map((part: any) => part.text)
754
+ .join("");
755
+ }
756
+
757
+ function resultText(result: any): string {
758
+ if (!Array.isArray(result?.content)) return "";
759
+ return result.content
760
+ .filter((part: any) => part?.type === "text" && typeof part.text === "string")
761
+ .map((part: any) => part.text)
762
+ .join("\n");
763
+ }
764
+
765
+ function tailByUtf8Bytes(value: string, maxBytes: number): string {
766
+ if (Buffer.byteLength(value, "utf8") <= maxBytes) return value;
767
+ let low = 0;
768
+ let high = value.length;
769
+ while (low < high) {
770
+ const mid = Math.floor((low + high) / 2);
771
+ if (Buffer.byteLength(value.slice(mid), "utf8") <= maxBytes) high = mid;
772
+ else low = mid + 1;
773
+ }
774
+ return value.slice(low);
775
+ }
776
+
777
+ function toolResultPreview(result: unknown): string | undefined {
778
+ const raw = resultText(result);
779
+ if (!raw.trim()) return undefined;
780
+ const safe = safeTranscriptText(raw, 64 * 1024);
781
+ const lines = safe.split(/\r?\n/).filter((line) => line.trim()).slice(-20).join("\n");
782
+ const bounded = tailByUtf8Bytes(lines, 8 * 1024);
783
+ return bounded.trim() || undefined;
784
+ }
785
+
786
+ /** Convert child session events into the private task-detail stream. */
787
+ export function forwardTelemetry(
788
+ event: unknown,
789
+ onTelemetry: (event: AgentTelemetryEvent) => void,
790
+ ): void {
791
+ try {
792
+ const e = event as any;
793
+ switch (e?.type) {
794
+ case "turn_start":
795
+ safeEmitTelemetry(onTelemetry, {
796
+ kind: "turn_start",
797
+ turnIndex: finiteNumber(e.turnIndex),
798
+ });
799
+ return;
800
+ case "message_update": {
801
+ const update = e.assistantMessageEvent;
802
+ if (update?.type === "text_delta" && typeof update.delta === "string") {
803
+ safeEmitTelemetry(onTelemetry, { kind: "text_delta", delta: update.delta });
804
+ } else if (update?.type === "thinking_start") {
805
+ safeEmitTelemetry(onTelemetry, { kind: "thinking_start" });
806
+ } else if (update?.type === "thinking_end") {
807
+ safeEmitTelemetry(onTelemetry, { kind: "thinking_end" });
808
+ }
809
+ return;
810
+ }
811
+ case "message_end":
812
+ if (e.message?.role === "assistant") {
813
+ safeEmitTelemetry(onTelemetry, {
814
+ kind: "message_end",
815
+ text: assistantText(e.message),
816
+ usage: normalizeTurnUsage(e.message.usage),
817
+ error: typeof e.message.errorMessage === "string" ? e.message.errorMessage : undefined,
818
+ });
819
+ }
820
+ return;
821
+ case "tool_execution_start":
822
+ if (typeof e.toolName === "string") {
823
+ const args = toolArgsPreview(e.toolName, e.args);
824
+ safeEmitTelemetry(onTelemetry, {
825
+ kind: "tool_start",
826
+ toolCallId: typeof e.toolCallId === "string" ? e.toolCallId : `${e.toolName}:unknown`,
827
+ toolName: e.toolName,
828
+ ...(args ? { toolArgs: args } : {}),
829
+ });
830
+ }
831
+ return;
832
+ case "tool_execution_end":
833
+ safeEmitTelemetry(onTelemetry, {
834
+ kind: "tool_end",
835
+ toolCallId: typeof e.toolCallId === "string" ? e.toolCallId : `${String(e.toolName ?? "tool")}:unknown`,
836
+ toolName: typeof e.toolName === "string" ? e.toolName : "tool",
837
+ isError: Boolean(e.isError),
838
+ resultPreview: toolResultPreview(e.result),
839
+ });
840
+ return;
841
+ case "auto_retry_start": {
842
+ const attempt = finiteNumber(e.attempt) ?? 0;
843
+ const maxAttempts = finiteNumber(e.maxAttempts) ?? 0;
844
+ const reason = typeof e.errorMessage === "string" ? safeDisplayText(e.errorMessage, 160) : "";
845
+ safeEmitTelemetry(onTelemetry, {
846
+ kind: "retry",
847
+ state: "start",
848
+ detail: `retry ${attempt}/${maxAttempts} in ${formatDelay(finiteNumber(e.delayMs) ?? 0)}${reason ? `: ${reason}` : ""}`,
849
+ });
850
+ return;
851
+ }
852
+ case "auto_retry_end": {
853
+ const failure = typeof e.finalError === "string" ? safeDisplayText(e.finalError, 180) : "";
854
+ safeEmitTelemetry(onTelemetry, {
855
+ kind: "retry",
856
+ state: "end",
857
+ detail: e.success ? "retry succeeded" : failure ? `retry failed: ${failure}` : "retry failed",
858
+ });
859
+ return;
860
+ }
861
+ case "compaction_start":
862
+ safeEmitTelemetry(onTelemetry, {
863
+ kind: "compaction",
864
+ state: "start",
865
+ detail: compactionStartDetail(e.reason),
866
+ });
867
+ return;
868
+ case "compaction_end":
869
+ safeEmitTelemetry(onTelemetry, {
870
+ kind: "compaction",
871
+ state: "end",
872
+ detail: e.aborted ? "compaction aborted" : e.errorMessage ? "compaction failed" : "compaction complete",
873
+ });
874
+ return;
875
+ default:
876
+ return;
877
+ }
878
+ } catch {
879
+ // best-effort: malformed provider events must not affect the run
880
+ }
881
+ }
882
+
883
+ function readUsage(
884
+ session: any,
885
+ counters: {
886
+ retries?: number;
887
+ compactions?: number;
888
+ turns?: number;
889
+ toolUses?: number;
890
+ observing?: boolean;
891
+ } = {},
892
+ ): AgentUsage {
265
893
  try {
266
894
  const stats = session.getSessionStats?.();
267
895
  if (stats?.tokens) {
268
896
  return {
897
+ inputTokens: stats.tokens.input ?? 0,
269
898
  outputTokens: stats.tokens.output ?? 0,
270
- totalTokens: stats.tokens.total ?? 0,
899
+ cacheReadTokens: stats.tokens.cacheRead ?? 0,
900
+ cacheWriteTokens: stats.tokens.cacheWrite ?? 0,
901
+ totalTokens: (stats.tokens.input ?? 0) + (stats.tokens.output ?? 0),
271
902
  cost: stats.cost ?? 0,
903
+ turns: counters.observing ? counters.turns ?? 0 : stats.assistantMessages ?? 0,
904
+ toolUses: counters.observing ? counters.toolUses ?? 0 : stats.toolCalls ?? 0,
905
+ retries: counters.retries ?? 0,
906
+ compactions: counters.compactions ?? 0,
272
907
  };
273
908
  }
274
909
  } catch {
275
910
  // fall through to message-based estimate
276
911
  }
277
912
  // Fallback: sum assistant usage from messages.
278
- let output = 0;
279
- let total = 0;
280
- let cost = 0;
281
- for (const message of (session.messages ?? []) as Array<Partial<AssistantMessage>>) {
913
+ const usage = emptyAgentUsage();
914
+ for (const message of (session.messages ?? []) as Array<any>) {
282
915
  if (message?.role === "assistant" && message.usage) {
283
- output += message.usage.output ?? 0;
284
- total += message.usage.totalTokens ?? 0;
285
- cost += message.usage.cost?.total ?? 0;
916
+ usage.turns = (usage.turns ?? 0) + 1;
917
+ usage.inputTokens = (usage.inputTokens ?? 0) + (message.usage.input ?? 0);
918
+ usage.outputTokens += message.usage.output ?? 0;
919
+ usage.cacheReadTokens = (usage.cacheReadTokens ?? 0) + (message.usage.cacheRead ?? 0);
920
+ usage.cacheWriteTokens = (usage.cacheWriteTokens ?? 0) + (message.usage.cacheWrite ?? 0);
921
+ usage.cost += message.usage.cost?.total ?? 0;
286
922
  }
923
+ if (message?.role === "toolResult") usage.toolUses = (usage.toolUses ?? 0) + 1;
924
+ }
925
+ if (counters.observing) {
926
+ usage.turns = counters.turns ?? 0;
927
+ usage.toolUses = counters.toolUses ?? 0;
287
928
  }
288
- return { outputTokens: output, totalTokens: total, cost };
929
+ usage.totalTokens = (usage.inputTokens ?? 0) + usage.outputTokens;
930
+ usage.retries = counters.retries ?? 0;
931
+ usage.compactions = counters.compactions ?? 0;
932
+ return usage;
289
933
  }
290
934
 
291
935
  function lastAssistantText(messages: unknown[]): string {
@@ -308,22 +952,228 @@ function lastAssistantText(messages: unknown[]): string {
308
952
  export function forwardActivity(event: unknown, onActivity: (e: AgentActivityInput) => void): void {
309
953
  try {
310
954
  const e = event as any;
311
- if (e?.type === "message_update") {
312
- const ame = e.assistantMessageEvent;
313
- if (!ame) return;
314
- if (ame.type === "text_delta" && typeof ame.delta === "string") {
315
- onActivity({ kind: "text", detail: ame.delta });
316
- } else if (ame.type === "thinking_delta") {
317
- onActivity({ kind: "thinking" });
318
- } else if (ame.type === "toolcall_start" && ame.toolName) {
319
- onActivity({ kind: "tool", detail: String(ame.toolName) });
955
+ switch (e?.type) {
956
+ case "agent_start":
957
+ onActivity({ kind: "waiting", detail: "starting agent" });
958
+ return;
959
+ case "turn_start":
960
+ // Pi emits this immediately before authentication/request setup and then
961
+ // waits for the provider's first stream event.
962
+ onActivity({ kind: "waiting", detail: "waiting for model" });
963
+ return;
964
+ case "message_start":
965
+ if (e.message?.role === "assistant") {
966
+ onActivity({ kind: "waiting", detail: "model stream opened" });
967
+ }
968
+ return;
969
+ case "message_update":
970
+ forwardMessageActivity(e.assistantMessageEvent, onActivity);
971
+ return;
972
+ case "message_end":
973
+ if (e.message?.role === "assistant") {
974
+ const error = compactText(e.message.errorMessage, 100);
975
+ onActivity({
976
+ kind: "waiting",
977
+ detail: error ? `model response ended: ${error}` : "model response complete",
978
+ });
979
+ }
980
+ return;
981
+ case "tool_execution_start":
982
+ forwardToolActivity(e, "start", onActivity);
983
+ return;
984
+ case "tool_execution_update":
985
+ forwardToolActivity(e, "update", onActivity);
986
+ return;
987
+ case "tool_execution_end":
988
+ forwardToolActivity(e, "end", onActivity);
989
+ return;
990
+ case "turn_end":
991
+ onActivity({ kind: "waiting", detail: "turn complete" });
992
+ return;
993
+ case "agent_end":
994
+ onActivity({
995
+ kind: "waiting",
996
+ detail: e.willRetry ? "agent ended; retry pending" : "finishing agent",
997
+ });
998
+ return;
999
+ case "agent_settled":
1000
+ onActivity({ kind: "waiting", detail: "agent settled" });
1001
+ return;
1002
+ case "auto_retry_start": {
1003
+ const attempt = finiteNumber(e.attempt) ?? 0;
1004
+ const maxAttempts = finiteNumber(e.maxAttempts) ?? 0;
1005
+ const delay = formatDelay(finiteNumber(e.delayMs) ?? 0);
1006
+ const error = compactText(e.errorMessage, 100);
1007
+ onActivity({
1008
+ kind: "retry",
1009
+ detail: `retry ${attempt}/${maxAttempts} in ${delay}${error ? `: ${error}` : ""}`,
1010
+ });
1011
+ return;
320
1012
  }
321
- return;
322
- }
323
- if (e?.type === "tool_execution_start" && e.toolName) {
324
- onActivity({ kind: "tool", detail: String(e.toolName) });
1013
+ case "auto_retry_end": {
1014
+ const attempt = finiteNumber(e.attempt) ?? 0;
1015
+ const error = compactText(e.finalError, 100);
1016
+ onActivity({
1017
+ kind: "retry",
1018
+ detail: e.success
1019
+ ? `retry ${attempt} succeeded`
1020
+ : `retry ${attempt} failed${error ? `: ${error}` : ""}`,
1021
+ });
1022
+ return;
1023
+ }
1024
+ case "compaction_start":
1025
+ onActivity({ kind: "compaction", detail: compactionStartDetail(e.reason) });
1026
+ return;
1027
+ case "compaction_end": {
1028
+ const error = compactText(e.errorMessage, 100);
1029
+ onActivity({
1030
+ kind: "compaction",
1031
+ detail: error
1032
+ ? `compaction failed: ${error}`
1033
+ : e.aborted
1034
+ ? "compaction aborted"
1035
+ : e.willRetry
1036
+ ? "compaction complete; retrying model"
1037
+ : "compaction complete",
1038
+ });
1039
+ return;
1040
+ }
1041
+ case "queue_update": {
1042
+ const queued = (Array.isArray(e.steering) ? e.steering.length : 0)
1043
+ + (Array.isArray(e.followUp) ? e.followUp.length : 0);
1044
+ if (queued > 0) onActivity({ kind: "waiting", detail: `${queued} queued message${queued === 1 ? "" : "s"}` });
1045
+ return;
1046
+ }
1047
+ case "thinking_level_changed":
1048
+ if (typeof e.level === "string") {
1049
+ onActivity({ kind: "waiting", detail: `thinking level: ${e.level}` });
1050
+ }
1051
+ return;
1052
+ default:
1053
+ return;
325
1054
  }
326
1055
  } catch {
327
1056
  // best-effort: never let observability break the run
328
1057
  }
329
1058
  }
1059
+
1060
+ function forwardMessageActivity(
1061
+ event: any,
1062
+ onActivity: (e: AgentActivityInput) => void,
1063
+ ): void {
1064
+ switch (event?.type) {
1065
+ case "text_start":
1066
+ case "text_end":
1067
+ onActivity({ kind: "text", detail: "responding" });
1068
+ return;
1069
+ case "text_delta":
1070
+ if (typeof event.delta === "string") {
1071
+ onActivity({ kind: "text", detail: "responding" });
1072
+ }
1073
+ return;
1074
+ case "thinking_start":
1075
+ case "thinking_delta":
1076
+ case "thinking_end":
1077
+ onActivity({ kind: "thinking", detail: "thinking" });
1078
+ return;
1079
+ case "toolcall_start":
1080
+ case "toolcall_delta":
1081
+ // Current Pi events do not expose toolName until toolcall_end.
1082
+ onActivity({ kind: "waiting", detail: "preparing tool call" });
1083
+ return;
1084
+ case "toolcall_end": {
1085
+ const name = typeof event.toolCall?.name === "string" ? event.toolCall.name : "tool";
1086
+ const args = toolArgsPreview(name, event.toolCall?.arguments);
1087
+ onActivity({
1088
+ kind: "waiting",
1089
+ detail: `preparing ${toolLabel(name, args)}`,
1090
+ });
1091
+ return;
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ function forwardToolActivity(
1097
+ event: any,
1098
+ state: "start" | "update" | "end",
1099
+ onActivity: (e: AgentActivityInput) => void,
1100
+ ): void {
1101
+ if (typeof event.toolName !== "string") return;
1102
+ const name = event.toolName;
1103
+ const args = state === "end" ? undefined : toolArgsPreview(name, event.args);
1104
+ onActivity({
1105
+ kind: "tool",
1106
+ detail: state === "end"
1107
+ ? `${name} ${event.isError ? "failed" : "finished"}`
1108
+ : toolLabel(name, args),
1109
+ toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : `${name}:unknown`,
1110
+ toolName: name,
1111
+ ...(args ? { toolArgs: args } : {}),
1112
+ toolState: state,
1113
+ });
1114
+ }
1115
+
1116
+ function toolLabel(name: string, args: string | undefined): string {
1117
+ const safeName = safeDisplayText(name, 40) || "tool";
1118
+ return args ? `${safeName}: ${args}` : safeName;
1119
+ }
1120
+
1121
+ const PATH_PREVIEW_TOOLS = new Set(["read", "write", "edit", "grep", "find", "ls"]);
1122
+
1123
+ /** Keep tool status useful without displaying free-form payload bodies. */
1124
+ export function toolArgsPreview(toolName: string, value: unknown, max = 80): string | undefined {
1125
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
1126
+ const args = value as Record<string, unknown>;
1127
+ const normalizedName = safeDisplayText(toolName, 40).toLowerCase();
1128
+ if (normalizedName === "bash") {
1129
+ const command = scalarPreview(args.command);
1130
+ if (command) return safeDisplayText(safeCommandPreview(command), max);
1131
+ }
1132
+ if (PATH_PREVIEW_TOOLS.has(normalizedName)) {
1133
+ for (const key of ["path", "file_path"]) {
1134
+ const filePath = scalarPreview(args[key]);
1135
+ if (filePath) return safePathPreview(filePath, max);
1136
+ }
1137
+ }
1138
+ return undefined;
1139
+ }
1140
+
1141
+ function safePathPreview(value: string, max: number): string {
1142
+ const clean = displayOneLine(value);
1143
+ if (!clean || /:\/\/|[?&#@]/.test(clean)) return "path";
1144
+ const segments = clean.replace(/\\/g, "/").split("/").filter(Boolean);
1145
+ const basename = segments.at(-1) ?? clean;
1146
+ return safeDisplayText(basename, max) || "path";
1147
+ }
1148
+
1149
+ function scalarPreview(value: unknown): string | undefined {
1150
+ if (typeof value === "string") {
1151
+ const sample = value.length > DISPLAY_INPUT_LIMIT
1152
+ ? value.slice(0, DISPLAY_INPUT_LIMIT)
1153
+ : value;
1154
+ if (sample.trim()) return value;
1155
+ }
1156
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
1157
+ return undefined;
1158
+ }
1159
+
1160
+ function compactionStartDetail(reason: unknown): string {
1161
+ if (reason === "overflow") return "context overflow; compacting";
1162
+ if (reason === "threshold") return "context threshold reached; compacting";
1163
+ return "compacting context";
1164
+ }
1165
+
1166
+ function finiteNumber(value: unknown): number | undefined {
1167
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1168
+ }
1169
+
1170
+ function formatDelay(ms: number): string {
1171
+ if (ms < 1000) return `${Math.max(0, Math.round(ms))}ms`;
1172
+ const seconds = ms / 1000;
1173
+ return `${Number.isInteger(seconds) ? seconds : seconds.toFixed(1)}s`;
1174
+ }
1175
+
1176
+ function compactText(value: unknown, max: number): string {
1177
+ if (typeof value !== "string") return "";
1178
+ return truncateDisplay(redactCommand(value), max);
1179
+ }