@namzu/sdk 33.0.0 → 33.1.1

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 (55) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  3. package/dist/bridge/a2a/mapper.js +13 -0
  4. package/dist/bridge/a2a/mapper.js.map +1 -1
  5. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  6. package/dist/bridge/sse/mapper.js +3 -0
  7. package/dist/bridge/sse/mapper.js.map +1 -1
  8. package/dist/provider/capabilities.d.ts +2 -0
  9. package/dist/provider/capabilities.d.ts.map +1 -1
  10. package/dist/provider/capabilities.js +6 -0
  11. package/dist/provider/capabilities.js.map +1 -1
  12. package/dist/runtime/query/index.d.ts +3 -1
  13. package/dist/runtime/query/index.d.ts.map +1 -1
  14. package/dist/runtime/query/index.js +2 -0
  15. package/dist/runtime/query/index.js.map +1 -1
  16. package/dist/runtime/query/iteration/index.d.ts +10 -0
  17. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  18. package/dist/runtime/query/iteration/index.js +67 -0
  19. package/dist/runtime/query/iteration/index.js.map +1 -1
  20. package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
  21. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  22. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  23. package/dist/runtime/query/result.d.ts.map +1 -1
  24. package/dist/runtime/query/result.js +25 -22
  25. package/dist/runtime/query/result.js.map +1 -1
  26. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  27. package/dist/tools/builtins/computer-use.js +90 -0
  28. package/dist/tools/builtins/computer-use.js.map +1 -1
  29. package/dist/types/errors/catalog.d.ts.map +1 -1
  30. package/dist/types/errors/catalog.js +13 -6
  31. package/dist/types/errors/catalog.js.map +1 -1
  32. package/dist/types/errors/index.d.ts.map +1 -1
  33. package/dist/types/errors/index.js +21 -8
  34. package/dist/types/errors/index.js.map +1 -1
  35. package/dist/types/provider/config.d.ts +9 -0
  36. package/dist/types/provider/config.d.ts.map +1 -1
  37. package/dist/types/run/events.d.ts +17 -0
  38. package/dist/types/run/events.d.ts.map +1 -1
  39. package/dist/types/run/events.js.map +1 -1
  40. package/dist/types/tool/presentation.d.ts +6 -2
  41. package/dist/types/tool/presentation.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/bridge/a2a/mapper.ts +23 -4
  44. package/src/bridge/sse/mapper.ts +3 -0
  45. package/src/provider/capabilities.ts +10 -0
  46. package/src/runtime/query/index.ts +5 -1
  47. package/src/runtime/query/iteration/index.ts +72 -0
  48. package/src/runtime/query/iteration/phases/context.ts +5 -0
  49. package/src/runtime/query/result.ts +25 -23
  50. package/src/tools/builtins/computer-use.ts +98 -0
  51. package/src/types/errors/catalog.ts +14 -6
  52. package/src/types/errors/index.ts +22 -8
  53. package/src/types/provider/config.ts +9 -0
  54. package/src/types/run/events.ts +17 -3
  55. package/src/types/tool/presentation.ts +10 -3
@@ -7,6 +7,7 @@ import {
7
7
  STRUCTURED_OUTPUT_REPROMPT,
8
8
  } from '../../../constants/tools/index.js'
9
9
  import { renderSkillsSection } from '../../../persona/assembler.js'
10
+ import { resolveProviderCapabilities } from '../../../provider/capabilities.js'
10
11
  import { collectChatCompletion } from '../../../provider/collect-chat-completion.js'
11
12
  import { formatCompletionNotification } from '../../../scheduler/completion-inbox.js'
