@qilin-agent/llm 0.0.0-stage → 3.1.4
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/LICENSE +21 -0
- package/README.i18n.yaml +64 -0
- package/README.md +177 -2
- package/README.zh.md +178 -0
- package/lib/index.js +2388 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +626 -0
- package/lib/typert.remote-client.d.ts +26 -0
- package/lib/typert.remote-client.js +109 -0
- package/lib/types/adapter-failure.d.ts +14 -0
- package/lib/types/adapter-failure.js +108 -0
- package/lib/types/api-key.d.ts +28 -0
- package/lib/types/api-key.js +34 -0
- package/lib/types/assembler.d.ts +75 -0
- package/lib/types/assembler.js +191 -0
- package/lib/types/assistant-stream.d.ts +166 -0
- package/lib/types/assistant-stream.js +458 -0
- package/lib/types/attribution.d.ts +47 -0
- package/lib/types/attribution.js +46 -0
- package/lib/types/brand.d.ts +56 -0
- package/lib/types/brand.js +53 -0
- package/lib/types/call-config.d.ts +53 -0
- package/lib/types/call-config.js +46 -0
- package/lib/types/content.d.ts +149 -0
- package/lib/types/content.js +355 -0
- package/lib/types/error.d.ts +81 -0
- package/lib/types/error.js +153 -0
- package/lib/types/index.d.ts +416 -0
- package/lib/types/index.js +948 -0
- package/lib/types/message.d.ts +246 -0
- package/lib/types/message.js +101 -0
- package/lib/types/retry-policy.d.ts +66 -0
- package/lib/types/retry-policy.js +127 -0
- package/lib/types/types.d.ts +532 -0
- package/lib/types/types.js +7 -0
- package/package.json +74 -3
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/** Content-block structure helpers. @module @qilin-agent/llm/content */
|
|
2
|
+
import { assertNever } from '@qilin-agent/util-values';
|
|
3
|
+
/**
|
|
4
|
+
* Bridge one attachment provider's host object location into the mounted
|
|
5
|
+
* tool execution world. The consumer supplies the current filesystem
|
|
6
|
+
* provider's mapping without making attachment or LLM definitions depend on it.
|
|
7
|
+
* @param attachments - provider that owns the normalized attachment object.
|
|
8
|
+
* @param mapHostPath - map one absolute host path into the current tool execution world.
|
|
9
|
+
* @param ref - durable normalized attachment reference.
|
|
10
|
+
* @returns a read-only execution-world path, or undefined when either provider exposes no mapping.
|
|
11
|
+
* @throws an attachment error when the durable reference is invalid.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveImageAttachmentAccess(attachments, mapHostPath, ref) {
|
|
14
|
+
const hostPath = attachments.imageHostPath(ref);
|
|
15
|
+
if (hostPath === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
const readonlyPath = mapHostPath(hostPath);
|
|
18
|
+
return readonlyPath === undefined ? undefined : { readonlyPath };
|
|
19
|
+
}
|
|
20
|
+
function quoted(value) {
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
function imageIdentity(ref) {
|
|
24
|
+
return ref.name === undefined
|
|
25
|
+
? String(ref.attachmentId)
|
|
26
|
+
: `${quoted(ref.name)} (${ref.attachmentId})`;
|
|
27
|
+
}
|
|
28
|
+
function extension(mediaType) {
|
|
29
|
+
switch (mediaType) {
|
|
30
|
+
case 'image/png': return '.png';
|
|
31
|
+
case 'image/jpeg': return '.jpg';
|
|
32
|
+
case 'image/webp': return '.webp';
|
|
33
|
+
case 'image/gif': return '.gif';
|
|
34
|
+
default: return assertNever(mediaType, 'image extension');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function normalizedAccessText(ref, access) {
|
|
38
|
+
return ` Normalized copy (read-only; may be resized or re-encoded): ${quoted(access.readonlyPath)} (${ref.width}x${ref.height}px, ${ref.mediaType}).`
|
|
39
|
+
+ ' Source dimensions, format, and byte size may differ.'
|
|
40
|
+
+ ` Copy to a writable path ending in ${extension(ref.mediaType)} before editing.`;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stable text shown to a model that cannot accept one durable image reference.
|
|
44
|
+
* @param ref - durable normalized attachment omitted from the request.
|
|
45
|
+
* @returns deterministic text-only placeholder.
|
|
46
|
+
*/
|
|
47
|
+
export function textOnlyImageText(ref) {
|
|
48
|
+
const digest = String(ref.attachmentId).slice('sha256:'.length, 'sha256:'.length + 8);
|
|
49
|
+
return `[image omitted because this model accepts text only; attachment sha256:${digest}]`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stable model-facing handle for one exact request image. Identity comes from
|
|
53
|
+
* the occurrence's own durable reference: request versions are prepared per
|
|
54
|
+
* attachment id, so one shared version may serve occurrences whose display
|
|
55
|
+
* names differ.
|
|
56
|
+
* @param ref - the occurrence's durable normalized attachment.
|
|
57
|
+
* @param version - exact request-image dimensions shown beside the text.
|
|
58
|
+
* @param access - optional path resolved for the current tool execution world.
|
|
59
|
+
* @returns attachment handle and request-image dimensions.
|
|
60
|
+
*/
|
|
61
|
+
export function requestImageHandleText(ref, version, access) {
|
|
62
|
+
const preview = `Image ${imageIdentity(ref)}; request preview ${version.width}x${version.height}px.`;
|
|
63
|
+
return access === undefined
|
|
64
|
+
? `${preview} It may be resized or re-encoded; source dimensions, format, and byte size may differ.`
|
|
65
|
+
: preview + normalizedAccessText(ref, access);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Stable per-image placeholder for a request-limit omission.
|
|
69
|
+
* @param ref - durable normalized attachment omitted from this request.
|
|
70
|
+
* @param access - optional provider-resolved path for model tools.
|
|
71
|
+
* @returns identity, normalized metadata, and the available recovery path.
|
|
72
|
+
*/
|
|
73
|
+
export function offloadedImageText(ref, access) {
|
|
74
|
+
const identity = `image omitted to fit request image limits; ${imageIdentity(ref)}.`;
|
|
75
|
+
if (access === undefined) {
|
|
76
|
+
return `[${identity} No local normalized image path is available; ask the user to attach it again if needed.]`;
|
|
77
|
+
}
|
|
78
|
+
return `[${identity}${normalizedAccessText(ref, access)}]`;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* True when typed model content contains an image block. This is the one image
|
|
82
|
+
* walk shared by every image policy (capability gating, text-only
|
|
83
|
+
* serialization, compaction survey), so a consumer cannot silently diverge.
|
|
84
|
+
* @param content - typed model content blocks.
|
|
85
|
+
* @returns whether any block is an image.
|
|
86
|
+
*/
|
|
87
|
+
export function contentHasImage(content) {
|
|
88
|
+
return content.some(block => block.type === 'image');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* True when typed model content contains a file block.
|
|
92
|
+
* Reads current content on every call without retaining scan results.
|
|
93
|
+
* @param content - typed model content blocks.
|
|
94
|
+
* @returns whether any block is a file.
|
|
95
|
+
*/
|
|
96
|
+
export function contentHasFile(content) {
|
|
97
|
+
for (const block of content) {
|
|
98
|
+
if (block.type === 'file')
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Stable model-facing handle for one durable file reference: the address of
|
|
105
|
+
* the verbatim stored copy and the instruction to read it on demand. This is
|
|
106
|
+
* the only representation a provider ever receives for a file.
|
|
107
|
+
* @param ref - durable verbatim file reference.
|
|
108
|
+
* @param readonlyPath - execution-world path of the stored copy, when resolvable.
|
|
109
|
+
* @returns deterministic handle text naming the file, its size, and its address.
|
|
110
|
+
*/
|
|
111
|
+
export function fileHandleText(ref, readonlyPath) {
|
|
112
|
+
const digest = String(ref.attachmentId).slice('sha256:'.length, 'sha256:'.length + 8);
|
|
113
|
+
const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
|
|
114
|
+
if (readonlyPath === undefined) {
|
|
115
|
+
return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
116
|
+
}
|
|
117
|
+
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
118
|
+
}
|
|
119
|
+
/** Replace every file occurrence with handle text. */
|
|
120
|
+
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
121
|
+
let next;
|
|
122
|
+
for (const [index, block] of blocks.entries()) {
|
|
123
|
+
if (block.type === 'file') {
|
|
124
|
+
next ??= blocks.slice(0, index);
|
|
125
|
+
next.push({ type: 'text', text: fileHandleText(block.attachment, resolvePath(block.attachment)) });
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
next?.push(block);
|
|
129
|
+
}
|
|
130
|
+
return next ?? blocks;
|
|
131
|
+
}
|
|
132
|
+
export function projectFilesToText(messages, resolvePath) {
|
|
133
|
+
if (!messages.some(message => contentHasFile(message.content)))
|
|
134
|
+
return messages;
|
|
135
|
+
return messages.map((message) => {
|
|
136
|
+
const content = replaceFilesWithHandles(message.content, resolvePath);
|
|
137
|
+
return content === message.content ? message : { ...message, content };
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Base64 length of raw image bytes, including padding. */
|
|
141
|
+
function base64Length(bytes) {
|
|
142
|
+
return Math.ceil(bytes / 3) * 4;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Visit every image occurrence of typed content in message order.
|
|
146
|
+
* @param content - typed model content blocks.
|
|
147
|
+
* @param visit - called once per occurrence.
|
|
148
|
+
*/
|
|
149
|
+
function visitImageBlocks(content, visit) {
|
|
150
|
+
for (const block of content) {
|
|
151
|
+
if (block.type === 'image')
|
|
152
|
+
visit(block);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Replace every offloaded occurrence with its placeholder. */
|
|
156
|
+
function replaceOffloadedImages(blocks, placeholder) {
|
|
157
|
+
let next;
|
|
158
|
+
for (const [index, block] of blocks.entries()) {
|
|
159
|
+
if (block.type === 'image' && block.offloaded === true) {
|
|
160
|
+
next ??= blocks.slice(0, index);
|
|
161
|
+
next.push({ type: 'text', text: placeholder(block.attachment) });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
next?.push(block);
|
|
165
|
+
}
|
|
166
|
+
return next ?? blocks;
|
|
167
|
+
}
|
|
168
|
+
export function projectOffloadedImages(messages, placeholder) {
|
|
169
|
+
return messages.map((message) => {
|
|
170
|
+
const content = replaceOffloadedImages(message.content, placeholder);
|
|
171
|
+
return content === message.content ? message : { ...message, content };
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Number of oldest retained image occurrences one route budget removes, in
|
|
176
|
+
* whole count and byte quanta, once the budget is exceeded. The result depends
|
|
177
|
+
* only on the represented lengths, so every route names the count the same
|
|
178
|
+
* way.
|
|
179
|
+
* @param lengths - represented byte length of every retained occurrence, oldest first.
|
|
180
|
+
* @param budget - count/byte budgets and removal quanta; unbounded when absent.
|
|
181
|
+
* @returns how many leading occurrences to offload.
|
|
182
|
+
*/
|
|
183
|
+
function offloadedImagePrefixCount(lengths, budget) {
|
|
184
|
+
const total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
185
|
+
const excessCount = budget.maxImages === undefined ? 0 : Math.max(0, lengths.length - budget.maxImages);
|
|
186
|
+
const excessBytes = budget.maxBytes === undefined ? 0 : Math.max(0, total - budget.maxBytes);
|
|
187
|
+
if (excessCount === 0 && excessBytes === 0)
|
|
188
|
+
return 0;
|
|
189
|
+
const countQuantum = budget.countQuantum ?? 1;
|
|
190
|
+
const byteQuantum = budget.byteQuantum ?? 1;
|
|
191
|
+
const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
|
|
192
|
+
const removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;
|
|
193
|
+
let count = 0;
|
|
194
|
+
let removedBytes = 0;
|
|
195
|
+
for (const imageBytes of lengths) {
|
|
196
|
+
const byteTargetMet = removeBytes === 0
|
|
197
|
+
|| (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes);
|
|
198
|
+
if (count >= removeCount && byteTargetMet)
|
|
199
|
+
break;
|
|
200
|
+
removedBytes += imageBytes;
|
|
201
|
+
count += 1;
|
|
202
|
+
}
|
|
203
|
+
return count;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Number of oldest retained occurrences a route must still offload before a
|
|
207
|
+
* derived request fits its budget at the exact byte length the route sends;
|
|
208
|
+
* zero when the request fits. A route fails with `IMAGE_OFFLOAD_REQUIRED`
|
|
209
|
+
* carrying this count instead of offloading on its own.
|
|
210
|
+
* @param messages - derived request history carrying the surface's `offloaded` marks.
|
|
211
|
+
* @param budget - route representation, budgets, and removal quanta.
|
|
212
|
+
* @param versionBytes - exact request-version byte length of one retained occurrence.
|
|
213
|
+
* @returns how many more leading retained occurrences to offload.
|
|
214
|
+
*/
|
|
215
|
+
export function requiredImageOffload(messages, budget, versionBytes) {
|
|
216
|
+
const lengths = [];
|
|
217
|
+
for (const message of messages) {
|
|
218
|
+
visitImageBlocks(message.content, (block) => {
|
|
219
|
+
if (block.offloaded === true)
|
|
220
|
+
return;
|
|
221
|
+
const bytes = versionBytes(block);
|
|
222
|
+
lengths.push(budget.representation === 'base64' ? base64Length(bytes) : bytes);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return offloadedImagePrefixCount(lengths, budget);
|
|
226
|
+
}
|
|
227
|
+
/** Replace every image occurrence for a text-only model. */
|
|
228
|
+
function replaceImagesForTextModel(blocks) {
|
|
229
|
+
let next;
|
|
230
|
+
for (const [index, block] of blocks.entries()) {
|
|
231
|
+
if (block.type === 'image') {
|
|
232
|
+
next ??= blocks.slice(0, index);
|
|
233
|
+
next.push({ type: 'text', text: textOnlyImageText(block.attachment) });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
next?.push(block);
|
|
237
|
+
}
|
|
238
|
+
return next ?? blocks;
|
|
239
|
+
}
|
|
240
|
+
export function projectImagesForTextModel(messages) {
|
|
241
|
+
if (!messages.some(message => contentHasImage(message.content)))
|
|
242
|
+
return messages;
|
|
243
|
+
return messages.map((message) => {
|
|
244
|
+
const content = replaceImagesForTextModel(message.content);
|
|
245
|
+
return content === message.content ? message : { ...message, content };
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function withoutDeveloperMessages(messages) {
|
|
249
|
+
const retained = messages.filter(message => message.role !== 'developer');
|
|
250
|
+
return retained.length === messages.length ? messages : retained;
|
|
251
|
+
}
|
|
252
|
+
function toolDeclarations(tools, mode, history) {
|
|
253
|
+
const declarations = new Map(history.tools.map(tool => [tool.name, tool]));
|
|
254
|
+
for (const update of history.updates) {
|
|
255
|
+
for (const tool of update.additions) {
|
|
256
|
+
if (!declarations.has(tool.name)) {
|
|
257
|
+
// Later additions activate these definitions at their recorded position.
|
|
258
|
+
declarations.set(tool.name, { ...tool, deferLoading: true });
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
switch (mode) {
|
|
263
|
+
case 'in-history':
|
|
264
|
+
// Removal blocks disable tools without discarding their historical definitions.
|
|
265
|
+
return declarations;
|
|
266
|
+
case 'addition-only': {
|
|
267
|
+
// Without removal support, the declaration list must omit inactive tools.
|
|
268
|
+
const activeNames = new Set(tools?.map(tool => tool.name));
|
|
269
|
+
for (const name of declarations.keys()) {
|
|
270
|
+
if (!activeNames.has(name))
|
|
271
|
+
declarations.delete(name);
|
|
272
|
+
}
|
|
273
|
+
return declarations;
|
|
274
|
+
}
|
|
275
|
+
/* v8 ignore next 2 -- closed-union exhaustiveness guard */
|
|
276
|
+
default:
|
|
277
|
+
return assertNever(mode);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Construct provider declarations from session-folded history without changing logged active tools.
|
|
282
|
+
* Unsupported routes and incomplete history use current declarations without developer updates.
|
|
283
|
+
* Explicitly deferred baseline tools become available only after their first retained addition.
|
|
284
|
+
* @param messages - complete request inputs, or the prefix selected for an auxiliary call.
|
|
285
|
+
* @param tools - currently active tool schemas.
|
|
286
|
+
* @param toolUpdate - the resolved route's update mode.
|
|
287
|
+
* @param history - immutable state folded from committed headers and developer messages.
|
|
288
|
+
* @returns provider declarations and the corresponding filtered history.
|
|
289
|
+
*/
|
|
290
|
+
export function projectToolUpdates(messages, tools, toolUpdate, history) {
|
|
291
|
+
if (toolUpdate === undefined) {
|
|
292
|
+
// Unsupported routes need immediately available tools and no update messages.
|
|
293
|
+
let immediateTools = tools;
|
|
294
|
+
if (tools?.some(tool => tool.deferLoading === true)) {
|
|
295
|
+
immediateTools = tools.map(({ deferLoading: _loading, ...tool }) => tool);
|
|
296
|
+
}
|
|
297
|
+
return { messages: withoutDeveloperMessages(messages), tools: immediateTools };
|
|
298
|
+
}
|
|
299
|
+
if (history === undefined) {
|
|
300
|
+
// Current schemas alone cannot resolve definitions referenced by past updates.
|
|
301
|
+
return { messages: withoutDeveloperMessages(messages), tools };
|
|
302
|
+
}
|
|
303
|
+
const messageIds = new Set(messages.flatMap(message => message.role === 'developer' ? [message.id] : []));
|
|
304
|
+
if (history.updates.some(update => !messageIds.has(update.messageId))) {
|
|
305
|
+
// An auxiliary prefix may omit updates needed to activate historical declarations.
|
|
306
|
+
return { messages: withoutDeveloperMessages(messages), tools };
|
|
307
|
+
}
|
|
308
|
+
const declarations = toolDeclarations(tools, toolUpdate, history);
|
|
309
|
+
const updateIds = new Set(history.updates.map(update => update.messageId));
|
|
310
|
+
// Deferred baseline tools still need their first addition to become available.
|
|
311
|
+
const offered = new Set(history.tools.filter(tool => !tool.deferLoading).map(tool => tool.name));
|
|
312
|
+
const projectedMessages = [];
|
|
313
|
+
for (const message of messages) {
|
|
314
|
+
if (message.role !== 'developer') {
|
|
315
|
+
projectedMessages.push(message);
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Earlier declaration series do not govern the current tool set.
|
|
319
|
+
if (!updateIds.has(message.id))
|
|
320
|
+
continue;
|
|
321
|
+
const content = message.content.filter((block) => {
|
|
322
|
+
switch (block.type) {
|
|
323
|
+
case 'tool-addition':
|
|
324
|
+
// Only declared tools that are not already available need activation.
|
|
325
|
+
if (!declarations.has(block.toolName) || offered.has(block.toolName))
|
|
326
|
+
return false;
|
|
327
|
+
offered.add(block.toolName);
|
|
328
|
+
return true;
|
|
329
|
+
case 'tool-removal':
|
|
330
|
+
// Addition-only routes cannot deactivate a tool through history.
|
|
331
|
+
if (toolUpdate !== 'in-history')
|
|
332
|
+
return false;
|
|
333
|
+
return offered.delete(block.toolName);
|
|
334
|
+
default:
|
|
335
|
+
// Other core and plugin-defined blocks retain their content and order.
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
if (content.length === 0)
|
|
340
|
+
continue;
|
|
341
|
+
if (content.length === message.content.length) {
|
|
342
|
+
projectedMessages.push(message);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
projectedMessages.push({ ...message, content });
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const unchanged = projectedMessages.length === messages.length
|
|
349
|
+
&& projectedMessages.every((message, index) => message === messages[index]);
|
|
350
|
+
return {
|
|
351
|
+
messages: unchanged ? messages : projectedMessages,
|
|
352
|
+
tools: [...declarations.values()],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness error base with a stable machine-routable code and chained cause.
|
|
3
|
+
* Package errors extend it so tool results and replay can retain failure class.
|
|
4
|
+
* @module @qilin-agent/llm/error
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
8
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the
|
|
9
|
+
* human-readable `message`, and supports `cause` chaining via the standard
|
|
10
|
+
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
11
|
+
*/
|
|
12
|
+
export declare class HarnessError extends Error {
|
|
13
|
+
/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */
|
|
14
|
+
readonly code: string;
|
|
15
|
+
constructor(message: string, code: string, options?: ErrorOptions);
|
|
16
|
+
}
|
|
17
|
+
/** Canonical provider-neutral code for a model request rejected because its context window was exceeded. */
|
|
18
|
+
export declare const CONTEXT_WINDOW_EXCEEDED_CODE = "CONTEXT_WINDOW_EXCEEDED";
|
|
19
|
+
/** Canonical provider-neutral code for an exhausted account quota or balance. */
|
|
20
|
+
export declare const QUOTA_EXCEEDED_CODE = "QUOTA";
|
|
21
|
+
/**
|
|
22
|
+
* Canonical provider-neutral code for a response that completed normally but
|
|
23
|
+
* carried no content blocks at all. Providers occasionally emit a degenerate
|
|
24
|
+
* completion (a terminal stop with zero output); adapters classify it as this
|
|
25
|
+
* failure instead of yielding an empty assistant message, because an empty
|
|
26
|
+
* message silently ends the turn with nothing for the user or the loop to act
|
|
27
|
+
* on. The attempt produced nothing durable, so retry policy treats it as safe
|
|
28
|
+
* to repeat.
|
|
29
|
+
*/
|
|
30
|
+
export declare const EMPTY_RESPONSE_CODE = "EMPTY_RESPONSE";
|
|
31
|
+
/**
|
|
32
|
+
* Canonical provider-neutral code for a credential that was supplied but
|
|
33
|
+
* cannot be used — malformed rather than absent. Distinct from
|
|
34
|
+
* `MISSING_CREDENTIAL` because the fix differs: correct the stored value
|
|
35
|
+
* rather than supply one. Deliberately outside the default retryable set —
|
|
36
|
+
* a malformed credential fails identically on every attempt.
|
|
37
|
+
*/
|
|
38
|
+
export declare const INVALID_CREDENTIAL_CODE = "INVALID_CREDENTIAL";
|
|
39
|
+
/**
|
|
40
|
+
* Recognize the context-overflow wording used by OpenAI-compatible providers
|
|
41
|
+
* and library adapters. Adapters pass all available provider code, type, and
|
|
42
|
+
* message text so both thrown and in-band delivery styles share one classifier.
|
|
43
|
+
* @param detail - provider error code/type/message text joined into one string.
|
|
44
|
+
* @returns true when the detail identifies a request exceeding the model context window.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isContextWindowExceededError(detail: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Recognize provider wording that identifies an exhausted account quota rather
|
|
49
|
+
* than a transient request-rate limit.
|
|
50
|
+
* @param detail - provider error code/type/message text joined into one string.
|
|
51
|
+
* @returns true only for terminal quota, balance, credit, budget, or usage-limit wording.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isQuotaExceededError(detail: string): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Render a thrown value with its full `cause` chain and AggregateError
|
|
56
|
+
* members, so transport wrappers like undici's `TypeError: fetch failed`
|
|
57
|
+
* surface the underlying failure instead of masking it. Plain structured
|
|
58
|
+
* failures render their own data-backed `message`. Diagnostic-surface
|
|
59
|
+
* rendering only (messages, notices, logs) — never parse the result; route on
|
|
60
|
+
* {@link HarnessError.code}.
|
|
61
|
+
* @param value - the caught value (`unknown` in catch clauses).
|
|
62
|
+
* @returns the outermost message first, each cause appended with `: ` (skipped
|
|
63
|
+
* when it repeats the wrapper message verbatim), and AggregateError members
|
|
64
|
+
* bracketed and `; `-joined.
|
|
65
|
+
*/
|
|
66
|
+
export declare function errorChain(value: unknown): string;
|
|
67
|
+
/**
|
|
68
|
+
* Narrow an arbitrary thrown value to a HarnessError (for `instanceof` at runtime boundaries).
|
|
69
|
+
* @param value - the caught value (`unknown` in catch clauses).
|
|
70
|
+
* @returns true only for real instances; duck-typed or cross-realm errors do not narrow.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isHarnessError(value: unknown): value is HarnessError;
|
|
73
|
+
/**
|
|
74
|
+
* Canonical code for a request an image-capable route cannot send until more
|
|
75
|
+
* of its images are offloaded. The failure's `offloadImages` names how many
|
|
76
|
+
* more of the oldest retained occurrences must be offloaded;
|
|
77
|
+
* `dsh-compaction-image-offload` records an `image/offload` selection before
|
|
78
|
+
* the agent or summarizer retries with freshly derived input.
|
|
79
|
+
*/
|
|
80
|
+
export declare const IMAGE_OFFLOAD_REQUIRED_CODE = "IMAGE_OFFLOAD_REQUIRED";
|
|
81
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness error base with a stable machine-routable code and chained cause.
|
|
3
|
+
* Package errors extend it so tool results and replay can retain failure class.
|
|
4
|
+
* @module @qilin-agent/llm/error
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Base class for all harness errors. Carries a `code` (stable, programmatic —
|
|
8
|
+
* e.g. `NO_ADAPTER`, `INVALID_ARGS`, `INVARIANT`) distinct from the
|
|
9
|
+
* human-readable `message`, and supports `cause` chaining via the standard
|
|
10
|
+
* `ErrorOptions`. `name` defaults to the subclass constructor name.
|
|
11
|
+
*/
|
|
12
|
+
export class HarnessError extends Error {
|
|
13
|
+
/** Stable machine-routable failure class (e.g. `RATE_LIMIT`); route on this, never by parsing `message`. */
|
|
14
|
+
code;
|
|
15
|
+
constructor(message, code, options) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.name = new.target.name;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Canonical provider-neutral code for a model request rejected because its context window was exceeded. */
|
|
22
|
+
export const CONTEXT_WINDOW_EXCEEDED_CODE = 'CONTEXT_WINDOW_EXCEEDED';
|
|
23
|
+
/** Canonical provider-neutral code for an exhausted account quota or balance. */
|
|
24
|
+
export const QUOTA_EXCEEDED_CODE = 'QUOTA';
|
|
25
|
+
/**
|
|
26
|
+
* Canonical provider-neutral code for a response that completed normally but
|
|
27
|
+
* carried no content blocks at all. Providers occasionally emit a degenerate
|
|
28
|
+
* completion (a terminal stop with zero output); adapters classify it as this
|
|
29
|
+
* failure instead of yielding an empty assistant message, because an empty
|
|
30
|
+
* message silently ends the turn with nothing for the user or the loop to act
|
|
31
|
+
* on. The attempt produced nothing durable, so retry policy treats it as safe
|
|
32
|
+
* to repeat.
|
|
33
|
+
*/
|
|
34
|
+
export const EMPTY_RESPONSE_CODE = 'EMPTY_RESPONSE';
|
|
35
|
+
/**
|
|
36
|
+
* Canonical provider-neutral code for a credential that was supplied but
|
|
37
|
+
* cannot be used — malformed rather than absent. Distinct from
|
|
38
|
+
* `MISSING_CREDENTIAL` because the fix differs: correct the stored value
|
|
39
|
+
* rather than supply one. Deliberately outside the default retryable set —
|
|
40
|
+
* a malformed credential fails identically on every attempt.
|
|
41
|
+
*/
|
|
42
|
+
export const INVALID_CREDENTIAL_CODE = 'INVALID_CREDENTIAL';
|
|
43
|
+
/** Structured codes and plain phrases that explicitly name a context bound being exceeded. */
|
|
44
|
+
const STRUCTURED_CONTEXT_OVERFLOW = new RegExp(String.raw `(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]`
|
|
45
|
+
+ String.raw `(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`, 'i');
|
|
46
|
+
/** Request-size wording that ties "too large" directly to model context capacity. */
|
|
47
|
+
const TOO_LARGE_FOR_CONTEXT = new RegExp(String.raw `\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?`
|
|
48
|
+
+ String.raw `too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?`
|
|
49
|
+
+ String.raw `(?:model(?:'s)?\s+)?context(?:\s+window)?\b`, 'i');
|
|
50
|
+
/** "Exceeds" wording is safe only when its object is explicitly the model context. */
|
|
51
|
+
const EXCEEDS_MODEL_CONTEXT = new RegExp(String.raw `\b(?:input|prompt|request|messages?)\b.{0,40}`
|
|
52
|
+
+ String.raw `\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}`
|
|
53
|
+
+ String.raw `\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`, 'i');
|
|
54
|
+
/**
|
|
55
|
+
* Recognize the context-overflow wording used by OpenAI-compatible providers
|
|
56
|
+
* and library adapters. Adapters pass all available provider code, type, and
|
|
57
|
+
* message text so both thrown and in-band delivery styles share one classifier.
|
|
58
|
+
* @param detail - provider error code/type/message text joined into one string.
|
|
59
|
+
* @returns true when the detail identifies a request exceeding the model context window.
|
|
60
|
+
*/
|
|
61
|
+
export function isContextWindowExceededError(detail) {
|
|
62
|
+
return STRUCTURED_CONTEXT_OVERFLOW.test(detail)
|
|
63
|
+
|| /\b(?:maximum|max)(?:\s+(?:allowed|supported))?\s+context\s+(?:length|window)\b/i.test(detail)
|
|
64
|
+
|| TOO_LARGE_FOR_CONTEXT.test(detail)
|
|
65
|
+
|| /\b(?:input|prompt|request)\s+(?:is\s+)?too\s+(?:long|large)\s+for\s+(?:this|the)\s+model\b/i.test(detail)
|
|
66
|
+
|| EXCEEDS_MODEL_CONTEXT.test(detail);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Recognize provider wording that identifies an exhausted account quota rather
|
|
70
|
+
* than a transient request-rate limit.
|
|
71
|
+
* @param detail - provider error code/type/message text joined into one string.
|
|
72
|
+
* @returns true only for terminal quota, balance, credit, budget, or usage-limit wording.
|
|
73
|
+
*/
|
|
74
|
+
export function isQuotaExceededError(detail) {
|
|
75
|
+
return /\binsufficient[\s_-]+(?:quota|balance|credits?)\b/i.test(detail)
|
|
76
|
+
|| /\b(?:quota|usage[\s_-]+limit)[\s_-]+(?:exceeded|exhausted|reached)\b/i.test(detail)
|
|
77
|
+
|| /\bexceed(?:ed|s)?[\s_-]+(?:(?:your|the)[\s_-]+)?(?:current[\s_-]+)?quota\b/i.test(detail)
|
|
78
|
+
|| /\b(?:balance|credits?)[\s_-]+(?:exhausted|depleted)\b/i.test(detail)
|
|
79
|
+
|| /\bout[\s_-]+of[\s_-]+(?:credits?|budget)\b/i.test(detail);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Render a thrown value with its full `cause` chain and AggregateError
|
|
83
|
+
* members, so transport wrappers like undici's `TypeError: fetch failed`
|
|
84
|
+
* surface the underlying failure instead of masking it. Plain structured
|
|
85
|
+
* failures render their own data-backed `message`. Diagnostic-surface
|
|
86
|
+
* rendering only (messages, notices, logs) — never parse the result; route on
|
|
87
|
+
* {@link HarnessError.code}.
|
|
88
|
+
* @param value - the caught value (`unknown` in catch clauses).
|
|
89
|
+
* @returns the outermost message first, each cause appended with `: ` (skipped
|
|
90
|
+
* when it repeats the wrapper message verbatim), and AggregateError members
|
|
91
|
+
* bracketed and `; `-joined.
|
|
92
|
+
*/
|
|
93
|
+
export function errorChain(value) {
|
|
94
|
+
// Tracks the active recursion path (entries removed on exit), so only true
|
|
95
|
+
// cycles are flagged and a diamond-shared cause still renders in full.
|
|
96
|
+
const path = new Set();
|
|
97
|
+
const render = (current) => {
|
|
98
|
+
if (path.has(current))
|
|
99
|
+
return '<circular cause>';
|
|
100
|
+
path.add(current);
|
|
101
|
+
try {
|
|
102
|
+
if (!(current instanceof Error)) {
|
|
103
|
+
if (typeof current === 'object' && current !== null) {
|
|
104
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, 'message');
|
|
105
|
+
if (descriptor !== undefined && 'value' in descriptor && typeof descriptor.value === 'string') {
|
|
106
|
+
return descriptor.value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return String(current);
|
|
110
|
+
}
|
|
111
|
+
const message = current.message === '' ? current.name : current.message;
|
|
112
|
+
const members = current instanceof AggregateError && current.errors.length > 0
|
|
113
|
+
? ` [${current.errors.map(render).join('; ')}]`
|
|
114
|
+
: '';
|
|
115
|
+
const causeText = current.cause === undefined || current.cause === null
|
|
116
|
+
? ''
|
|
117
|
+
: render(current.cause);
|
|
118
|
+
// Wrappers like `new HarnessError(String(value), code, { cause: value })`
|
|
119
|
+
// repeat their cause verbatim; rendering it again would only add noise.
|
|
120
|
+
const cause = causeText === '' || causeText === message ? '' : `: ${causeText}`;
|
|
121
|
+
return `${message}${members}${cause}`;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Only hostile coercion or hostile accessors (a throwing toString /
|
|
125
|
+
// Symbol.toPrimitive on a non-Error, or a throwing message/name/cause/
|
|
126
|
+
// errors getter on an Error subclass): this renderer feeds UI notices
|
|
127
|
+
// and logs, so nothing may escape. Inner frames catch their own throws,
|
|
128
|
+
// so only the hostile node collapses, not the whole chain.
|
|
129
|
+
return '<unrenderable value>';
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
path.delete(current);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
return render(value);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Narrow an arbitrary thrown value to a HarnessError (for `instanceof` at runtime boundaries).
|
|
139
|
+
* @param value - the caught value (`unknown` in catch clauses).
|
|
140
|
+
* @returns true only for real instances; duck-typed or cross-realm errors do not narrow.
|
|
141
|
+
*/
|
|
142
|
+
export function isHarnessError(value) {
|
|
143
|
+
return value instanceof HarnessError;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Canonical code for a request an image-capable route cannot send until more
|
|
147
|
+
* of its images are offloaded. The failure's `offloadImages` names how many
|
|
148
|
+
* more of the oldest retained occurrences must be offloaded;
|
|
149
|
+
* `dsh-compaction-image-offload` records an `image/offload` selection before
|
|
150
|
+
* the agent or summarizer retries with freshly derived input.
|
|
151
|
+
*/
|
|
152
|
+
export const IMAGE_OFFLOAD_REQUIRED_CODE = 'IMAGE_OFFLOAD_REQUIRED';
|
|
153
|
+
//# sourceMappingURL=error.js.map
|