neoctl 0.2.41 → 0.2.43

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.
Files changed (60) hide show
  1. package/README.md +2 -0
  2. package/dist/context/prompts.js +2 -1
  3. package/dist/context/prompts.js.map +1 -1
  4. package/dist/core/image-registry.js +1 -1
  5. package/dist/core/image-registry.js.map +1 -1
  6. package/dist/core/message-pipeline.js +2 -1
  7. package/dist/core/message-pipeline.js.map +1 -1
  8. package/dist/core/query-engine.d.ts +6 -0
  9. package/dist/core/query-engine.js +22 -0
  10. package/dist/core/query-engine.js.map +1 -1
  11. package/dist/core/query-timing.d.ts +43 -0
  12. package/dist/core/query-timing.js +124 -0
  13. package/dist/core/query-timing.js.map +1 -0
  14. package/dist/core/query.d.ts +5 -0
  15. package/dist/core/query.js +60 -10
  16. package/dist/core/query.js.map +1 -1
  17. package/dist/core/run-agent.d.ts +2 -0
  18. package/dist/core/run-agent.js +4 -0
  19. package/dist/core/run-agent.js.map +1 -1
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +1 -0
  22. package/dist/index.js.map +1 -1
  23. package/dist/model/model-gateway.d.ts +6 -0
  24. package/dist/model/model-gateway.js.map +1 -1
  25. package/dist/model/openai-chat-mapper.js +6 -0
  26. package/dist/model/openai-chat-mapper.js.map +1 -1
  27. package/dist/model/openai-mappers.js +2 -2
  28. package/dist/model/openai-mappers.js.map +1 -1
  29. package/dist/model/openai-responses-mapper.js +6 -0
  30. package/dist/model/openai-responses-mapper.js.map +1 -1
  31. package/dist/repl/index.js +13 -1
  32. package/dist/repl/index.js.map +1 -1
  33. package/dist/session/session-store.d.ts +11 -0
  34. package/dist/session/session-store.js +24 -0
  35. package/dist/session/session-store.js.map +1 -1
  36. package/dist/tools/builtins/image-capabilities.d.ts +22 -0
  37. package/dist/tools/builtins/image-capabilities.js +98 -0
  38. package/dist/tools/builtins/image-capabilities.js.map +1 -0
  39. package/dist/tools/builtins/image-generation-tool.d.ts +14 -7
  40. package/dist/tools/builtins/image-generation-tool.js +183 -158
  41. package/dist/tools/builtins/image-generation-tool.js.map +1 -1
  42. package/dist/tools/builtins/image-output-verification.d.ts +14 -0
  43. package/dist/tools/builtins/image-output-verification.js +183 -0
  44. package/dist/tools/builtins/image-output-verification.js.map +1 -0
  45. package/dist/tools/registry.d.ts +2 -0
  46. package/dist/tools/registry.js +6 -0
  47. package/dist/tools/registry.js.map +1 -1
  48. package/dist/tools/run-tool-use.js +5 -0
  49. package/dist/tools/run-tool-use.js.map +1 -1
  50. package/dist/tools/tool-orchestration.d.ts +5 -0
  51. package/dist/tools/tool-orchestration.js +1 -0
  52. package/dist/tools/tool-orchestration.js.map +1 -1
  53. package/dist/types/events.d.ts +10 -0
  54. package/dist/web/image-result-metadata.d.ts +20 -0
  55. package/dist/web/image-result-metadata.js +20 -0
  56. package/dist/web/image-result-metadata.js.map +1 -0
  57. package/dist/web/index.d.ts +18 -3
  58. package/dist/web/index.js +68 -17
  59. package/dist/web/index.js.map +1 -1
  60. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-result-metadata.js","sourceRoot":"","sources":["../../src/web/image-result-metadata.ts"],"names":[],"mappings":"AAYA,wGAAwG;AACxG,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IACrF,MAAM,IAAI,GAAG,MAAiC,CAAC;IAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAClD,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/E,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5H,MAAM,IAAI,GAAG,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAChF,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;QAClH,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC;QAC1H,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;YAC9B,MAAM,KAAK,GAAG,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAgC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzF,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;QAC5O,CAAC,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import type { TimingRecord } from "../core/query-timing.js";
