@namzu/sdk 52.0.0 → 52.1.0
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 +19 -0
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +57 -17
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/contracts/a2a.d.ts +2 -2
- package/dist/provider/capabilities.d.ts +9 -0
- package/dist/provider/capabilities.d.ts.map +1 -1
- package/dist/provider/capabilities.js +15 -0
- package/dist/provider/capabilities.js.map +1 -1
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +13 -0
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/fallback.d.ts.map +1 -1
- package/dist/provider/fallback.js +10 -0
- package/dist/provider/fallback.js.map +1 -1
- package/dist/provider/idle-timeout.d.ts.map +1 -1
- package/dist/provider/idle-timeout.js +5 -0
- package/dist/provider/idle-timeout.js.map +1 -1
- package/dist/provider/image-input-refusal.d.ts +9 -0
- package/dist/provider/image-input-refusal.d.ts.map +1 -0
- package/dist/provider/image-input-refusal.js +54 -0
- package/dist/provider/image-input-refusal.js.map +1 -0
- package/dist/provider/index.d.ts +2 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +2 -1
- package/dist/provider/index.js.map +1 -1
- package/dist/provider/retry.d.ts +7 -2
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +38 -4
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +1 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +1 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/runtime/query/deferred-sandbox.d.ts +54 -0
- package/dist/runtime/query/deferred-sandbox.d.ts.map +1 -0
- package/dist/runtime/query/deferred-sandbox.js +75 -0
- package/dist/runtime/query/deferred-sandbox.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +10 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +24 -3
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +93 -25
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +26 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +164 -21
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +10 -7
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +10 -7
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts +42 -1
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +119 -21
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts +21 -1
- package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -1
- package/dist/runtime/query/iteration/provider-rejected-image.js +53 -7
- package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -1
- package/dist/runtime/query/iteration/request-repair.d.ts +108 -0
- package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -0
- package/dist/runtime/query/iteration/request-repair.js +593 -0
- package/dist/runtime/query/iteration/request-repair.js.map +1 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts +6 -0
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +2 -2
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/request-rich-content.d.ts +5 -1
- package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
- package/dist/runtime/query/request-rich-content.js +67 -25
- package/dist/runtime/query/request-rich-content.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +1 -1
- package/dist/tools/builtins/computer-use.d.ts.map +1 -1
- package/dist/tools/builtins/computer-use.js +12 -5
- package/dist/tools/builtins/computer-use.js.map +1 -1
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +30 -5
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/interface.d.ts +12 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/provider/stream.d.ts +1 -1
- package/dist/types/session/config.d.ts +14 -0
- package/dist/types/session/config.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +20 -5
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/package.json +1 -1
- package/src/compaction/verifier.ts +57 -20
- package/src/provider/capabilities.ts +17 -0
- package/src/provider/errors.ts +13 -0
- package/src/provider/fallback.ts +10 -0
- package/src/provider/idle-timeout.ts +5 -0
- package/src/provider/image-input-refusal.ts +54 -0
- package/src/provider/index.ts +2 -0
- package/src/provider/retry.ts +42 -4
- package/src/public-runtime.ts +3 -0
- package/src/runtime/query/deferred-sandbox.ts +110 -0
- package/src/runtime/query/executor.ts +25 -3
- package/src/runtime/query/index.ts +124 -49
- package/src/runtime/query/iteration/index.ts +188 -23
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +10 -8
- package/src/runtime/query/iteration/phases/compaction.ts +151 -35
- package/src/runtime/query/iteration/provider-rejected-image.ts +71 -5
- package/src/runtime/query/iteration/request-repair.ts +743 -0
- package/src/runtime/query/iteration/stream-turn.ts +11 -1
- package/src/runtime/query/request-rich-content.ts +77 -27
- package/src/tools/builtins/computer-use.ts +13 -4
- package/src/types/provider/errors.ts +33 -5
- package/src/types/provider/interface.ts +13 -0
- package/src/types/provider/stream.ts +1 -1
- package/src/types/session/config.ts +14 -0
- package/src/types/session/events.ts +25 -5
|
@@ -42,7 +42,11 @@ import { generateMessageId, generateToolCallId } from '../../../utils/id.js'
|
|
|
42
42
|
import type { Logger } from '../../../utils/logger.js'
|
|
43
43
|
import type { EmitEvent } from '../events.js'
|
|
44
44
|
import type { RequestImageIdentity } from '../request-rich-content.js'
|
|
45
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
type TextOnlyModelRecovery,
|
|
47
|
+
streamWithProviderRejectedImageRecovery,
|
|
48
|
+
} from './provider-rejected-image.js'
|
|
49
|
+
import type { RequestRepair } from './request-repair.js'
|
|
46
50
|
import {
|
|
47
51
|
type ParsedToolArguments,
|
|
48
52
|
capPartialArguments,
|
|
@@ -237,6 +241,10 @@ export async function* streamProviderTurn(
|
|
|
237
241
|
announceAs?: import('../../../types/ids/index.js').MessageId,
|
|
238
242
|
imageRecovery?: {
|
|
239
243
|
readonly onAccepted: (identity: RequestImageIdentity) => Promise<void>
|
|
244
|
+
/** A model refused the request for holding an image it cannot read; the retry sent none. */
|
|
245
|
+
readonly textOnly?: TextOnlyModelRecovery
|
|
246
|
+
/** Repairs and resends a request the provider rejects as invalid. */
|
|
247
|
+
readonly repair?: RequestRepair
|
|
240
248
|
},
|
|
241
249
|
captureRequest = false,
|
|
242
250
|
/** The session the turn belongs to, named on the chat span as `gen_ai.conversation.id`. */
|
|
@@ -363,6 +371,8 @@ export async function* streamProviderTurn(
|
|
|
363
371
|
streamParams,
|
|
364
372
|
imageRecovery.onAccepted,
|
|
365
373
|
observeRequest,
|
|
374
|
+
imageRecovery.textOnly,
|
|
375
|
+
imageRecovery.repair,
|
|
366
376
|
)
|
|
367
377
|
: provider.chatStream(streamParams)
|
|
368
378
|
) as AsyncIterable<StreamChunk>
|
|
@@ -34,7 +34,8 @@ interface RichOccurrence {
|
|
|
34
34
|
readonly source: 'user' | 'tool'
|
|
35
35
|
readonly kind: RichKind
|
|
36
36
|
readonly bytes: number
|
|
37
|
-
readonly
|
|
37
|
+
readonly mediaType?: string
|
|
38
|
+
readonly reason: 'budget' | 'provider-rejected' | 'invalid-image' | 'model-text-only'
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface RequestImageIdentity {
|
|
@@ -65,6 +66,24 @@ const invalidUserImageMarker = (): string =>
|
|
|
65
66
|
const invalidToolImageMarker = (): string =>
|
|
66
67
|
`[image omitted from this model request because the producing tool returned encoded bytes that are not a complete supported raster matching the declared media type. ${toolRecovery} Correct the image source before presenting it again.]`
|
|
67
68
|
|
|
69
|
+
/** `12 KB`-style size of an encoded payload, so the model can tell a thumbnail from a full screenshot. */
|
|
70
|
+
function describeEncodedSize(base64Length: number): string {
|
|
71
|
+
const bytes = Math.floor((base64Length * 3) / 4)
|
|
72
|
+
if (bytes < 1024) return `${bytes} B`
|
|
73
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`
|
|
74
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* What stands in for an image the current model cannot read. It says so, and
|
|
79
|
+
* what the image was, because the model otherwise sees a tool that reported
|
|
80
|
+
* success next to nothing and tends to retry it or claim to have seen the screen.
|
|
81
|
+
*/
|
|
82
|
+
const textOnlyImageMarker = (mediaType: string | undefined, base64Length: number): string =>
|
|
83
|
+
`[image omitted: the current model cannot read images${
|
|
84
|
+
mediaType ? ` (${mediaType}, ${describeEncodedSize(base64Length)})` : ''
|
|
85
|
+
}]`
|
|
86
|
+
|
|
68
87
|
function assertInlineAttachment(
|
|
69
88
|
attachment: MessageAttachment,
|
|
70
89
|
): asserts attachment is Exclude<MessageAttachment, { readonly type: 'stored' }> {
|
|
@@ -75,7 +94,10 @@ function assertInlineAttachment(
|
|
|
75
94
|
}
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
function collectRichOccurrences(
|
|
97
|
+
function collectRichOccurrences(
|
|
98
|
+
messages: readonly Message[],
|
|
99
|
+
imagesReadable: boolean,
|
|
100
|
+
): RichOccurrence[] {
|
|
79
101
|
const occurrences: RichOccurrence[] = []
|
|
80
102
|
|
|
81
103
|
for (const [messageIndex, message] of messages.entries()) {
|
|
@@ -88,13 +110,17 @@ function collectRichOccurrences(messages: readonly Message[]): RichOccurrence[]
|
|
|
88
110
|
source: 'user',
|
|
89
111
|
kind: attachment.type === 'document' ? 'document' : 'image',
|
|
90
112
|
bytes: attachment.data.length,
|
|
113
|
+
mediaType: attachment.mediaType,
|
|
91
114
|
reason:
|
|
92
|
-
attachment.type !== 'document' &&
|
|
93
|
-
? '
|
|
115
|
+
attachment.type !== 'document' && !imagesReadable
|
|
116
|
+
? 'model-text-only'
|
|
94
117
|
: attachment.type !== 'document' &&
|
|
95
|
-
attachment.modelOmission?.reason === '
|
|
96
|
-
? '
|
|
97
|
-
: '
|
|
118
|
+
attachment.modelOmission?.reason === 'invalid-image'
|
|
119
|
+
? 'invalid-image'
|
|
120
|
+
: attachment.type !== 'document' &&
|
|
121
|
+
attachment.modelOmission?.reason === 'provider-rejected'
|
|
122
|
+
? 'provider-rejected'
|
|
123
|
+
: 'budget',
|
|
98
124
|
})
|
|
99
125
|
}
|
|
100
126
|
continue
|
|
@@ -109,12 +135,15 @@ function collectRichOccurrences(messages: readonly Message[]): RichOccurrence[]
|
|
|
109
135
|
source: 'tool',
|
|
110
136
|
kind: block.type,
|
|
111
137
|
bytes: block.data.length,
|
|
138
|
+
mediaType: block.mediaType,
|
|
112
139
|
reason:
|
|
113
|
-
block.type === 'image' &&
|
|
114
|
-
? '
|
|
115
|
-
: block.type === 'image' && block.modelOmission?.reason === '
|
|
116
|
-
? '
|
|
117
|
-
: '
|
|
140
|
+
block.type === 'image' && !imagesReadable
|
|
141
|
+
? 'model-text-only'
|
|
142
|
+
: block.type === 'image' && block.modelOmission?.reason === 'invalid-image'
|
|
143
|
+
? 'invalid-image'
|
|
144
|
+
: block.type === 'image' && block.modelOmission?.reason === 'provider-rejected'
|
|
145
|
+
? 'provider-rejected'
|
|
146
|
+
: 'budget',
|
|
118
147
|
})
|
|
119
148
|
}
|
|
120
149
|
}
|
|
@@ -130,11 +159,13 @@ function appendTextMarkers(content: string, markers: readonly string[]): string
|
|
|
130
159
|
|
|
131
160
|
function appendMarkers(content: string, occurrences: readonly RichOccurrence[]): string {
|
|
132
161
|
const markers = occurrences.map((occurrence) =>
|
|
133
|
-
occurrence.reason === '
|
|
134
|
-
?
|
|
135
|
-
: occurrence.reason === '
|
|
136
|
-
?
|
|
137
|
-
:
|
|
162
|
+
occurrence.reason === 'model-text-only'
|
|
163
|
+
? textOnlyImageMarker(occurrence.mediaType, occurrence.bytes)
|
|
164
|
+
: occurrence.reason === 'provider-rejected'
|
|
165
|
+
? rejectedUserImageMarker()
|
|
166
|
+
: occurrence.reason === 'invalid-image'
|
|
167
|
+
? invalidUserImageMarker()
|
|
168
|
+
: userMarker(occurrence.kind),
|
|
138
169
|
)
|
|
139
170
|
return appendTextMarkers(content, markers)
|
|
140
171
|
}
|
|
@@ -148,8 +179,12 @@ function appendMarkers(content: string, occurrences: readonly RichOccurrence[]):
|
|
|
148
179
|
* original array when nothing is omitted makes the no-op and exact-boundary
|
|
149
180
|
* cases observable and keeps the common path allocation-free.
|
|
150
181
|
*/
|
|
151
|
-
export function projectRequestRichContent(
|
|
152
|
-
|
|
182
|
+
export function projectRequestRichContent(
|
|
183
|
+
messages: Message[],
|
|
184
|
+
maxBytes: number,
|
|
185
|
+
options: { readonly imagesReadable?: boolean } = {},
|
|
186
|
+
): Message[] {
|
|
187
|
+
const occurrences = collectRichOccurrences(messages, options.imagesReadable ?? true)
|
|
153
188
|
if (occurrences.length === 0) return messages
|
|
154
189
|
|
|
155
190
|
const omitted = new Map<number, Map<number, RichOccurrence>>()
|
|
@@ -163,9 +198,7 @@ export function projectRequestRichContent(messages: Message[], maxBytes: number)
|
|
|
163
198
|
}
|
|
164
199
|
|
|
165
200
|
for (const occurrence of occurrences) {
|
|
166
|
-
if (occurrence.reason
|
|
167
|
-
omit(occurrence)
|
|
168
|
-
}
|
|
201
|
+
if (occurrence.reason !== 'budget') omit(occurrence)
|
|
169
202
|
}
|
|
170
203
|
|
|
171
204
|
let total = occurrences.reduce(
|
|
@@ -206,11 +239,13 @@ export function projectRequestRichContent(messages: Message[], maxBytes: number)
|
|
|
206
239
|
? {
|
|
207
240
|
type: 'text',
|
|
208
241
|
text:
|
|
209
|
-
occurrence.reason === '
|
|
210
|
-
?
|
|
211
|
-
: occurrence.reason === '
|
|
212
|
-
?
|
|
213
|
-
:
|
|
242
|
+
occurrence.reason === 'model-text-only'
|
|
243
|
+
? textOnlyImageMarker(occurrence.mediaType, occurrence.bytes)
|
|
244
|
+
: occurrence.reason === 'provider-rejected'
|
|
245
|
+
? rejectedToolImageMarker()
|
|
246
|
+
: occurrence.reason === 'invalid-image'
|
|
247
|
+
? invalidToolImageMarker()
|
|
248
|
+
: toolMarker(occurrence.kind),
|
|
214
249
|
}
|
|
215
250
|
: block
|
|
216
251
|
})
|
|
@@ -221,6 +256,21 @@ export function projectRequestRichContent(messages: Message[], maxBytes: number)
|
|
|
221
256
|
})
|
|
222
257
|
}
|
|
223
258
|
|
|
259
|
+
/** Whether the provider-bound request carries an image anywhere, in a user attachment or a tool result. */
|
|
260
|
+
export function requestHasImages(messages: readonly Message[]): boolean {
|
|
261
|
+
for (const message of messages) {
|
|
262
|
+
if (message.role === 'user') {
|
|
263
|
+
for (const attachment of message.attachments ?? []) {
|
|
264
|
+
if (attachment.type !== 'stored' && attachment.type !== 'document') return true
|
|
265
|
+
}
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
if (message.role !== 'tool' || !Array.isArray(message.content)) continue
|
|
269
|
+
if (message.content.some((block) => block.type === 'image')) return true
|
|
270
|
+
}
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
|
|
224
274
|
function sameImage(
|
|
225
275
|
image: { readonly data: string; readonly mediaType: string },
|
|
226
276
|
identity: RequestImageIdentity,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { resolveProviderCapabilities } from '../../provider/capabilities.js'
|
|
2
|
+
import { modelReadsImages, resolveProviderCapabilities } from '../../provider/capabilities.js'
|
|
3
3
|
import type {
|
|
4
4
|
ComputerUseAction,
|
|
5
5
|
ComputerUseCapabilities,
|
|
@@ -105,10 +105,19 @@ export interface ComputerUseToolOptions {
|
|
|
105
105
|
* {@link ComputerUseToolOptions.unavailableReason}.
|
|
106
106
|
*/
|
|
107
107
|
export function computerUseUnavailableReason(
|
|
108
|
-
provider: Pick<LLMProvider, 'id' | 'capabilities'>,
|
|
108
|
+
provider: Pick<LLMProvider, 'id' | 'capabilities' | 'supportsImageInputFor'>,
|
|
109
|
+
model?: string,
|
|
109
110
|
): string | undefined {
|
|
110
|
-
if (resolveProviderCapabilities(provider).supportsToolResultImages)
|
|
111
|
-
|
|
111
|
+
if (!resolveProviderCapabilities(provider).supportsToolResultImages) {
|
|
112
|
+
return `The ${provider.id} provider cannot return images in tool results, so the model would never see a screenshot. Use a provider that can (for example Anthropic, Codex or Google) for computer use.`
|
|
113
|
+
}
|
|
114
|
+
// The driver can carry the image; the model behind it may still not read one.
|
|
115
|
+
// It would act on a screen it never saw, and the first screenshot would make
|
|
116
|
+
// the provider refuse every later request in the conversation.
|
|
117
|
+
if (model !== undefined && !modelReadsImages(provider, model)) {
|
|
118
|
+
return `The model ${model} cannot read images, so it would never see a screenshot. Use a model that reads images for computer use.`
|
|
119
|
+
}
|
|
120
|
+
return undefined
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
// ---------------------------------------------------------------------------
|
|
@@ -270,6 +270,14 @@ export function readOutputOverflow(err: unknown): OutputOverflowFigures | undefi
|
|
|
270
270
|
return undefined
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
/** Undici's `TypeError: terminated` anywhere on the chain: the body stream was cut. */
|
|
274
|
+
function isUndiciTermination(err: unknown): boolean {
|
|
275
|
+
for (const link of causeChain(err)) {
|
|
276
|
+
if ((link as unknown) instanceof TypeError && link.message === 'terminated') return true
|
|
277
|
+
}
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
|
|
273
281
|
/** Node/undici transport failures that mean "never reached the model". */
|
|
274
282
|
const NETWORK_ERRNOS: ReadonlySet<string> = new Set([
|
|
275
283
|
'ECONNRESET',
|
|
@@ -304,12 +312,19 @@ function codeFromStatus(status: number): ProviderErrorCode {
|
|
|
304
312
|
return 'unknown'
|
|
305
313
|
}
|
|
306
314
|
|
|
315
|
+
function overflowWordingAllowed(status: number | undefined): boolean {
|
|
316
|
+
return status === undefined || status === 400 || status === 413
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const CONTEXT_WINDOW_OVERFLOW =
|
|
320
|
+
/(?:exceed\w*|too (?:long|large|big)|larger than|longer than|over the|beyond|maximum|overflow\w*)\b[^.]{0,60}context window|context window[^.]{0,60}\b(?:exceed\w*|too (?:long|large|big)|larger than|longer than|over the|beyond|maximum|overflow\w*)/i
|
|
321
|
+
|
|
307
322
|
/**
|
|
308
323
|
* Message sniffing is the LAST resort, used only when there is no status
|
|
309
324
|
* and no errno. Vendors that tunnel a 400 through a 200 body (several
|
|
310
325
|
* gateways do) are only reachable this way.
|
|
311
326
|
*/
|
|
312
|
-
function codeFromMessage(message: string): ProviderErrorCode | undefined {
|
|
327
|
+
function codeFromMessage(message: string, status?: number): ProviderErrorCode | undefined {
|
|
313
328
|
const m = message.toLowerCase()
|
|
314
329
|
if (
|
|
315
330
|
m.includes('context length') ||
|
|
@@ -322,6 +337,13 @@ function codeFromMessage(message: string): ProviderErrorCode | undefined {
|
|
|
322
337
|
// overflow rescue tests for by exact equality. The turn died holding
|
|
323
338
|
// the remedy.
|
|
324
339
|
m.includes('too long for') ||
|
|
340
|
+
// The reference terminal's own wordings (its `context window` and
|
|
341
|
+
// `input length and max_tokens exceed context limit` matchers).
|
|
342
|
+
// Only where the status cannot say otherwise: a 429, 5xx or 401 that
|
|
343
|
+
// mentions the window is still a rate limit, a server fault or an auth
|
|
344
|
+
// failure, and must keep its retry and fallback behaviour.
|
|
345
|
+
(overflowWordingAllowed(status) &&
|
|
346
|
+
(CONTEXT_WINDOW_OVERFLOW.test(message) || m.includes('exceed context limit'))) ||
|
|
325
347
|
m.includes('maximum length') ||
|
|
326
348
|
m.includes('exceeds the maximum') ||
|
|
327
349
|
m.includes('input is too large') ||
|
|
@@ -498,7 +520,9 @@ export function classifyProviderError(
|
|
|
498
520
|
// usually generic ("request failed") — so matching only the outer text
|
|
499
521
|
// looks at the one string least likely to say anything.
|
|
500
522
|
const messages = chainMessages(err)
|
|
501
|
-
const fromMessage = messages
|
|
523
|
+
const fromMessage = messages
|
|
524
|
+
.map((m) => codeFromMessage(m, status))
|
|
525
|
+
.find((code) => code !== undefined)
|
|
502
526
|
// A first-hand statement from whoever produced the failure, if there
|
|
503
527
|
// is one. It outranks the code's inference, not the code itself: the
|
|
504
528
|
// classification still says WHAT went wrong.
|
|
@@ -522,7 +546,7 @@ export function classifyProviderError(
|
|
|
522
546
|
|
|
523
547
|
// A 400 that is really a window overflow must not be filed as a plain
|
|
524
548
|
// invalid_request: the caller can recover from one and not the other.
|
|
525
|
-
if (messages.some((m) => codeFromMessage(m) === 'context_length_exceeded')) {
|
|
549
|
+
if (messages.some((m) => codeFromMessage(m, status) === 'context_length_exceeded')) {
|
|
526
550
|
return new ProviderError({
|
|
527
551
|
code: 'context_length_exceeded',
|
|
528
552
|
message,
|
|
@@ -566,8 +590,12 @@ export function classifyProviderError(
|
|
|
566
590
|
}
|
|
567
591
|
}
|
|
568
592
|
|
|
569
|
-
// `fetch` rejects with a bare TypeError on transport failure
|
|
570
|
-
|
|
593
|
+
// `fetch` rejects with a bare TypeError on transport failure, and undici
|
|
594
|
+
// ends a response body the peer cut mid-stream with `TypeError:
|
|
595
|
+
// terminated`. Its socket error normally rides along as the cause and the
|
|
596
|
+
// errno walk above catches it, but a vendor SDK that re-wraps the stream
|
|
597
|
+
// error can drop that cause and leave only the bare word.
|
|
598
|
+
if ((err instanceof TypeError && /fetch|network/i.test(message)) || isUndiciTermination(err)) {
|
|
571
599
|
return new ProviderError({
|
|
572
600
|
code: 'network',
|
|
573
601
|
message,
|
|
@@ -22,6 +22,19 @@ export interface LLMProvider {
|
|
|
22
22
|
/** Refine hosted search support for a model and mode. Absent uses the driver-wide declaration. */
|
|
23
23
|
supportsHostedWebSearchFor?(model: string, mode: 'live' | 'cached'): boolean
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Whether `model` reads images. `capabilities.supportsVision` is a statement
|
|
27
|
+
* about the DRIVER (does it map image parts onto the wire); this is about the
|
|
28
|
+
* MODEL behind it, which only the driver's catalogue can know: one driver
|
|
29
|
+
* serves vision and text-only models side by side, and a text-only model
|
|
30
|
+
* refuses the whole request when it finds an image in it.
|
|
31
|
+
*
|
|
32
|
+
* Answer `false` only when the model is known to be text-only. Absent, or
|
|
33
|
+
* `true`, means "send images": the runtime also recovers from a refusal, so a
|
|
34
|
+
* driver with no catalogue loses nothing by omitting this.
|
|
35
|
+
*/
|
|
36
|
+
supportsImageInputFor?(model: string): boolean
|
|
37
|
+
|
|
25
38
|
/**
|
|
26
39
|
* Retry behaviour this DRIVER wants, when the generic default is wrong
|
|
27
40
|
* for the vendor behind it.
|
|
@@ -174,7 +174,7 @@ export interface ProviderRetryNotice {
|
|
|
174
174
|
/** The delay came from the server's own `Retry-After`, not backoff. */
|
|
175
175
|
readonly serverDirected: boolean
|
|
176
176
|
/**
|
|
177
|
-
* The failed attempt had already streamed text or reasoning, and the request
|
|
177
|
+
* The failed attempt had already streamed text or reasoning (reasoning alone is retried without any option), and the request
|
|
178
178
|
* is being sent again from the start. Whatever that attempt produced is void
|
|
179
179
|
* and the consumer must drop it before the next chunk.
|
|
180
180
|
*/
|
|
@@ -88,6 +88,20 @@ export interface TurnConfig {
|
|
|
88
88
|
* behaviour every sandboxed turn had before this existed.
|
|
89
89
|
*/
|
|
90
90
|
workspace?: 'ephemeral' | 'working-directory'
|
|
91
|
+
/**
|
|
92
|
+
* When the turn acquires its sandbox. Absent means `'eager'`: acquired,
|
|
93
|
+
* and waited for, before the input guardrails and the first model
|
|
94
|
+
* request.
|
|
95
|
+
*
|
|
96
|
+
* `'concurrent'` starts at the same point but lets the first model
|
|
97
|
+
* request go out while the provider allocates; the first tool call
|
|
98
|
+
* waits for the handle. `'on-first-use'` does not start until the first
|
|
99
|
+
* tool call. Either way `sandbox_created` is emitted when the handle
|
|
100
|
+
* arrives, and a failure or timeout ends the turn with the stop reason
|
|
101
|
+
* an eager acquisition would have produced, at the first tool call or
|
|
102
|
+
* at turn end, whichever comes first.
|
|
103
|
+
*/
|
|
104
|
+
acquire?: 'eager' | 'concurrent' | 'on-first-use'
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
/**
|
|
@@ -280,8 +280,11 @@ type CoreSessionEvent =
|
|
|
280
280
|
* a provider that rejects the pairing. A reducer bug, and one
|
|
281
281
|
* `findSafeTrimIndex` exists to prevent.
|
|
282
282
|
* - `breaker_open` — only with `compactionConfig.circuitBreaker`:
|
|
283
|
-
* three passes in a row failed, so
|
|
284
|
-
* the rest of the turn
|
|
283
|
+
* three passes in a row failed, so proactive compaction is off for
|
|
284
|
+
* the rest of the turn; a context overflow the provider reports
|
|
285
|
+
* still runs the reactive relief. Sent once, without `error`: it is
|
|
286
|
+
* for a host's records, not a line for the person. `messages` is
|
|
287
|
+
* the live count.
|
|
285
288
|
* - `refilling` — only with `compactionConfig.circuitBreaker`: the
|
|
286
289
|
* context filled back up within three iterations of a compaction,
|
|
287
290
|
* three times in a row, and the turn is stopping. Sent once.
|
|
@@ -289,7 +292,7 @@ type CoreSessionEvent =
|
|
|
289
292
|
cause: 'reducer_threw' | 'shed_nothing' | 'split_tool_pair' | 'breaker_open' | 'refilling'
|
|
290
293
|
/** Unchanged, and stated so a reader need not infer it. */
|
|
291
294
|
messages: number
|
|
292
|
-
/** For `reducer_threw`, the reducer's message; for `
|
|
295
|
+
/** For `reducer_threw`, the reducer's message; for `refilling`, the one line to show. */
|
|
293
296
|
error?: string
|
|
294
297
|
}
|
|
295
298
|
| {
|
|
@@ -714,9 +717,26 @@ type CoreSessionEvent =
|
|
|
714
717
|
sessionId: SessionId
|
|
715
718
|
turnId?: TurnId
|
|
716
719
|
capability: 'tools' | 'vision' | 'documents'
|
|
717
|
-
/**
|
|
718
|
-
|
|
720
|
+
/**
|
|
721
|
+
* Present when the mismatch was not about the user's own message:
|
|
722
|
+
* `'tool-result'` after a tool executed; `'history'` when the model
|
|
723
|
+
* itself turned out not to read images (see `model`) and the images
|
|
724
|
+
* already in the conversation were left out of the request;
|
|
725
|
+
* `'media-removed'` when the provider rejected an image or document
|
|
726
|
+
* and it was left out of the request that then went through;
|
|
727
|
+
* `'media-removed-current'` when that included a file attached to the
|
|
728
|
+
* latest user message; `'media-limit'` when the oldest files were left
|
|
729
|
+
* out only to fit a count the provider accepts.
|
|
730
|
+
*/
|
|
731
|
+
contentSource?:
|
|
732
|
+
| 'tool-result'
|
|
733
|
+
| 'history'
|
|
734
|
+
| 'media-removed'
|
|
735
|
+
| 'media-removed-current'
|
|
736
|
+
| 'media-limit'
|
|
719
737
|
providerId: string
|
|
738
|
+
/** The model that cannot read images, when the warning is about one model rather than the driver. */
|
|
739
|
+
model?: string
|
|
720
740
|
message: string
|
|
721
741
|
}
|
|
722
742
|
/**
|