12
13
  import {
@@ -19,8 +20,10 @@ import { getTracer } from '../../../telemetry/runtime-accessors.js'
19
20
  import { STRUCTURED_OUTPUT_TOOL_NAME } from '../../../tools/builtins/structuredOutput.js'
20
21
  import { DELEGATION_TIMEOUT_MS } from '../../../tools/coordinator/index.js'
21
22
  import type { CostInfo, TokenUsage } from '../../../types/common/index.js'
23
+ import { NamzuError } from '../../../types/errors/index.js'
22
24
  import type { MessageId } from '../../../types/ids/index.js'
23
25
  import {
26
+ type Message,
24
27
  createAssistantMessage,
25
28
  createRuntimeContextMessage,
26
29
  createSystemMessage,
@@ -148,6 +151,8 @@ export class IterationOrchestrator {
148
151
  * one process must not suppress each other's first envelope.
149
152
  */
150
153
  private lastEnvelopeKey: string | undefined
154
+ /** Rich tool blocks already reported; durable history is scanned every turn. */
155
+ private readonly warnedRichToolResults = new Set<string>()
151
156
  /**
152
157
  * The previous iteration held a `stopWhen` decision open for a worker.
153
158
  *
@@ -168,6 +173,70 @@ export class IterationOrchestrator {
168
173
  this.ctx = ctx
169
174
  }
170
175
 
176
+ /**
177
+ * Check the exact post-budget request for tool-result shapes the active driver
178
+ * cannot carry. Initial capability negotiation cannot see results produced by
179
+ * a later tool turn, so this boundary runs immediately before every provider
180
+ * call. Keys are durable call/block coordinates, which prevents old history
181
+ * from warning again on every subsequent iteration.
182
+ */
183
+ private async reportUnsupportedToolResults(messages: readonly Message[]): Promise<void> {
184
+ const capabilities =
185
+ this.ctx.providerCapabilities ?? resolveProviderCapabilities(this.ctx.provider)
186
+ const images: string[] = []
187
+ const documents: string[] = []
188
+ for (const message of messages) {
189
+ if (message.role !== 'tool' || !Array.isArray(message.content)) continue
190
+ for (const [index, block] of message.content.entries()) {
191
+ const key = `${message.toolCallId}:${index}:${block.type}`
192
+ if (this.warnedRichToolResults.has(key)) continue
193
+ if (block.type === 'image' && !capabilities.supportsToolResultImages) {
194
+ images.push(key)
195
+ }
196
+ if (block.type === 'document' && !capabilities.supportsToolResultDocuments) {
197
+ documents.push(key)
198
+ }
199
+ }
200
+ }
201
+
202
+ const report = async (
203
+ keys: readonly string[],
204
+ capability: 'vision' | 'documents',
205
+ label: 'image' | 'document',
206
+ ): Promise<void> => {
207
+ if (keys.length === 0) return
208
+ const message = `Provider '${this.ctx.provider.id}' declares it cannot map ${label} tool results, but this request carries ${keys.length} new ${label} block(s). The model will receive the driver's explicit text fallback instead of that content.`
209
+ if (this.ctx.strictCapabilities) {
210
+ throw new NamzuError({
211
+ code: 'capability_unavailable',
212
+ message,
213
+ details: {
214
+ providerId: this.ctx.provider.id,
215
+ capability,
216
+ blockCount: keys.length,
217
+ },
218
+ })
219
+ }
220
+ for (const key of keys) this.warnedRichToolResults.add(key)
221
+ this.ctx.log.warn('Capability mismatch: the provider cannot map rich tool results', {
222
+ 'namzu.capability.detail': message,
223
+ [GENAI.SYSTEM]: this.ctx.provider.id,
224
+ 'namzu.runtime.rich_tool_result_count': keys.length,
225
+ })
226
+ await this.ctx.emitEvent({
227
+ type: 'capability_warning',
228
+ runId: this.ctx.runMgr.id,
229
+ capability,
230
+ contentSource: 'tool-result',
231
+ providerId: this.ctx.provider.id,
232
+ message,
233
+ })
234
+ }
235
+
236
+ await report(images, 'vision', 'image')
237
+ await report(documents, 'documents', 'document')
238
+ }
239
+
171
240
  /**
172
241
  * Adopt the run's span after construction.
173
242
  *
@@ -459,6 +528,8 @@ export class IterationOrchestrator {
459
528
  requestHistory,
460
529
  this.ctx.runConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
461
530
  )
531
+ await this.reportUnsupportedToolResults(messages)
532
+ yield* this.ctx.drainPending()
462
533
 
463
534
  // What the model is about to be ASKED, recorded when it
464
535
  // changed. `run_started` carries one system prompt and tool
@@ -1986,6 +2057,7 @@ export class IterationOrchestrator {
1986
2057
  finalHistory,
1987
2058
  this.ctx.runConfig.maxRequestRichContentBytes ?? DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES,
1988
2059
  )
2060
+ await this.reportUnsupportedToolResults(finalMessages)
1989
2061
 
1990
2062
  // Same cache discipline as the forced-final iteration: keep the
1991
2063
  // tools param identical to prior iterations (cache prefix intact,
@@ -7,6 +7,7 @@ import { NAMZU } from '../../../../constants/telemetry/index.js'
7
7
  import type { PlanManager } from '../../../../manager/plan/lifecycle.js'
8
8
  import type { RunPersistence } from '../../../../manager/run/persistence.js'
9
9
  import type { PromptContributionRegistry } from '../../../../prompt/contributions.js'
10
+ import type { ResolvedProviderCapabilities } from '../../../../provider/capabilities.js'
10
11
  import type { ServingMember } from '../../../../provider/fallback.js'
11
12
  import type { CompletionInbox } from '../../../../scheduler/completion-inbox.js'
12
13
  import type { ActivityStore } from '../../../../store/activity/memory.js'
@@ -44,6 +45,10 @@ import type { ToolGrantSet } from '../../tool-grants.js'
44
45
 
45
46
  export interface IterationContext {
46
47
  readonly provider: LLMProvider
48
+ /** Driver-level request shapes negotiated for this run. */
49
+ readonly providerCapabilities?: ResolvedProviderCapabilities
50
+ /** Refuse a capability mismatch instead of emitting a warning and degrading. */
51
+ readonly strictCapabilities?: boolean
47
52
  /**
48
53
  * Which chain member `provider` will route the NEXT request to.
49
54
  *
@@ -140,6 +140,28 @@ export class ResultAssembler {
140
140
  // later, discarding every field of it. `toPlatformError` is the
141
141
  // projection that was written for exactly this and had no callers.
142
142
  const failure = toPlatformError(err)
143
+ // The driver's classification and the operator explanation describe the
144
+ // throwable, not the terminal verdict. Compute them before choosing paused
145
+ // versus failed so a recoverable run does not become the one path that
146
+ // discards the reason and remedy a host needs in order to recover it.
147
+ const providerError = isProviderRequestError(err)
148
+ ? {
149
+ kind: err.kind,
150
+ providerId: err.providerId,
151
+ ...(err.providerCode !== undefined ? { providerCode: err.providerCode } : {}),
152
+ ...(err.status !== undefined ? { status: err.status } : {}),
153
+ ...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
154
+ // The provider's own sentence, already truncated and scrubbed
155
+ // by the driver. Without it a host rendering this metadata
156
+ // knows a request was rejected but not which field, and has to
157
+ // re-parse prose to find out.
158
+ ...(err.detail !== undefined ? { detail: err.detail } : {}),
159
+ }
160
+ : undefined
161
+ // Classification is structural; remediation is editorial. The catalog is
162
+ // optional because inventing advice for an uncharacterised failure is worse
163
+ // than presenting the reason alone.
164
+ const explanation = explainError(err) ?? undefined
143
165
 
144
166
  // A transient failure that survived every in-turn recovery is not the
145
167
  // same thing as a bad API key, and settling both as `failed` gave the
@@ -157,6 +179,9 @@ export class ResultAssembler {
157
179
  runId: runMgr.id,
158
180
  checkpointId: resumeFrom,
159
181
  reason: errorMessage,
182
+ failure,
183
+ ...(providerError ? { providerError } : {}),
184
+ ...(explanation ? { explanation } : {}),
160
185
  })
161
186
  yield* drainPending()
162
187
 
@@ -177,35 +202,12 @@ export class ResultAssembler {
177
202
  return
178
203
  }
179
204
 
180
- // The driver's classification, carried onto the run so a host can
181
- // branch on WHAT failed without re-parsing a sentence.
182
- const providerError = isProviderRequestError(err)
183
- ? {
184
- kind: err.kind,
185
- providerId: err.providerId,
186
- ...(err.providerCode !== undefined ? { providerCode: err.providerCode } : {}),
187
- ...(err.status !== undefined ? { status: err.status } : {}),
188
- ...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
189
- // The provider's own sentence, already truncated and scrubbed
190
- // by the driver. Without it a host rendering this metadata
191
- // knows a request was rejected but not which field, and has to
192
- // go re-parse `error` to find out — which is exactly the
193
- // re-parsing the line above says this exists to avoid.
194
- ...(err.detail !== undefined ? { detail: err.detail } : {}),
195
- }
196
- : undefined
197
205
  runMgr.markFailed(errorMessage, providerError)
198
206
 
199
207
  if (planManager.isActive) {
200
208
  planManager.failPlan(errorMessage)
201
209
  }
202
210
 
203
- // The classification says what kind of failure it is; the catalog
204
- // says what a person should do about it. Keeping them separate is the
205
- // point — classification is structural and belongs at the boundary,
206
- // remediation is editorial and belongs in a list a human appends to.
207
- const explanation = explainError(err) ?? undefined
208
-
209
211
  // Same terminal-verdict recording as the success path in completeRun —
210
212
  // see LOG-14, design §5. Placed AFTER the early `resumeFrom !== undefined`
211
213
  // return above, so a paused/resumable run is never audited as 'failure'.
@@ -43,6 +43,60 @@ const actionSchema = z.discriminatedUnion('type', [
43
43
  z.object({ type: z.literal('key'), keys: z.string() }),
44
44
  ])
45
45
 
46
+ /**
47
+ * The provider-facing shape is deliberately flat.
48
+ *
49
+ * The runtime schema above is the authoritative contract: it knows which
50
+ * fields each action requires. Rendering that discriminated union produces a
51
+ * root `anyOf`, however, and some custom-tool wires reject root combinators
52
+ * even when every branch is an object. A model can still see every field and
53
+ * every action here; incomplete combinations are rejected by `actionSchema`
54
+ * before the host is called, with the recovery hint below.
55
+ */
56
+ const pointModelInputSchema = {
57
+ type: 'object',
58
+ properties: {
59
+ x: { type: 'integer' },
60
+ y: { type: 'integer' },
61
+ },
62
+ required: ['x', 'y'],
63
+ additionalProperties: false,
64
+ } as const
65
+
66
+ const modelInputSchema: Record<string, unknown> = {
67
+ type: 'object',
68
+ properties: {
69
+ type: {
70
+ type: 'string',
71
+ enum: [
72
+ 'screenshot',
73
+ 'cursor_position',
74
+ 'mouse_move',
75
+ 'mouse_click',
76
+ 'mouse_drag',
77
+ 'scroll',
78
+ 'type_text',
79
+ 'key',
80
+ ],
81
+ description:
82
+ 'Desktop action. screenshot and cursor_position need no other fields; mouse_move needs to; mouse_click needs at and button; mouse_drag needs from, to, and button; scroll needs at, direction, and amount; type_text needs text; key needs keys.',
83
+ },
84
+ to: pointModelInputSchema,
85
+ at: pointModelInputSchema,
86
+ from: pointModelInputSchema,
87
+ button: { type: 'string', enum: ['left', 'right', 'middle'] },
88
+ direction: { type: 'string', enum: ['up', 'down', 'left', 'right'] },
89
+ amount: { type: 'integer', description: 'Positive integer scroll distance.' },
90
+ text: { type: 'string', description: 'Literal text to type.' },
91
+ keys: {
92
+ type: 'string',
93
+ description: 'Key or key chord to press, for example ENTER or CTRL+R.',
94
+ },
95
+ },
96
+ required: ['type'],
97
+ additionalProperties: false,
98
+ }
99
+
46
100
  /**
47
101
  * The tool's input, inferred from its schema.
48
102
  *
@@ -106,6 +160,38 @@ function buildDescription(host: ComputerUseHost): string {
106
160
  return lines.join(' ')
107
161
  }
108
162
 
163
+ function pointLabel(point: { readonly x: number; readonly y: number }): string {
164
+ return `(${point.x}, ${point.y})`
165
+ }
166
+
167
+ function quotedText(value: string): string {
168
+ const oneLine = value.replace(/\s+/g, ' ')
169
+ const visible = oneLine.length > 64 ? `${oneLine.slice(0, 63)}…` : oneLine
170
+ return JSON.stringify(visible)
171
+ }
172
+
173
+ /** Human activity text; the raw action union remains the model-facing input. */
174
+ function actionLabel(input: ActionInput): string {
175
+ switch (input.type) {
176
+ case 'screenshot':
177
+ return 'Capture screenshot'
178
+ case 'cursor_position':
179
+ return 'Read cursor position'
180
+ case 'mouse_move':
181
+ return `Move pointer to ${pointLabel(input.to)}`
182
+ case 'mouse_click':
183
+ return `Click ${input.button} at ${pointLabel(input.at)}`
184
+ case 'mouse_drag':
185
+ return `Drag ${input.button} from ${pointLabel(input.from)} to ${pointLabel(input.to)}`
186
+ case 'scroll':
187
+ return `Scroll ${input.direction} ${input.amount} at ${pointLabel(input.at)}`
188
+ case 'type_text':
189
+ return `Type ${quotedText(input.text)}`
190
+ case 'key':
191
+ return `Press ${input.keys}`
192
+ }
193
+ }
194
+
109
195
  function resultToToolResult(result: ComputerUseResult): ToolResult {
110
196
  switch (result.type) {
111
197
  case 'screenshot': {
@@ -193,11 +279,23 @@ export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<Act
193
279
  name: COMPUTER_USE_TOOL_NAME,
194
280
  description: buildDescription(host),
195
281
  inputSchema: actionSchema,
282
+ modelInputSchema: structuredClone(modelInputSchema),
283
+ validationErrorHint:
284
+ 'Action requirements: mouse_move needs "to"; mouse_click needs "at" and "button"; mouse_drag needs "from", "to", and "button"; scroll needs "at", "direction", and positive "amount"; type_text needs "text"; key needs "keys".',
196
285
  category: 'custom',
197
286
  permissions: [],
198
287
  readOnly: false,
199
288
  destructive: (input: ActionInput) => DESTRUCTIVE_ACTION_TYPES.has(input.type),
200
289
  concurrencySafe: false,
290
+ presentCall: (input) => ({
291
+ kind: 'generic',
292
+ label: actionLabel(input),
293
+ presentation: 'activity',
294
+ }),
295
+ presentResult: (_input, result) =>
296
+ result.success && result.output.trim().toLowerCase() === 'ok'
297
+ ? { kind: 'generic', label: result.output, visibility: 'hidden' }
298
+ : undefined,
201
299
 
202
300
  async execute(input, _context): Promise<ToolResult> {
203
301
  const required = requiredCapability(input.type)
@@ -1,4 +1,4 @@
1
- import { ProviderError } from '../provider/errors.js'
1
+ import { ProviderError, classifyProviderError } from '../provider/errors.js'
2
2
  import { isNamzuError } from './index.js'
3
3
 
4
4
  /**
@@ -84,13 +84,21 @@ export function factsOf(err: unknown): ErrorFacts {
84
84
  const hint = readHint(err)
85
85
  const message = err instanceof Error ? err.message : String(err)
86
86
 
87
- if (err instanceof ProviderError) {
87
+ const providerFailure =
88
+ err instanceof ProviderError
89
+ ? err
90
+ : err instanceof Error && err.name === 'ProviderRequestError'
91
+ ? classifyProviderError(err)
92
+ : undefined
93
+ if (providerFailure) {
88
94
  return {
89
- code: err.code,
95
+ code: providerFailure.code,
90
96
  message,
91
- name: err.name,
92
- ...(err.status !== undefined ? { status: err.status } : {}),
93
- ...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
97
+ name: providerFailure.name,
98
+ ...(providerFailure.status !== undefined ? { status: providerFailure.status } : {}),
99
+ ...(providerFailure.retryAfterMs !== undefined
100
+ ? { retryAfterMs: providerFailure.retryAfterMs }
101
+ : {}),
94
102
  ...(hint !== undefined ? { hint } : {}),
95
103
  }
96
104
  }
@@ -1,5 +1,5 @@
1
1
  import type { PlatformError } from '../common/index.js'
2
- import { ProviderError } from '../provider/errors.js'
2
+ import { ProviderError, classifyProviderError } from '../provider/errors.js'
3
3
 
4
4
  /**
5
5
  * What went wrong, at the granularity a HOST actually branches on.
@@ -97,17 +97,31 @@ export function toPlatformError(err: unknown): PlatformError {
97
97
  }
98
98
  }
99
99
 
100
- if (err instanceof ProviderError) {
100
+ // Current drivers throw `ProviderRequestError`; older/custom providers may
101
+ // throw `ProviderError`. The retry layer already classifies both through the
102
+ // shared function below, but this terminal projection used to recognise only
103
+ // the older class and turn a first-hand 429 into unknown/non-retryable.
104
+ const providerFailure =
105
+ err instanceof ProviderError
106
+ ? err
107
+ : err instanceof Error && err.name === 'ProviderRequestError'
108
+ ? classifyProviderError(err)
109
+ : undefined
110
+ if (providerFailure) {
101
111
  return {
102
112
  code: 'provider_error',
103
- message: err.message,
113
+ message: providerFailure.message,
104
114
  details: {
105
- providerCode: err.code,
106
- ...(err.providerId !== undefined ? { providerId: err.providerId } : {}),
107
- ...(err.status !== undefined ? { status: err.status } : {}),
108
- ...(err.retryAfterMs !== undefined ? { retryAfterMs: err.retryAfterMs } : {}),
115
+ providerCode: providerFailure.code,
116
+ ...(providerFailure.providerId !== undefined
117
+ ? { providerId: providerFailure.providerId }
118
+ : {}),
119
+ ...(providerFailure.status !== undefined ? { status: providerFailure.status } : {}),
120
+ ...(providerFailure.retryAfterMs !== undefined
121
+ ? { retryAfterMs: providerFailure.retryAfterMs }
122
+ : {}),
109
123
  },
110
- retryable: err.retryable,
124
+ retryable: providerFailure.retryable,
111
125
  }
112
126
  }
113
127
 
@@ -138,6 +138,15 @@ export interface ProviderCapabilities {
138
138
  * treated as capable, same permissive default.
139
139
  */
140
140
  supportsDocuments?: boolean
141
+ /**
142
+ * Whether the driver maps image blocks returned by tools onto its tool-result
143
+ * wire. Separate from `supportsVision`: some protocols admit user image input
144
+ * but only text in a function result. Absent keeps the permissive compatibility
145
+ * default used by the older flags.
146
+ */
147
+ supportsToolResultImages?: boolean
148
+ /** Whether the driver maps document blocks returned by tools onto its result wire. */
149
+ supportsToolResultDocuments?: boolean
141
150
  maxOutputTokens?: number
142
151
  }
143
152
 
@@ -505,6 +505,17 @@ type CoreRunEvent =
505
505
  runId: RunId
506
506
  checkpointId: CheckpointId
507
507
  reason: string
508
+ /**
509
+ * The same structured failure projection a terminal `run_failed`
510
+ * carries. A pause is a different verdict, not a less informative one:
511
+ * the retryability and any provider-directed delay are what let a host
512
+ * decide when and how to resume this checkpoint.
513
+ */
514
+ failure?: PlatformError
515
+ /** First-hand driver classification, when the provider produced one. */
516
+ providerError?: import('../provider/error.js').ProviderErrorInfo
517
+ /** Curated operator copy, absent when no catalog rule matched. */
518
+ explanation?: { id: string; message: string; hint: string }
508
519
  }
509
520
  | {
510
521
  type: 'run_resuming'
@@ -593,13 +604,16 @@ type CoreRunEvent =
593
604
  // run when the request asks for something the provider DRIVER declared
594
605
  // it cannot do — tools registered against a no-tools driver (tool
595
606
  // surfaces stripped so the model is never told about uncallable
596
- // tools), image attachments against a no-vision driver, or document
597
- // attachments against a no-documents driver. Hosts surface these so
598
- // degradation is visible, not silent.
607
+ // tools), image attachments against a no-vision driver, document
608
+ // attachments against a no-documents driver, or rich tool blocks against a
609
+ // result wire that only carries text. Hosts surface these so degradation is
610
+ // visible, not silent.
599
611
  | {
600
612
  type: 'capability_warning'
601
613
  runId: RunId
602
614
  capability: 'tools' | 'vision' | 'documents'
615
+ /** Present when the mismatch was produced after a tool executed. */
616
+ contentSource?: 'tool-result'
603
617
  providerId: string
604
618
  message: string
605
619
  }
@@ -11,13 +11,20 @@ import type { ToolResult } from './index.js'
11
11
  * the raw arguments and rebuilt the same switch.
12
12
  *
13
13
  * The tool knows what it is doing; the host knows how its surface renders.
14
- * These are the three shapes one host had already found it needed, closed
14
+ * These are the shapes hosts have agreed to render, closed
15
15
  * deliberately: an open union would let a tool ask for a rendering no host
16
16
  * has, which is a request that fails silently at the far end.
17
17
  */
18
18
  export type ToolCallView =
19
19
  /** A line of text. What everything that is not a diff or a command gets. */
20
- | { readonly kind: 'generic'; readonly label: string }
20
+ | {
21
+ readonly kind: 'generic'
22
+ readonly label: string
23
+ /** Render this complete authored label without adding the registry name. */
24
+ readonly presentation?: 'activity'
25
+ /** A successful result may add no information beyond the completed call row. */
26
+ readonly visibility?: 'hidden'
27
+ }
21
28
  /**
22
29
  * A change to a document. `path` is optional because not every diff is
23
30
  * a file — a tool patching a remote record has a before and an after
@@ -32,7 +39,7 @@ export type ToolCallView =
32
39
  /** A command and what it printed. */
33
40
  | { readonly kind: 'terminal'; readonly command?: string; readonly output: string }
34
41
 
35
- /** The same three shapes, for what a call produced. */
42
+ /** The same shapes, for what a call produced. */
36
43
  export type ToolResultView = ToolCallView
37
44
 
38
45
  /**