3
+ import { type ImageResultMetadata } from "./image-result-metadata.js";
2
4
  import { type IncomingMessage, type ServerResponse } from "node:http";
3
5
  import { QueryEngine } from "../core/query-engine.js";
4
6
  import { type AppPromptStore } from "../app/app-prompt.js";
@@ -82,6 +84,7 @@ export declare class SessionUsageTracker {
82
84
  }
83
85
  interface UiLineImage {
84
86
  src?: string;
87
+ originalSrc?: string;
85
88
  imageId?: string;
86
89
  label?: string;
87
90
  mimeType: string;
@@ -159,6 +162,7 @@ interface UiLine {
159
162
  toolPresentation?: ToolPresentation;
160
163
  parentToolName?: string;
161
164
  toolUseId?: string;
165
+ timing?: TimingRecord;
162
166
  parentToolUseId?: string;
163
167
  title?: string;
164
168
  bodyTitle?: string;
@@ -174,6 +178,7 @@ interface UiLine {
174
178
  live?: boolean;
175
179
  collapsible?: boolean;
176
180
  image?: UiLineImage;
181
+ imageResult?: ImageResultMetadata;
177
182
  compaction?: UiCompactionReport;
178
183
  }
179
184
  type WebActionFailure = {
@@ -185,6 +190,15 @@ type WebActionResult<T extends object = object> = ({
185
190
  ok: true;
186
191
  } & T) | WebActionFailure;
187
192
  interface UiStatus {
193
+ modelOutput?: {
194
+ kind: "text";
195
+ } | {
196
+ kind: "tool_call";
197
+ callId: string;
198
+ name?: string;
199
+ };
200
+ queryTiming?: TimingRecord;
201
+ toolTimings?: TimingRecord[];
188
202
  phase: string;
189
203
  detail?: string;
190
204
  currentTool?: {
@@ -235,7 +249,7 @@ export interface CreateWebRuntimeOptions {
235
249
  sessionPluginOverrides?: Readonly<Record<string, boolean>>;
236
250
  persistSessionPluginOverrides?: WebRuntimePluginSupport["persist"];
237
251
  resolveSessionPluginOverrides?: WebRuntimePluginSupport["resolve"];
238
- /** Global tool defaults. Missing names default to enabled. */
252
+ /** Global tool overrides. Missing names use registry defaults (delegation tools are opt-in). */
239
253
  globalToolOverrides?: Readonly<Record<string, boolean>>;
240
254
  /** Per-session tool overrides. Missing names inherit their global default. */
241
255
  sessionToolOverrides?: Readonly<Record<string, boolean>>;
@@ -435,7 +449,7 @@ export declare class WebRepl {
435
449
  steps: {
436
450
  id: string;
437
451
  title: string;
438
- status: "completed" | "running" | "failed";
452
+ status: "running" | "completed" | "failed";
439
453
  }[] | undefined;
440
454
  };
441
455
  pendingMessageCount: number;
@@ -563,7 +577,7 @@ export declare class WebRepl {
563
577
  steps: {
564
578
  id: string;
565
579
  title: string;
566
- status: "completed" | "running" | "failed";
580
+ status: "running" | "completed" | "failed";
567
581
  }[] | undefined;
568
582
  };
569
583
  pendingMessageCount: number;
@@ -788,6 +802,7 @@ export declare class WebRepl {
788
802
  private backgroundTasks;
789
803
  private detachRunningForeground;
790
804
  private reattachRunningSession;
805
+ private timedStatus;
791
806
  private reduce;
792
807
  private finalizeLiveLine;
793
808
  private finalizeThinkingLine;
package/dist/web/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { imageResultMetadata } from "./image-result-metadata.js";
2
3
  import { subagentHeader, subagentStatusFacts } from "./status-semantics.js";
3
4
  import { handlePromptConfigRequest } from "./prompt-config-protocol.js";
4
5
  import http from "node:http";
@@ -17,7 +18,7 @@ import { readModelProviderConfig } from "../model/config.js";
17
18
  import { findModelMetadata, loadModelCatalog, reasoningEffortsForModel, resolveContextWindowTokens } from "../model/context-window.js";
18
19
  import { CommunicationLogger, LoggingModelGateway } from "../model/communication-logger.js";
19
20
  import { createModelGatewayFromConfig, createModelGatewayFromProcessEnv } from "../model/provider-factory.js";
20
- import { ToolRegistry } from "../tools/registry.js";
21
+ import { ToolRegistry, isToolEnabledByDefault } from "../tools/registry.js";
21
22
  import { builtinToolPresentation, toolPresentationForSource } from "../tools/tool-catalog.js";
22
23
  import { editTool, writeTool } from "../tools/builtins/edit-tool.js";
23
24
  import { createExecTool, createWriteStdinTool } from "../tools/builtins/exec-tool.js";
@@ -287,7 +288,7 @@ function normalizeToolOverrides(value, catalog) {
287
288
  }
288
289
  function applyToolOverrides(registry, catalog, globalOverrides, sessionOverrides) {
289
290
  for (const tool of catalog)
290
- registry.setEnabled(tool.name, sessionOverrides[tool.name] ?? globalOverrides[tool.name] ?? true);
291
+ registry.setEnabled(tool.name, sessionOverrides[tool.name] ?? globalOverrides[tool.name] ?? isToolEnabledByDefault(tool.name));
291
292
  }
292
293
  /** Called only at a safe turn boundary or while idle, never during tool execution. */
293
294
  export function refreshGlobalToolOverrides(runtime) {
@@ -544,7 +545,7 @@ export class WebRepl {
544
545
  }
545
546
  return {
546
547
  lines: this.lines.map(line => line.toolError ? { ...line, toolError: this.runtime.engine.redactDisplayValue?.(line.toolError) ?? line.toolError } : line),
547
- status: this.status,
548
+ status: this.timedStatus(),
548
549
  busy: this.busy,
549
550
  queuedInput: this.queuedInput,
550
551
  backgroundTaskCount: backgroundTasks.length,
@@ -596,7 +597,7 @@ export class WebRepl {
596
597
  return {
597
598
  items: support.catalog.map((tool) => ({
598
599
  ...tool,
599
- configuredEnabled: support.globalOverrides[tool.name] ?? true,
600
+ configuredEnabled: support.globalOverrides[tool.name] ?? isToolEnabledByDefault(tool.name),
600
601
  })),
601
602
  };
602
603
  }
@@ -614,7 +615,7 @@ export class WebRepl {
614
615
  busy: this.busy,
615
616
  items: support.catalog.map((tool) => {
616
617
  const override = support.sessionOverrides[tool.name];
617
- const globallyEnabled = support.globalOverrides[tool.name] ?? true;
618
+ const globallyEnabled = support.globalOverrides[tool.name] ?? isToolEnabledByDefault(tool.name);
618
619
  const available = tool.source !== "plugin" || Boolean(tool.pluginId && activePluginIds.has(tool.pluginId));
619
620
  return {
620
621
  ...tool,
@@ -1405,6 +1406,14 @@ export class WebRepl {
1405
1406
  this.setBusy(true);
1406
1407
  this.setStatus({ ...this.status, phase: "running", detail: "working" });
1407
1408
  }
1409
+ timedStatus() {
1410
+ const records = this.runtime.engine.getTimingRecords?.() ?? [];
1411
+ if (records.length === 0)
1412
+ return this.status;
1413
+ const queryTiming = records.filter(record => record.kind === "query").at(-1);
1414
+ return { ...this.status, queryTiming,
1415
+ toolTimings: records.filter(record => record.kind === "tool" && record.runId === queryTiming?.runId) };
1416
+ }
1408
1417
  reduce(event) {
1409
1418
  this.status = reduceStatus(this.status, event);
1410
1419
  if (event.type === "usage")
@@ -1565,6 +1574,18 @@ export class WebRepl {
1565
1574
  }
1566
1575
  handleEvent(event) {
1567
1576
  this.reduce(event);
1577
+ if (event.type === "timing.updated") {
1578
+ if (event.timing.kind === "tool") {
1579
+ const line = this.lines.find(item => item.timing?.id === event.timing.id)
1580
+ ?? this.lines.find(item => item.id === this.liveToolLineIds.get(event.timing.toolUseId));
1581
+ if (line) {
1582
+ this.lines = this.lines.map(item => item.id === line.id ? { ...item, timing: event.timing } : item);
1583
+ this.queueDeltaOperation({ type: "line.patch", id: line.id, patch: { timing: event.timing } });
1584
+ }
1585
+ }
1586
+ this.queueDeltaStatus();
1587
+ return;
1588
+ }
1568
1589
  if (event.type === "message" && this.matchesPendingUserImageEcho(event.message)) {
1569
1590
  const messageId = this.pendingUserImageEchoMessageId;
1570
1591
  this.pendingUserImageEchoIds = undefined;
@@ -1573,7 +1594,7 @@ export class WebRepl {
1573
1594
  this.broadcastSync();
1574
1595
  return;
1575
1596
  }
1576
- if (event.type === "tool_call.delta") {
1597
+ if (event.type === "tool_call.started" || event.type === "tool_call.delta") {
1577
1598
  this.queueDeltaStatus();
1578
1599
  return;
1579
1600
  }
@@ -1818,7 +1839,7 @@ export class WebRepl {
1818
1839
  this.activeAbortController = abortController;
1819
1840
  this.interruptArmed = false;
1820
1841
  this.setBusy(true);
1821
- this.setStatus({ ...this.status, phase: "running", detail: "working", usage: undefined, streamedOutputTokens: 0, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
1842
+ this.setStatus({ ...this.status, phase: "running", detail: "working", modelOutput: undefined, usage: undefined, streamedOutputTokens: 0, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined });
1822
1843
  const engine = this.runtime.engine;
1823
1844
  try {
1824
1845
  for await (const event of engine.sendUserText(promptPayload.text, {
@@ -1832,7 +1853,7 @@ export class WebRepl {
1832
1853
  if (this.runtime.engine !== engine)
1833
1854
  continue;
1834
1855
  if (this.suppressReattachedStreaming.has(engine)) {
1835
- if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage") {
1856
+ if (event.type === "message" || event.type === "terminal" || event.type === "error" || event.type === "context.metrics" || event.type === "usage" || event.type === "timing.updated") {
1836
1857
  if (event.type === "message" || event.type === "terminal" || event.type === "error")
1837
1858
  this.suppressReattachedStreaming.delete(engine);
1838
1859
  this.handleEvent(event);
@@ -2002,7 +2023,7 @@ export class WebRepl {
2002
2023
  protocolVersion: 2,
2003
2024
  sessionId: this.runtime.engine.snapshot().session?.sessionId,
2004
2025
  operations,
2005
- status: this.status,
2026
+ status: this.timedStatus(),
2006
2027
  };
2007
2028
  for (const subscriber of this.subscribers)
2008
2029
  this.send(subscriber, "delta", payload);
@@ -2376,6 +2397,14 @@ function restoredHistoryLines(runtime) {
2376
2397
  historyToolLineIndexes.set(line.toolUseId, lines.length - 1);
2377
2398
  return lines.length;
2378
2399
  };
2400
+ const toolTimings = new Map();
2401
+ for (const record of runtime.engine.getTimingRecords?.() ?? []) {
2402
+ if (record.kind !== "tool" || !record.toolUseId)
2403
+ continue;
2404
+ const matches = toolTimings.get(record.toolUseId) ?? [];
2405
+ matches.push(record);
2406
+ toolTimings.set(record.toolUseId, matches);
2407
+ }
2379
2408
  const entries = runtime.engine.getDisplayEntries();
2380
2409
  let lastCompactionIndex = -1;
2381
2410
  for (let index = entries.length - 1; index >= 0; index -= 1) {
@@ -2396,6 +2425,12 @@ function restoredHistoryLines(runtime) {
2396
2425
  const report = entry.report ?? legacyCompactionReport(entry.reason);
2397
2426
  append(compactionLine(report, entry.createdAt, entryIndex === lastCompactionIndex));
2398
2427
  }
2428
+ // Legacy histories can reuse call IDs. Ambiguous associations stay unknown.
2429
+ for (const line of lines) {
2430
+ const matches = line.toolUseId ? toolTimings.get(line.toolUseId) : undefined;
2431
+ if (matches?.length === 1)
2432
+ line.timing = matches[0];
2433
+ }
2399
2434
  return lines;
2400
2435
  }
2401
2436
  function initialStatus(runtime, metrics = runtime.initialMetrics) {
@@ -2543,7 +2578,7 @@ function toolStreamStepStatus(phase) {
2543
2578
  }
2544
2579
  function reduceStatus(status, event) {
2545
2580
  if (event.type === "state")
2546
- return { ...status, phase: event.phase, detail: event.detail, currentTool: event.phase === "running_tools" ? status.currentTool : undefined, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
2581
+ return { ...status, phase: event.phase, detail: event.detail, modelOutput: undefined, currentTool: event.phase === "running_tools" ? status.currentTool : undefined, usage: event.phase === "preparing" ? undefined : status.usage, streamedOutputTokens: event.phase === "preparing" ? 0 : status.streamedOutputTokens, inputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.phase === "preparing" ? undefined : status.outputTokenUpdatedAt, retryCooldownUntil: event.phase === "preparing" ? undefined : status.retryCooldownUntil, activityTick: status.activityTick + 1 };
2547
2582
  if (event.type === "context.compacted")
2548
2583
  return { ...status, phase: "compacting", detail: formatCompactionReportSummary(event.compaction), activityTick: status.activityTick + 1 };
2549
2584
  if (event.type === "context.metrics")
@@ -2551,21 +2586,23 @@ function reduceStatus(status, event) {
2551
2586
  if (event.type === "usage")
2552
2587
  return { ...status, usage: event.usage, inputTokenUpdatedAt: event.usage.inputTokens !== undefined ? Date.now() : status.inputTokenUpdatedAt, outputTokenUpdatedAt: event.usage.outputTokens !== undefined ? Date.now() : status.outputTokenUpdatedAt, activityTick: status.activityTick + 1 };
2553
2588
  if (event.type === "assistant.delta")
2554
- return { ...status, phase: "calling_model", streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2589
+ return { ...status, phase: "calling_model", modelOutput: { kind: "text" }, streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2555
2590
  if (event.type === "thinking.delta")
2556
- return { ...status, phase: "thinking", streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2591
+ return { ...status, phase: "thinking", modelOutput: undefined, streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.text), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2592
+ if (event.type === "tool_call.started")
2593
+ return { ...status, phase: "calling_model", modelOutput: { kind: "tool_call", callId: event.callId, name: event.name }, activityTick: status.activityTick + 1 };
2557
2594
  if (event.type === "tool_call.delta")
2558
- return { ...status, phase: "calling_model", streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.argumentsDelta), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2595
+ return { ...status, phase: "calling_model", modelOutput: { kind: "tool_call", callId: event.callId, name: event.name ?? (status.modelOutput?.kind === "tool_call" && status.modelOutput.callId === event.callId ? status.modelOutput.name : undefined) }, streamedOutputTokens: status.streamedOutputTokens + estimateTokens(event.argumentsDelta), outputTokenUpdatedAt: Date.now(), activityTick: status.activityTick + 1 };
2559
2596
  if (event.type === "retrying")
2560
- return { ...status, phase: "calling_model", detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`, retryCooldownUntil: Date.now() + event.delayMs, activityTick: status.activityTick + 1 };
2597
+ return { ...status, phase: "calling_model", modelOutput: undefined, detail: `retrying in ${(event.delayMs / 1000).toFixed(1)}s`, retryCooldownUntil: Date.now() + event.delayMs, activityTick: status.activityTick + 1 };
2561
2598
  if (event.type === "tool.started")
2562
- return { ...status, phase: "running_tools", currentTool: { id: event.toolUse.id, name: event.toolUse.name, input: event.toolUse.input, startedAt: Date.now() }, activityTick: status.activityTick + 1 };
2599
+ return { ...status, phase: "running_tools", modelOutput: undefined, currentTool: { id: event.toolUse.id, name: event.toolUse.name, input: event.toolUse.input, startedAt: Date.now() }, activityTick: status.activityTick + 1 };
2563
2600
  if (event.type === "tool.progress")
2564
2601
  return { ...status, phase: "running_tools", detail: event.progress.message || status.detail, currentTool: { id: event.toolUse.id, name: event.toolUse.name, input: event.toolUse.input, startedAt: status.currentTool?.id === event.toolUse.id ? status.currentTool.startedAt : Date.now() }, activityTick: status.activityTick + 1 };
2565
2602
  if (event.type === "tool.result.available" || event.type === "tool.finished" || event.type === "tool.batch.completed")
2566
2603
  return { ...status, currentTool: "toolUse" in event && status.currentTool?.id === event.toolUse.id ? undefined : status.currentTool, activityTick: status.activityTick + 1 };
2567
2604
  if (event.type === "terminal")
2568
- return { ...status, phase: "stopped", detail: event.reason, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
2605
+ return { ...status, phase: "stopped", modelOutput: undefined, detail: event.reason, currentTool: undefined, inputTokenUpdatedAt: undefined, outputTokenUpdatedAt: undefined, retryCooldownUntil: undefined, activityTick: status.activityTick + 1 };
2569
2606
  if (event.type === "message" || event.type === "error")
2570
2607
  return { ...status, activityTick: status.activityTick + 1 };
2571
2608
  return status;
@@ -2948,6 +2985,7 @@ function imageLineForBlock(role, block, messageId, blockIndex) {
2948
2985
  text: block.label ?? `[image ${block.mimeType}]`,
2949
2986
  image: {
2950
2987
  src: resolution.available ? (block.imageId ? `/api/images/by-id/${encodeURIComponent(block.imageId)}` : `/api/images/${encodeURIComponent(messageId)}/${blockIndex}`) : undefined,
2988
+ originalSrc: resolution.available ? (block.imageId ? `/api/images/by-id/${encodeURIComponent(block.imageId)}` : `/api/images/${encodeURIComponent(messageId)}/${blockIndex}`) : undefined,
2951
2989
  imageId: block.imageId,
2952
2990
  label: block.label,
2953
2991
  mimeType: block.mimeType,
@@ -3090,6 +3128,7 @@ function formatToolResultLine(toolName, output, ok, presentToolResult) {
3090
3128
  title: presentation?.title ?? toolTitle(toolName, "finished"),
3091
3129
  bodyTitle: presentation?.bodyTitle ?? formatted.bodyTitle,
3092
3130
  titleStatus: ok ? "success" : "failure",
3131
+ imageResult: toolName === "image_create" ? imageResultMetadata(output) : undefined,
3093
3132
  text: presentation?.text ?? formatted.text,
3094
3133
  toolDisplay: resources?.length ? undefined : buildToolResultDisplay(toolName, output, ok),
3095
3134
  format: formatted.format,
@@ -3593,7 +3632,19 @@ function formatImageGenerationToolResult(output, ok) {
3593
3632
  const lines = [`${mode === "edit" ? "edited" : "generated"} ${returnedImages ?? 0} image${returnedImages === 1 ? "" : "s"}`];
3594
3633
  const details = [provider, model, size, quality && quality !== "auto" ? quality : undefined, format].filter((value) => Boolean(value));
3595
3634
  if (details.length > 0)
3596
- lines.push(details.join(" · "));
3635
+ lines.push(`${output.requested ? "requested: " : ""}${details.join(" · ")}`);
3636
+ if (isRecord(output.actual)) {
3637
+ lines.push(`upstream-reported quality: ${String(output.actual.quality ?? "unreported")}`);
3638
+ if (Array.isArray(output.images))
3639
+ for (const image of output.images.filter(isRecord)) {
3640
+ if (typeof image.width === "number" && typeof image.height === "number")
3641
+ lines.push(`actual image: ${image.width}x${image.height} · ${String(image.mimeType ?? "")}`);
3642
+ }
3643
+ }
3644
+ if (Array.isArray(output.warnings))
3645
+ for (const warning of output.warnings.filter(isRecord)) {
3646
+ lines.push(`warning [${String(warning.code ?? "IMAGE_WARNING")}]: ${String(warning.message ?? "")}`);
3647
+ }
3597
3648
  if (sourceImages !== undefined)
3598
3649
  lines.push(`source images: ${sourceImages}`);
3599
3650
  const duration = imageGenerationDuration(output);