@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.
- package/CHANGELOG.md +36 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +13 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +3 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/provider/capabilities.d.ts +2 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +6 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/runtime/query/index.d.ts +3 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +2 -0
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +10 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +67 -0
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +5 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/result.d.ts.map +1 -1
- package/dist/runtime/query/result.js +25 -22
- package/dist/runtime/query/result.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +90 -0
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/types/errors/catalog.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +13 -6
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/errors/index.d.ts.map +1 -1
- package/dist/types/errors/index.js +21 -8
- package/dist/types/errors/index.js.map +1 -1
- package/dist/types/provider/config.d.ts +9 -0
- package/dist/types/provider/config.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +17 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/tool/presentation.d.ts +6 -2
- package/dist/types/tool/presentation.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/bridge/a2a/mapper.ts +23 -4
- package/src/bridge/sse/mapper.ts +3 -0
- package/src/provider/capabilities.ts +10 -0
- package/src/runtime/query/index.ts +5 -1
- package/src/runtime/query/iteration/index.ts +72 -0
- package/src/runtime/query/iteration/phases/context.ts +5 -0
- package/src/runtime/query/result.ts +25 -23
- package/src/tools/builtins/computer-use.ts +98 -0
- package/src/types/errors/catalog.ts +14 -6
- package/src/types/errors/index.ts +22 -8
- package/src/types/provider/config.ts +9 -0
- package/src/types/run/events.ts +17 -3
- 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
|
-
|
|
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:
|
|
95
|
+
code: providerFailure.code,
|
|
90
96
|
message,
|
|
91
|
-
name:
|
|
92
|
-
...(
|
|
93
|
-
...(
|
|
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
|
-
|
|
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:
|
|
113
|
+
message: providerFailure.message,
|
|
104
114
|
details: {
|
|
105
|
-
providerCode:
|
|
106
|
-
...(
|
|
107
|
-
|
|
108
|
-
|
|
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:
|
|
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
|
|
package/src/types/run/events.ts
CHANGED
|
@@ -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,
|
|
597
|
-
// attachments against a no-documents driver
|
|
598
|
-
//
|
|
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
|
|
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
|
-
| {
|
|
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
|
|
42
|
+
/** The same shapes, for what a call produced. */
|
|
36
43
|
export type ToolResultView = ToolCallView
|
|
37
44
|
|
|
38
45
|
/**
|