@deepseek-ai/dsh-llm 0.1.1-rc.2 → 0.1.2-alpha.2
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/README.i18n.yaml +2 -2
- package/README.md +123 -62
- package/README.zh.md +123 -66
- package/lib/index.js +780 -680
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +528 -0
- package/lib/typert.remote-client.d.ts +26 -0
- package/lib/typert.remote-client.js +102 -0
- package/lib/types/assembler.js +3 -3
- package/lib/types/brand.d.ts +10 -10
- package/lib/types/brand.js +13 -12
- package/lib/types/call-config.d.ts +0 -9
- package/lib/types/call-config.js +0 -40
- package/lib/types/content.d.ts +50 -16
- package/lib/types/content.js +99 -41
- package/lib/types/index.d.ts +38 -8
- package/lib/types/index.js +668 -570
- package/lib/types/message.d.ts +4 -13
- package/lib/types/message.js +4 -22
- package/lib/types/types.d.ts +53 -5
- package/package.json +25 -13
- package/lib/types/never.d.ts +0 -16
- package/lib/types/never.js +0 -21
package/lib/index.js
CHANGED
|
@@ -1,138 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { Remote, RemoteError, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
3
|
+
import { assertNever, deepFreeze } from "@deepseek-ai/dsh-util-values";
|
|
4
|
+
import { randomUUID } from "@deepseek-ai/dsh-util-crypto";
|
|
5
|
+
import { brandString } from "@deepseek-ai/dsh-brand";
|
|
3
6
|
import z from "@deepseek-ai/schemastery";
|
|
4
7
|
import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
|
|
5
|
-
//#region lib/types/brand.js
|
|
6
|
-
/**
|
|
7
|
-
* dsh-llm's owned branded ids: tool-call correlation and provider request
|
|
8
|
-
* diagnostics.
|
|
9
|
-
*
|
|
10
|
-
* The `Branded<B>` primitive itself lives in `@deepseek-ai/dsh-brand` (a
|
|
11
|
-
* zero-dependency type-only package) so every owner of a cross-boundary id can
|
|
12
|
-
* brand it without depending on dsh-llm; see that package's README for the
|
|
13
|
-
* nominal-typing policy.
|
|
14
|
-
*
|
|
15
|
-
* @module @deepseek-ai/dsh-llm/brand
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* Brand a message identifier.
|
|
19
|
-
* @param id - the opaque message identifier.
|
|
20
|
-
* @returns the same string, branded; no validation is performed.
|
|
21
|
-
*/
|
|
22
|
-
function MessageId(id) {
|
|
23
|
-
return id;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Brand a string as a {@link CallId}.
|
|
27
|
-
* @param id - the provider-issued (or synthesized) call id.
|
|
28
|
-
* @returns the same string, branded; no validation is performed.
|
|
29
|
-
*/
|
|
30
|
-
function CallId(id) {
|
|
31
|
-
return id;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Brand a provider-issued request identifier.
|
|
35
|
-
* @param id - the opaque provider-issued string.
|
|
36
|
-
* @returns the same string, branded; no validation is performed.
|
|
37
|
-
*/
|
|
38
|
-
function ProviderRequestId(id) {
|
|
39
|
-
return id;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Brand an adapter-owned reasoning-effort identifier.
|
|
43
|
-
* @param id - the opaque identifier exposed by one model capability.
|
|
44
|
-
* @returns the same string, branded; no validation is performed.
|
|
45
|
-
*/
|
|
46
|
-
function ReasoningEffortId(id) {
|
|
47
|
-
return id;
|
|
48
|
-
}
|
|
49
|
-
//#endregion
|
|
50
|
-
//#region lib/types/call-config.js
|
|
51
|
-
/**
|
|
52
|
-
* Conversation call configuration and freeze utilities. Provider routing,
|
|
53
|
-
* model, reasoning effort, and sampling values are request-header state that
|
|
54
|
-
* can affect cache reuse; request waterfalls replace them and the loop logs
|
|
55
|
-
* changed snapshots instead of allowing silent per-call drift.
|
|
56
|
-
* @module dsh-llm/call-config
|
|
57
|
-
*/
|
|
58
|
-
/** Process-local identities of request objects assembled by dsh-agent-loop. */
|
|
59
|
-
const AGENT_LOOP_REQUESTS = /* @__PURE__ */ new WeakSet();
|
|
60
|
-
/**
|
|
61
|
-
* Field-wise equality over {@link LlmCallConfig} — the comparison a caller
|
|
62
|
-
* runs to decide whether a proposed configuration is a real change (worth a
|
|
63
|
-
* logged header snapshot) or the held one restated.
|
|
64
|
-
* @param a - one configuration.
|
|
65
|
-
* @param b - the other.
|
|
66
|
-
* @returns whether every field (including the `stop` list, element-wise) matches.
|
|
67
|
-
*/
|
|
68
|
-
function callConfigEquals(a, b) {
|
|
69
|
-
if (a.provider !== b.provider || a.model !== b.model || a.reasoningEffort !== b.reasoningEffort || a.temperature !== b.temperature || a.maxTokens !== b.maxTokens) return false;
|
|
70
|
-
if (a.stop === void 0 || b.stop === void 0) return a.stop === b.stop;
|
|
71
|
-
return a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Mark one exact request object as assembled by dsh-agent-loop.
|
|
75
|
-
* @param request - loop-owned request envelope before LLM dispatch.
|
|
76
|
-
* @returns the same request object marked as created by the process-local agent loop.
|
|
77
|
-
*/
|
|
78
|
-
function markAgentLoopRequest(request) {
|
|
79
|
-
AGENT_LOOP_REQUESTS.add(request);
|
|
80
|
-
return request;
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Test whether the exact request object was assembled by dsh-agent-loop.
|
|
84
|
-
* @param request - request envelope observed at the LLM waterfall.
|
|
85
|
-
* @returns whether {@link markAgentLoopRequest} recorded this object.
|
|
86
|
-
*/
|
|
87
|
-
function isAgentLoopRequest(request) {
|
|
88
|
-
return AGENT_LOOP_REQUESTS.has(request);
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Deep-freeze a value in place with an iterative traversal, guarding cycles,
|
|
92
|
-
* so later mutation throws without imposing a JavaScript call-stack depth cap.
|
|
93
|
-
* {@link AbortSignal} objects are deliberately skipped because they are the
|
|
94
|
-
* request's live cancellation channel and freezing them breaks abort.
|
|
95
|
-
* @param value - the value to freeze in place.
|
|
96
|
-
* @returns the same value, frozen.
|
|
97
|
-
*/
|
|
98
|
-
function deepFreeze(value) {
|
|
99
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
100
|
-
const pending = [{
|
|
101
|
-
kind: "visit",
|
|
102
|
-
node: value
|
|
103
|
-
}];
|
|
104
|
-
while (pending.length > 0) {
|
|
105
|
-
const task = pending.pop();
|
|
106
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
107
|
-
if (task === void 0) continue;
|
|
108
|
-
if (task.kind === "property") {
|
|
109
|
-
pending.push({
|
|
110
|
-
kind: "visit",
|
|
111
|
-
node: task.source[task.key]
|
|
112
|
-
});
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const node = task.node;
|
|
116
|
-
if (node === null || typeof node !== "object") continue;
|
|
117
|
-
if (node instanceof AbortSignal) continue;
|
|
118
|
-
if (seen.has(node)) continue;
|
|
119
|
-
seen.add(node);
|
|
120
|
-
Object.freeze(node);
|
|
121
|
-
const keys = Object.keys(node);
|
|
122
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
123
|
-
const key = keys[index];
|
|
124
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
125
|
-
if (key === void 0) continue;
|
|
126
|
-
pending.push({
|
|
127
|
-
kind: "property",
|
|
128
|
-
source: node,
|
|
129
|
-
key
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return value;
|
|
134
|
-
}
|
|
135
|
-
//#endregion
|
|
136
8
|
//#region lib/types/message.js
|
|
137
9
|
/** Message value types, identity, and immutable construction helpers. */
|
|
138
10
|
/**
|
|
@@ -165,7 +37,7 @@ function freezeMessage(message) {
|
|
|
165
37
|
function createMessage(input) {
|
|
166
38
|
return freezeMessage({
|
|
167
39
|
...input,
|
|
168
|
-
id:
|
|
40
|
+
id: brandString(randomUUID())
|
|
169
41
|
});
|
|
170
42
|
}
|
|
171
43
|
/**
|
|
@@ -213,22 +85,6 @@ function createToolResultMessage(input) {
|
|
|
213
85
|
}]
|
|
214
86
|
});
|
|
215
87
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Whether a stream chunk carries visible model output (the first-token
|
|
218
|
-
* boundary shared by client step timing and the whole-log sessionStats
|
|
219
|
-
* projection). Empty deltas (heartbeats, empty tool-call frames) do not count
|
|
220
|
-
* as a first token.
|
|
221
|
-
* @param chunk - the stream chunk to test.
|
|
222
|
-
* @returns true when the chunk contains a non-empty text/reasoning/tool delta.
|
|
223
|
-
*/
|
|
224
|
-
function isTokenDelta(chunk) {
|
|
225
|
-
switch (chunk.type) {
|
|
226
|
-
case "text-delta":
|
|
227
|
-
case "reasoning-delta": return chunk.text !== "";
|
|
228
|
-
case "tool-call-delta": return chunk.argumentsDelta !== "" || chunk.name !== void 0;
|
|
229
|
-
default: return false;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
88
|
//#endregion
|
|
233
89
|
//#region lib/types/error.js
|
|
234
90
|
/**
|
|
@@ -455,6 +311,47 @@ function resolveRetryPolicy(config, path) {
|
|
|
455
311
|
}
|
|
456
312
|
}
|
|
457
313
|
//#endregion
|
|
314
|
+
//#region lib/types/call-config.js
|
|
315
|
+
/**
|
|
316
|
+
* Conversation call configuration and freeze utilities. Provider routing,
|
|
317
|
+
* model, reasoning effort, and sampling values are request-header state that
|
|
318
|
+
* can affect cache reuse; request waterfalls replace them and the loop logs
|
|
319
|
+
* changed snapshots instead of allowing silent per-call drift.
|
|
320
|
+
* @module dsh-llm/call-config
|
|
321
|
+
*/
|
|
322
|
+
/** Process-local identities of request objects assembled by dsh-agent-loop. */
|
|
323
|
+
const AGENT_LOOP_REQUESTS = /* @__PURE__ */ new WeakSet();
|
|
324
|
+
/**
|
|
325
|
+
* Field-wise equality over {@link LlmCallConfig} — the comparison a caller
|
|
326
|
+
* runs to decide whether a proposed configuration is a real change (worth a
|
|
327
|
+
* logged header snapshot) or the held one restated.
|
|
328
|
+
* @param a - one configuration.
|
|
329
|
+
* @param b - the other.
|
|
330
|
+
* @returns whether every field (including the `stop` list, element-wise) matches.
|
|
331
|
+
*/
|
|
332
|
+
function callConfigEquals(a, b) {
|
|
333
|
+
if (a.provider !== b.provider || a.model !== b.model || a.reasoningEffort !== b.reasoningEffort || a.temperature !== b.temperature || a.maxTokens !== b.maxTokens) return false;
|
|
334
|
+
if (a.stop === void 0 || b.stop === void 0) return a.stop === b.stop;
|
|
335
|
+
return a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Mark one exact request object as assembled by dsh-agent-loop.
|
|
339
|
+
* @param request - loop-owned request envelope before LLM dispatch.
|
|
340
|
+
* @returns the same request object marked as created by the process-local agent loop.
|
|
341
|
+
*/
|
|
342
|
+
function markAgentLoopRequest(request) {
|
|
343
|
+
AGENT_LOOP_REQUESTS.add(request);
|
|
344
|
+
return request;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Test whether the exact request object was assembled by dsh-agent-loop.
|
|
348
|
+
* @param request - request envelope observed at the LLM waterfall.
|
|
349
|
+
* @returns whether {@link markAgentLoopRequest} recorded this object.
|
|
350
|
+
*/
|
|
351
|
+
function isAgentLoopRequest(request) {
|
|
352
|
+
return AGENT_LOOP_REQUESTS.has(request);
|
|
353
|
+
}
|
|
354
|
+
//#endregion
|
|
458
355
|
//#region lib/types/adapter-failure.js
|
|
459
356
|
/**
|
|
460
357
|
* Normalization for values thrown by a final LLM adapter boundary.
|
|
@@ -582,23 +479,72 @@ function normalizeApiKey(raw) {
|
|
|
582
479
|
//#endregion
|
|
583
480
|
//#region lib/types/content.js
|
|
584
481
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
585
|
-
/**
|
|
586
|
-
|
|
482
|
+
/**
|
|
483
|
+
* Bridge one attachment provider's host object location into the mounted
|
|
484
|
+
* tool execution world. The consumer supplies the current filesystem
|
|
485
|
+
* provider's mapping without making attachment or LLM definitions depend on it.
|
|
486
|
+
* @param attachments - provider that owns the normalized attachment object.
|
|
487
|
+
* @param mapHostPath - map one absolute host path into the current tool execution world.
|
|
488
|
+
* @param ref - durable normalized attachment reference.
|
|
489
|
+
* @returns a read-only execution-world path, or undefined when either provider exposes no mapping.
|
|
490
|
+
* @throws an attachment error when the durable reference is invalid.
|
|
491
|
+
*/
|
|
492
|
+
function resolveImageAttachmentAccess(attachments, mapHostPath, ref) {
|
|
493
|
+
const hostPath = attachments.imageHostPath(ref);
|
|
494
|
+
if (hostPath === void 0) return void 0;
|
|
495
|
+
const readonlyPath = mapHostPath(hostPath);
|
|
496
|
+
return readonlyPath === void 0 ? void 0 : { readonlyPath };
|
|
497
|
+
}
|
|
498
|
+
function quoted(value) {
|
|
499
|
+
return JSON.stringify(value);
|
|
500
|
+
}
|
|
501
|
+
function imageIdentity(ref) {
|
|
502
|
+
return ref.name === void 0 ? String(ref.attachmentId) : `${quoted(ref.name)} (${ref.attachmentId})`;
|
|
503
|
+
}
|
|
504
|
+
function extension(mediaType) {
|
|
505
|
+
switch (mediaType) {
|
|
506
|
+
case "image/png": return ".png";
|
|
507
|
+
case "image/jpeg": return ".jpg";
|
|
508
|
+
case "image/webp": return ".webp";
|
|
509
|
+
case "image/gif": return ".gif";
|
|
510
|
+
default: return assertNever(mediaType, "image extension");
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
function normalizedAccessText(ref, access) {
|
|
514
|
+
return ` Normalized copy (read-only; may be resized or re-encoded): ${quoted(access.readonlyPath)} (${ref.width}x${ref.height}px, ${ref.mediaType}). Source dimensions, format, and byte size may differ. Copy to a writable path ending in ${extension(ref.mediaType)} before editing.`;
|
|
515
|
+
}
|
|
587
516
|
/**
|
|
588
517
|
* Stable text shown to a model that cannot accept one durable image reference.
|
|
589
|
-
* @param ref - durable
|
|
518
|
+
* @param ref - durable normalized attachment omitted from the request.
|
|
590
519
|
* @returns deterministic text-only placeholder.
|
|
591
520
|
*/
|
|
592
521
|
function textOnlyImageText(ref) {
|
|
593
522
|
return `[image omitted because this model accepts text only; attachment sha256:${String(ref.attachmentId).slice(7, 15)}]`;
|
|
594
523
|
}
|
|
595
524
|
/**
|
|
596
|
-
* Stable model-facing handle for one exact request image.
|
|
597
|
-
*
|
|
525
|
+
* Stable model-facing handle for one exact request image. Identity comes from
|
|
526
|
+
* the occurrence's own durable reference: request versions are prepared per
|
|
527
|
+
* attachment id, so one shared version may serve occurrences whose display
|
|
528
|
+
* names differ.
|
|
529
|
+
* @param ref - the occurrence's durable normalized attachment.
|
|
530
|
+
* @param version - exact request-image dimensions shown beside the text.
|
|
531
|
+
* @param access - optional path resolved for the current tool execution world.
|
|
598
532
|
* @returns attachment handle and request-image dimensions.
|
|
599
533
|
*/
|
|
600
|
-
function requestImageHandleText(version) {
|
|
601
|
-
|
|
534
|
+
function requestImageHandleText(ref, version, access) {
|
|
535
|
+
const preview = `Image ${imageIdentity(ref)}; request preview ${version.width}x${version.height}px.`;
|
|
536
|
+
return access === void 0 ? `${preview} It may be resized or re-encoded; source dimensions, format, and byte size may differ.` : preview + normalizedAccessText(ref, access);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Stable per-image placeholder for a request-limit omission.
|
|
540
|
+
* @param ref - durable normalized attachment omitted from this request.
|
|
541
|
+
* @param access - optional provider-resolved path for model tools.
|
|
542
|
+
* @returns identity, normalized metadata, and the available recovery path.
|
|
543
|
+
*/
|
|
544
|
+
function offloadedImageText(ref, access) {
|
|
545
|
+
const identity = `image omitted to fit request image limits; ${imageIdentity(ref)}.`;
|
|
546
|
+
if (access === void 0) return `[${identity} No local normalized image path is available; ask the user to attach it again if needed.]`;
|
|
547
|
+
return `[${identity}${normalizedAccessText(ref, access)}]`;
|
|
602
548
|
}
|
|
603
549
|
/**
|
|
604
550
|
* True when typed model content contains an image block, walking nested
|
|
@@ -623,7 +569,7 @@ function collectImageLengths(blocks, lengths, policy) {
|
|
|
623
569
|
} else if (block.type === "tool-result") collectImageLengths(block.content, lengths, policy);
|
|
624
570
|
}
|
|
625
571
|
/** Replace the first `remaining.count` image occurrences without mutating durable messages. */
|
|
626
|
-
function replaceOldestImages(blocks, remaining) {
|
|
572
|
+
function replaceOldestImages(blocks, remaining, placeholder) {
|
|
627
573
|
let next;
|
|
628
574
|
for (const [index, block] of blocks.entries()) {
|
|
629
575
|
if (block.type === "image" && remaining.count > 0) {
|
|
@@ -631,12 +577,12 @@ function replaceOldestImages(blocks, remaining) {
|
|
|
631
577
|
next ??= blocks.slice(0, index);
|
|
632
578
|
next.push({
|
|
633
579
|
type: "text",
|
|
634
|
-
text:
|
|
580
|
+
text: placeholder(block.attachment)
|
|
635
581
|
});
|
|
636
582
|
continue;
|
|
637
583
|
}
|
|
638
584
|
if (block.type === "tool-result") {
|
|
639
|
-
const content = replaceOldestImages(block.content, remaining);
|
|
585
|
+
const content = replaceOldestImages(block.content, remaining, placeholder);
|
|
640
586
|
if (content !== block.content) {
|
|
641
587
|
next ??= blocks.slice(0, index);
|
|
642
588
|
next.push({
|
|
@@ -693,20 +639,31 @@ function projectImagesForTextModel(messages) {
|
|
|
693
639
|
});
|
|
694
640
|
}
|
|
695
641
|
/**
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
* @param
|
|
701
|
-
* @param
|
|
702
|
-
* @returns
|
|
642
|
+
* Number of oldest image occurrences one request projection removes, in whole
|
|
643
|
+
* count and byte quanta, once a route budget is exceeded. The result depends
|
|
644
|
+
* only on the represented lengths, so provider request pricing reproduces the
|
|
645
|
+
* exact serialization decision without building the projected messages.
|
|
646
|
+
* @param lengths - represented byte length of every occurrence, in request order.
|
|
647
|
+
* @param policy - count/byte budgets and removal quanta; unbounded when absent.
|
|
648
|
+
* @returns how many leading occurrences the projection replaces with placeholders.
|
|
703
649
|
*/
|
|
704
|
-
function
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
650
|
+
function offloadedImagePrefixCount(lengths, policy) {
|
|
651
|
+
const total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
652
|
+
const excessCount = policy.maxImages === void 0 ? 0 : Math.max(0, lengths.length - policy.maxImages);
|
|
653
|
+
const excessBytes = policy.maxBytes === void 0 ? 0 : Math.max(0, total - policy.maxBytes);
|
|
654
|
+
if (excessCount === 0 && excessBytes === 0) return 0;
|
|
655
|
+
const countQuantum = policy.countQuantum ?? 1;
|
|
656
|
+
const byteQuantum = policy.byteQuantum ?? 1;
|
|
657
|
+
const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
|
|
658
|
+
const removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;
|
|
659
|
+
let count = 0;
|
|
660
|
+
let removedBytes = 0;
|
|
661
|
+
for (const imageBytes of lengths) {
|
|
662
|
+
if (count >= removeCount && (removeBytes === 0 || (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes))) break;
|
|
663
|
+
removedBytes += imageBytes;
|
|
664
|
+
count += 1;
|
|
665
|
+
}
|
|
666
|
+
return count;
|
|
710
667
|
}
|
|
711
668
|
/**
|
|
712
669
|
* Return a deterministic transient projection whose oldest images are replaced
|
|
@@ -722,24 +679,11 @@ function offloadRequestImages(messages, maxRequestImageBytes) {
|
|
|
722
679
|
function offloadRequestImagesWithPolicy(messages, policy) {
|
|
723
680
|
const lengths = [];
|
|
724
681
|
for (const message of messages) collectImageLengths(message.content, lengths, policy);
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
const excessBytes = policy.maxBytes === void 0 ? 0 : Math.max(0, total - policy.maxBytes);
|
|
728
|
-
if (excessCount === 0 && excessBytes === 0) return messages;
|
|
729
|
-
const countQuantum = policy.countQuantum ?? 1;
|
|
730
|
-
const byteQuantum = policy.byteQuantum ?? 1;
|
|
731
|
-
const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
|
|
732
|
-
const removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;
|
|
733
|
-
let count = 0;
|
|
734
|
-
let removedBytes = 0;
|
|
735
|
-
for (const imageBytes of lengths) {
|
|
736
|
-
if (count >= removeCount && (removeBytes === 0 || (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes))) break;
|
|
737
|
-
removedBytes += imageBytes;
|
|
738
|
-
count += 1;
|
|
739
|
-
}
|
|
682
|
+
const count = offloadedImagePrefixCount(lengths, policy);
|
|
683
|
+
if (count === 0) return messages;
|
|
740
684
|
const remaining = { count };
|
|
741
685
|
return messages.map((message) => {
|
|
742
|
-
const content = replaceOldestImages(message.content, remaining);
|
|
686
|
+
const content = replaceOldestImages(message.content, remaining, policy.placeholder);
|
|
743
687
|
return content === message.content ? message : {
|
|
744
688
|
...message,
|
|
745
689
|
content
|
|
@@ -789,24 +733,49 @@ function attributionHeaders(identity = APP_IDENTITY) {
|
|
|
789
733
|
return { "user-agent": userAgent(identity) };
|
|
790
734
|
}
|
|
791
735
|
//#endregion
|
|
792
|
-
//#region lib/types/
|
|
736
|
+
//#region lib/types/brand.js
|
|
737
|
+
/**
|
|
738
|
+
* dsh-llm's owned branded ids: tool-call correlation and provider request
|
|
739
|
+
* diagnostics.
|
|
740
|
+
*
|
|
741
|
+
* The `Branded<B>` primitive and stateless constructor live in
|
|
742
|
+
* `@deepseek-ai/dsh-brand` so every owner of a cross-boundary id can brand it
|
|
743
|
+
* without depending on dsh-llm; see that package's README for the
|
|
744
|
+
* nominal-typing policy.
|
|
745
|
+
*
|
|
746
|
+
* @module @deepseek-ai/dsh-llm/brand
|
|
747
|
+
*/
|
|
748
|
+
/**
|
|
749
|
+
* Brand a message identifier.
|
|
750
|
+
* @param id - the opaque message identifier.
|
|
751
|
+
* @returns the same string with the message-id brand.
|
|
752
|
+
*/
|
|
753
|
+
function MessageId(id) {
|
|
754
|
+
return brandString(id);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Brand a string as a {@link ToolCallId}.
|
|
758
|
+
* @param id - the provider-issued or synthesized call id.
|
|
759
|
+
* @returns the same string with the tool-call-id brand.
|
|
760
|
+
*/
|
|
761
|
+
function ToolCallId(id) {
|
|
762
|
+
return brandString(id);
|
|
763
|
+
}
|
|
793
764
|
/**
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
797
|
-
* through because plugins may add valid unknown cases.
|
|
798
|
-
* @module @deepseek-ai/dsh-llm/never
|
|
765
|
+
* Brand a provider-issued request identifier.
|
|
766
|
+
* @param id - the opaque provider-issued string.
|
|
767
|
+
* @returns the same string, branded; no validation is performed.
|
|
799
768
|
*/
|
|
769
|
+
function ProviderRequestId(id) {
|
|
770
|
+
return brandString(id);
|
|
771
|
+
}
|
|
800
772
|
/**
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
* @
|
|
804
|
-
* @param context - optional label (e.g. the switch site) prefixed into the throw message.
|
|
805
|
-
* @returns never — it always throws, with the offending value JSON-rendered in the message.
|
|
773
|
+
* Brand an adapter-owned reasoning-effort identifier.
|
|
774
|
+
* @param id - the opaque identifier exposed by one model capability.
|
|
775
|
+
* @returns the same string, branded; no validation is performed.
|
|
806
776
|
*/
|
|
807
|
-
function
|
|
808
|
-
|
|
809
|
-
throw new Error(`unreachable variant${context ? ` in ${context}` : ""}: ${rendered}`);
|
|
777
|
+
function ReasoningEffortId(id) {
|
|
778
|
+
return brandString(id);
|
|
810
779
|
}
|
|
811
780
|
//#endregion
|
|
812
781
|
//#region lib/types/assembler.js
|
|
@@ -908,7 +877,7 @@ var BlockAssembler = class {
|
|
|
908
877
|
};
|
|
909
878
|
case "tool-call": return {
|
|
910
879
|
type: "tool-call",
|
|
911
|
-
id: partial.toolCallId ??
|
|
880
|
+
id: partial.toolCallId ?? brandString(`call-${index}`),
|
|
912
881
|
name: partial.toolCallName ?? "",
|
|
913
882
|
arguments: partial.toolCallArguments
|
|
914
883
|
};
|
|
@@ -1012,6 +981,44 @@ var BlockAssembler = class {
|
|
|
1012
981
|
*
|
|
1013
982
|
* @module @deepseek-ai/dsh-llm
|
|
1014
983
|
*/
|
|
984
|
+
var __runInitializers = function(thisArg, initializers, value) {
|
|
985
|
+
var useValue = arguments.length > 2;
|
|
986
|
+
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
987
|
+
return useValue ? value : void 0;
|
|
988
|
+
};
|
|
989
|
+
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
990
|
+
function accept(f) {
|
|
991
|
+
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
992
|
+
return f;
|
|
993
|
+
}
|
|
994
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
995
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
996
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
997
|
+
var _, done = false;
|
|
998
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
999
|
+
var context = {};
|
|
1000
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
1001
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
1002
|
+
context.addInitializer = function(f) {
|
|
1003
|
+
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
1004
|
+
extraInitializers.push(accept(f || null));
|
|
1005
|
+
};
|
|
1006
|
+
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
1007
|
+
get: descriptor.get,
|
|
1008
|
+
set: descriptor.set
|
|
1009
|
+
} : descriptor[key], context);
|
|
1010
|
+
if (kind === "accessor") {
|
|
1011
|
+
if (result === void 0) continue;
|
|
1012
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
1013
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
1014
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
1015
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
1016
|
+
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
1017
|
+
else descriptor[key] = _;
|
|
1018
|
+
}
|
|
1019
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
1020
|
+
done = true;
|
|
1021
|
+
};
|
|
1015
1022
|
/**
|
|
1016
1023
|
* Typed error for LLM-related failures. Extends {@link HarnessError}, so the
|
|
1017
1024
|
* `code` string (e.g. `AUTH`, `RATE_LIMIT`, `NO_ADAPTER`) is shared taxonomy.
|
|
@@ -1089,6 +1096,16 @@ var LlmAdapter = class {
|
|
|
1089
1096
|
*/
|
|
1090
1097
|
providerRetryPolicy(_provider) {}
|
|
1091
1098
|
/**
|
|
1099
|
+
* Resolve provider-side request-image pricing for one exact model route.
|
|
1100
|
+
* The default declares none, so consumers fall back to their own neutral
|
|
1101
|
+
* estimate. Implementations must answer synchronously without I/O; the
|
|
1102
|
+
* token meter resolves this per measurement.
|
|
1103
|
+
* @param _provider - a route passed to `registerAdapter()` for this instance.
|
|
1104
|
+
* @param _model - exact model id passed to {@link GenerateOptions.model}.
|
|
1105
|
+
* @returns route-owned image pricing, or `undefined` when the route declares none.
|
|
1106
|
+
*/
|
|
1107
|
+
imageRequestPricing(_provider, _model) {}
|
|
1108
|
+
/**
|
|
1092
1109
|
* List models this adapter can currently advertise for one owned provider.
|
|
1093
1110
|
* The result is advisory: an adapter may accept unlisted model ids, and
|
|
1094
1111
|
* consumers must not turn absence into request rejection.
|
|
@@ -1134,512 +1151,595 @@ var LlmAdapter = class {
|
|
|
1134
1151
|
* The abstract `llm` service: an adapter registry plus a streaming model-call
|
|
1135
1152
|
* API, interceptable via the `llm/stream` waterfall.
|
|
1136
1153
|
*/
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1154
|
+
let LlmRuntime = (() => {
|
|
1155
|
+
let _classSuper = TypertRemoteService;
|
|
1156
|
+
let _instanceExtraInitializers = [];
|
|
1157
|
+
let _listProviders_decorators;
|
|
1158
|
+
let _listConfigurableProviders_decorators;
|
|
1159
|
+
let _remoteDiscoverModels_decorators;
|
|
1160
|
+
return class LlmRuntime extends _classSuper {
|
|
1161
|
+
static {
|
|
1162
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
1163
|
+
_listProviders_decorators = [Remote];
|
|
1164
|
+
_listConfigurableProviders_decorators = [Remote];
|
|
1165
|
+
_remoteDiscoverModels_decorators = [Remote("discoverModels")];
|
|
1166
|
+
__esDecorate(this, null, _listProviders_decorators, {
|
|
1167
|
+
kind: "method",
|
|
1168
|
+
name: "listProviders",
|
|
1169
|
+
static: false,
|
|
1170
|
+
private: false,
|
|
1171
|
+
access: {
|
|
1172
|
+
has: (obj) => "listProviders" in obj,
|
|
1173
|
+
get: (obj) => obj.listProviders
|
|
1174
|
+
},
|
|
1175
|
+
metadata: _metadata
|
|
1176
|
+
}, null, _instanceExtraInitializers);
|
|
1177
|
+
__esDecorate(this, null, _listConfigurableProviders_decorators, {
|
|
1178
|
+
kind: "method",
|
|
1179
|
+
name: "listConfigurableProviders",
|
|
1180
|
+
static: false,
|
|
1181
|
+
private: false,
|
|
1182
|
+
access: {
|
|
1183
|
+
has: (obj) => "listConfigurableProviders" in obj,
|
|
1184
|
+
get: (obj) => obj.listConfigurableProviders
|
|
1185
|
+
},
|
|
1186
|
+
metadata: _metadata
|
|
1187
|
+
}, null, _instanceExtraInitializers);
|
|
1188
|
+
__esDecorate(this, null, _remoteDiscoverModels_decorators, {
|
|
1189
|
+
kind: "method",
|
|
1190
|
+
name: "remoteDiscoverModels",
|
|
1191
|
+
static: false,
|
|
1192
|
+
private: false,
|
|
1193
|
+
access: {
|
|
1194
|
+
has: (obj) => "remoteDiscoverModels" in obj,
|
|
1195
|
+
get: (obj) => obj.remoteDiscoverModels
|
|
1196
|
+
},
|
|
1197
|
+
metadata: _metadata
|
|
1198
|
+
}, null, _instanceExtraInitializers);
|
|
1199
|
+
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
1200
|
+
enumerable: true,
|
|
1201
|
+
configurable: true,
|
|
1202
|
+
writable: true,
|
|
1203
|
+
value: _metadata
|
|
1151
1204
|
});
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1205
|
+
}
|
|
1206
|
+
adapters = (__runInitializers(this, _instanceExtraInitializers), /* @__PURE__ */ new Map());
|
|
1207
|
+
directory = /* @__PURE__ */ new Map();
|
|
1208
|
+
discoveries = /* @__PURE__ */ new Map();
|
|
1209
|
+
constructor(ctx) {
|
|
1210
|
+
super(ctx, "llm");
|
|
1211
|
+
}
|
|
1212
|
+
/** Notify topology observers without letting one broken listener veto the commit. */
|
|
1213
|
+
emitAdaptersUpdated() {
|
|
1214
|
+
let invariantFailure;
|
|
1215
|
+
for (const listener of this.ctx.events.dispatch("emit", ["llm/adapters-updated"])) try {
|
|
1216
|
+
const returned = listener();
|
|
1217
|
+
if (returned != null && typeof returned.then === "function") Promise.resolve(returned).then(void 0, (error) => {
|
|
1218
|
+
this.warnAdaptersListenerFailure(error);
|
|
1219
|
+
});
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
if (error?.code === "INVARIANT") {
|
|
1222
|
+
invariantFailure ??= error;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
this.warnAdaptersListenerFailure(error);
|
|
1156
1226
|
}
|
|
1157
|
-
|
|
1227
|
+
if (invariantFailure !== void 0) throw invariantFailure;
|
|
1158
1228
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
this.ctx.logger.warn("llm: an llm/adapters-updated listener failed");
|
|
1164
|
-
this.ctx.logger.warn(error);
|
|
1165
|
-
}
|
|
1166
|
-
/**
|
|
1167
|
-
* Register an adapter for the given provider routes. Throws `LlmError` with code
|
|
1168
|
-
* `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing).
|
|
1169
|
-
* Disposed with the fiber.
|
|
1170
|
-
* @param providers - every provider route this adapter should serve.
|
|
1171
|
-
* @param adapter - the adapter that streams calls for those providers.
|
|
1172
|
-
* @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
|
|
1173
|
-
*/
|
|
1174
|
-
registerAdapter(providers, adapter) {
|
|
1175
|
-
const owned = /* @__PURE__ */ new Set();
|
|
1176
|
-
let released = false;
|
|
1177
|
-
const dispose = this.ctx.effect(function* () {
|
|
1178
|
-
if (providers.length === 0) throw new LlmError("an adapter must register at least one provider", "INVALID_ADAPTER");
|
|
1179
|
-
this.commitRoutes(owned, this.prepareRoutes(providers, adapter, owned));
|
|
1180
|
-
yield () => {
|
|
1181
|
-
released = true;
|
|
1182
|
-
for (const provider of owned) this.adapters.delete(provider);
|
|
1183
|
-
owned.clear();
|
|
1184
|
-
this.emitAdaptersUpdated();
|
|
1185
|
-
};
|
|
1186
|
-
}.bind(this), "llm.registerAdapter()");
|
|
1187
|
-
const handle = (() => void dispose());
|
|
1188
|
-
handle.replace = (next) => {
|
|
1189
|
-
if (released) throw new LlmError("a disposed adapter registration cannot replace its routes", "REGISTRATION_DISPOSED");
|
|
1190
|
-
this.commitRoutes(owned, this.prepareRoutes(next, adapter, owned));
|
|
1191
|
-
};
|
|
1192
|
-
return handle;
|
|
1193
|
-
}
|
|
1194
|
-
/**
|
|
1195
|
-
* Validate one candidate route set for `adapter`, treating routes this
|
|
1196
|
-
* registration already holds as available. Nothing is mutated: a rejected
|
|
1197
|
-
* candidate leaves the registry exactly as it was.
|
|
1198
|
-
*/
|
|
1199
|
-
prepareRoutes(providers, adapter, owned) {
|
|
1200
|
-
const unique = /* @__PURE__ */ new Set();
|
|
1201
|
-
const registrations = [];
|
|
1202
|
-
for (const provider of providers) {
|
|
1203
|
-
if (provider.length === 0) throw new LlmError("adapter provider names must be non-empty", "INVALID_ADAPTER");
|
|
1204
|
-
if (unique.has(provider) || this.adapters.has(provider) && !owned.has(provider)) throw new LlmError(`an adapter for provider "${provider}" is already registered`, "DUPLICATE_ADAPTER");
|
|
1205
|
-
const info = adapter.providerInfo(provider);
|
|
1206
|
-
if (typeof info.id !== "string" || info.id !== provider || typeof info.name !== "string" || info.name.length === 0) throw new LlmError(`adapter metadata for provider "${provider}" must preserve its id and have a non-empty name`, "INVALID_ADAPTER");
|
|
1207
|
-
unique.add(provider);
|
|
1208
|
-
const retryPolicy = adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(void 0, `llm: provider "${provider}" retryPolicy`);
|
|
1209
|
-
registrations.push({
|
|
1210
|
-
adapter,
|
|
1211
|
-
provider: {
|
|
1212
|
-
id: info.id,
|
|
1213
|
-
name: info.name
|
|
1214
|
-
},
|
|
1215
|
-
retryPolicy
|
|
1216
|
-
});
|
|
1229
|
+
/** Contained-listener diagnostic shared by the sync and async failure paths. */
|
|
1230
|
+
warnAdaptersListenerFailure(error) {
|
|
1231
|
+
this.ctx.logger.warn("llm: an llm/adapters-updated listener failed");
|
|
1232
|
+
this.ctx.logger.warn(error);
|
|
1217
1233
|
}
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1234
|
+
/**
|
|
1235
|
+
* Register an adapter for the given provider routes. Throws `LlmError` with code
|
|
1236
|
+
* `DUPLICATE_ADAPTER` if any provider already has an adapter (all-or-nothing).
|
|
1237
|
+
* Disposed with the fiber.
|
|
1238
|
+
* @param providers - every provider route this adapter should serve.
|
|
1239
|
+
* @param adapter - the adapter that streams calls for those providers.
|
|
1240
|
+
* @returns the disposer, carrying {@link AdapterRegistrationHandle.replace}.
|
|
1241
|
+
*/
|
|
1242
|
+
registerAdapter(providers, adapter) {
|
|
1243
|
+
const owned = /* @__PURE__ */ new Set();
|
|
1244
|
+
let released = false;
|
|
1245
|
+
const dispose = this.ctx.effect(function* () {
|
|
1246
|
+
if (providers.length === 0) throw new LlmError("an adapter must register at least one provider", "INVALID_ADAPTER");
|
|
1247
|
+
this.commitRoutes(owned, this.prepareRoutes(providers, adapter, owned));
|
|
1248
|
+
yield () => {
|
|
1249
|
+
released = true;
|
|
1250
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
1251
|
+
owned.clear();
|
|
1252
|
+
this.emitAdaptersUpdated();
|
|
1253
|
+
};
|
|
1254
|
+
}.bind(this), "llm.registerAdapter()");
|
|
1255
|
+
const handle = (() => void dispose());
|
|
1256
|
+
handle.replace = (next) => {
|
|
1257
|
+
if (released) throw new LlmError("a disposed adapter registration cannot replace its routes", "REGISTRATION_DISPOSED");
|
|
1258
|
+
this.commitRoutes(owned, this.prepareRoutes(next, adapter, owned));
|
|
1259
|
+
};
|
|
1260
|
+
return handle;
|
|
1233
1261
|
}
|
|
1234
|
-
this.emitAdaptersUpdated();
|
|
1235
|
-
}
|
|
1236
|
-
/**
|
|
1237
|
-
* Describe provider routes with a registered adapter.
|
|
1238
|
-
* @returns detached provider metadata in registration order.
|
|
1239
|
-
*/
|
|
1240
|
-
listProviders() {
|
|
1241
|
-
return [...this.adapters.values()].map(({ provider }) => ({ ...provider }));
|
|
1242
|
-
}
|
|
1243
|
-
/**
|
|
1244
|
-
* Declare provider routes an adapter plugin can activate through
|
|
1245
|
-
* configuration. Registration is all-or-nothing: an empty list, invalid
|
|
1246
|
-
* entry, or a provider already declared by any registration throws
|
|
1247
|
-
* `LlmError` without registering the rest. Disposed with the fiber.
|
|
1248
|
-
* @param entries - every configurable provider this plugin owns.
|
|
1249
|
-
* @returns a handle that withdraws all of them, and can atomically replace them.
|
|
1250
|
-
*/
|
|
1251
|
-
registerConfigurableProviders(entries) {
|
|
1252
|
-
let held = [];
|
|
1253
|
-
let disposed = false;
|
|
1254
1262
|
/**
|
|
1255
|
-
* Validate
|
|
1256
|
-
*
|
|
1257
|
-
*
|
|
1258
|
-
* place — the property that makes `replace` a swap rather than a
|
|
1259
|
-
* delete-then-add that can strand the directory empty.
|
|
1263
|
+
* Validate one candidate route set for `adapter`, treating routes this
|
|
1264
|
+
* registration already holds as available. Nothing is mutated: a rejected
|
|
1265
|
+
* candidate leaves the registry exactly as it was.
|
|
1260
1266
|
*/
|
|
1261
|
-
|
|
1262
|
-
const
|
|
1263
|
-
const
|
|
1264
|
-
for (const
|
|
1265
|
-
if (
|
|
1266
|
-
if (
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1267
|
+
prepareRoutes(providers, adapter, owned) {
|
|
1268
|
+
const unique = /* @__PURE__ */ new Set();
|
|
1269
|
+
const registrations = [];
|
|
1270
|
+
for (const provider of providers) {
|
|
1271
|
+
if (provider.length === 0) throw new LlmError("adapter provider names must be non-empty", "INVALID_ADAPTER");
|
|
1272
|
+
if (unique.has(provider) || this.adapters.has(provider) && !owned.has(provider)) throw new LlmError(`an adapter for provider "${provider}" is already registered`, "DUPLICATE_ADAPTER");
|
|
1273
|
+
const info = adapter.providerInfo(provider);
|
|
1274
|
+
if (typeof info.id !== "string" || info.id !== provider || typeof info.name !== "string" || info.name.length === 0) throw new LlmError(`adapter metadata for provider "${provider}" must preserve its id and have a non-empty name`, "INVALID_ADAPTER");
|
|
1275
|
+
unique.add(provider);
|
|
1276
|
+
const retryPolicy = adapter.providerRetryPolicy(provider) ?? resolveRetryPolicy(void 0, `llm: provider "${provider}" retryPolicy`);
|
|
1277
|
+
registrations.push({
|
|
1278
|
+
adapter,
|
|
1279
|
+
provider: {
|
|
1280
|
+
id: info.id,
|
|
1281
|
+
name: info.name
|
|
1282
|
+
},
|
|
1283
|
+
retryPolicy
|
|
1271
1284
|
});
|
|
1272
1285
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1286
|
+
return registrations;
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Swap this registration's routes for the prepared ones in one synchronous
|
|
1290
|
+
* section, so no observer can see the registry between the release and the
|
|
1291
|
+
* re-registration. The route set's one mutation point is also where
|
|
1292
|
+
* `llm/adapters-updated` is published, so a `replace` announces itself
|
|
1293
|
+
* exactly like a first registration.
|
|
1294
|
+
*/
|
|
1295
|
+
commitRoutes(owned, registrations) {
|
|
1296
|
+
for (const provider of owned) this.adapters.delete(provider);
|
|
1297
|
+
owned.clear();
|
|
1298
|
+
for (const registration of registrations) {
|
|
1299
|
+
this.adapters.set(registration.provider.id, registration);
|
|
1300
|
+
owned.add(registration.provider.id);
|
|
1301
|
+
}
|
|
1276
1302
|
this.emitAdaptersUpdated();
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Describe provider routes with a registered adapter.
|
|
1306
|
+
* @returns detached provider metadata in registration order.
|
|
1307
|
+
*/
|
|
1308
|
+
listProviders() {
|
|
1309
|
+
return [...this.adapters.values()].map(({ provider }) => ({ ...provider }));
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Declare provider routes an adapter plugin can activate through
|
|
1313
|
+
* configuration. Registration is all-or-nothing: an empty list, invalid
|
|
1314
|
+
* entry, or a provider already declared by any registration throws
|
|
1315
|
+
* `LlmError` without registering the rest. Disposed with the fiber.
|
|
1316
|
+
* @param entries - every configurable provider this plugin owns.
|
|
1317
|
+
* @returns a handle that withdraws all of them, and can atomically replace them.
|
|
1318
|
+
*/
|
|
1319
|
+
registerConfigurableProviders(entries) {
|
|
1320
|
+
let held = [];
|
|
1321
|
+
let disposed = false;
|
|
1322
|
+
/**
|
|
1323
|
+
* Validate a candidate set in full against everything this registration
|
|
1324
|
+
* does not already hold, then publish it. Nothing is written until the
|
|
1325
|
+
* whole set passes, so a refused candidate leaves the current entries in
|
|
1326
|
+
* place — the property that makes `replace` a swap rather than a
|
|
1327
|
+
* delete-then-add that can strand the directory empty.
|
|
1328
|
+
*/
|
|
1329
|
+
const commit = (candidates) => {
|
|
1330
|
+
const detached = [];
|
|
1331
|
+
const own = new Set(held.map((entry) => entry.provider));
|
|
1332
|
+
for (const entry of candidates) {
|
|
1333
|
+
if (entry.provider.length === 0 || entry.displayName.length === 0 || entry.settingsNs.length === 0) throw new LlmError("configurable providers need a non-empty provider, displayName, and settingsNs", "INVALID_DIRECTORY");
|
|
1334
|
+
if (entry.settingsPath.some((segment) => segment.length === 0)) throw new LlmError(`configurable provider "${entry.provider}" has an empty settingsPath segment`, "INVALID_DIRECTORY");
|
|
1335
|
+
if (this.directory.has(entry.provider) && !own.has(entry.provider) || detached.some((seen) => seen.provider === entry.provider)) throw new LlmError(`configurable provider "${entry.provider}" is already declared`, "DUPLICATE_DIRECTORY");
|
|
1336
|
+
detached.push({
|
|
1337
|
+
...entry,
|
|
1338
|
+
settingsPath: [...entry.settingsPath]
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1283
1341
|
for (const entry of held) this.directory.delete(entry.provider);
|
|
1284
|
-
|
|
1342
|
+
for (const entry of detached) this.directory.set(entry.provider, entry);
|
|
1343
|
+
held = detached;
|
|
1285
1344
|
this.emitAdaptersUpdated();
|
|
1286
1345
|
};
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
...entry,
|
|
1302
|
-
settingsPath: [...entry.settingsPath]
|
|
1303
|
-
}));
|
|
1304
|
-
}
|
|
1305
|
-
/**
|
|
1306
|
-
* Offer to interrogate provider endpoints on behalf of the settings
|
|
1307
|
-
* namespace this plugin owns. The namespace is the key because that is what
|
|
1308
|
-
* a configuration surface already holds from the configurable-provider
|
|
1309
|
-
* directory, and because a provider being *added* has no route to name yet.
|
|
1310
|
-
* Disposed with the fiber.
|
|
1311
|
-
* @param settingsNs - the namespace whose profiles this discovery serves.
|
|
1312
|
-
* @param discover - interrogates one endpoint; must honor `request.signal`.
|
|
1313
|
-
* @returns the disposer that withdraws the offer.
|
|
1314
|
-
*/
|
|
1315
|
-
registerModelDiscovery(settingsNs, discover) {
|
|
1316
|
-
const dispose = this.ctx.effect(function* () {
|
|
1317
|
-
if (settingsNs.length === 0) throw new LlmError("model discovery needs a non-empty settings namespace", "INVALID_DISCOVERY");
|
|
1318
|
-
if (this.discoveries.has(settingsNs)) throw new LlmError(`model discovery for "${settingsNs}" is already registered`, "DUPLICATE_DISCOVERY");
|
|
1319
|
-
this.discoveries.set(settingsNs, discover);
|
|
1320
|
-
yield () => {
|
|
1321
|
-
this.discoveries.delete(settingsNs);
|
|
1346
|
+
const dispose = this.ctx.effect(function* () {
|
|
1347
|
+
if (entries.length === 0) throw new LlmError("a configurable-provider registration must declare at least one provider", "INVALID_DIRECTORY");
|
|
1348
|
+
commit(entries);
|
|
1349
|
+
yield () => {
|
|
1350
|
+
disposed = true;
|
|
1351
|
+
for (const entry of held) this.directory.delete(entry.provider);
|
|
1352
|
+
held = [];
|
|
1353
|
+
this.emitAdaptersUpdated();
|
|
1354
|
+
};
|
|
1355
|
+
}.bind(this), "llm.registerConfigurableProviders()");
|
|
1356
|
+
const handle = (() => void dispose());
|
|
1357
|
+
handle.replace = (next) => {
|
|
1358
|
+
if (disposed) throw new LlmError("this configurable-provider registration was disposed", "REGISTRATION_DISPOSED");
|
|
1359
|
+
commit(next);
|
|
1322
1360
|
};
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1361
|
+
return handle;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* List every declared configurable provider, registered or dormant.
|
|
1365
|
+
* @returns detached directory entries in declaration order.
|
|
1366
|
+
*/
|
|
1367
|
+
listConfigurableProviders() {
|
|
1368
|
+
return [...this.directory.values()].map((entry) => ({
|
|
1369
|
+
...entry,
|
|
1370
|
+
settingsPath: [...entry.settingsPath]
|
|
1371
|
+
}));
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Offer to interrogate provider endpoints on behalf of the settings
|
|
1375
|
+
* namespace this plugin owns. The namespace is the key because that is what
|
|
1376
|
+
* a configuration surface already holds from the configurable-provider
|
|
1377
|
+
* directory, and because a provider being *added* has no route to name yet.
|
|
1378
|
+
* Disposed with the fiber.
|
|
1379
|
+
* @param settingsNs - the namespace whose profiles this discovery serves.
|
|
1380
|
+
* @param discover - interrogates one endpoint and must honor the supplied signal.
|
|
1381
|
+
* @returns the disposer that withdraws the offer.
|
|
1382
|
+
*/
|
|
1383
|
+
registerModelDiscovery(settingsNs, discover) {
|
|
1384
|
+
const dispose = this.ctx.effect(function* () {
|
|
1385
|
+
if (settingsNs.length === 0) throw new LlmError("model discovery needs a non-empty settings namespace", "INVALID_DISCOVERY");
|
|
1386
|
+
if (this.discoveries.has(settingsNs)) throw new LlmError(`model discovery for "${settingsNs}" is already registered`, "DUPLICATE_DISCOVERY");
|
|
1387
|
+
this.discoveries.set(settingsNs, discover);
|
|
1388
|
+
yield () => {
|
|
1389
|
+
this.discoveries.delete(settingsNs);
|
|
1390
|
+
};
|
|
1391
|
+
}.bind(this), "llm.registerModelDiscovery()");
|
|
1392
|
+
return () => void dispose();
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Interrogate one provider endpoint for the models it advertises. The
|
|
1396
|
+
* request describes a draft, not a stored route, so nothing here reads or
|
|
1397
|
+
* writes settings or credentials — the caller owns both, and the reply is
|
|
1398
|
+
* candidate metadata a surface may offer for adoption.
|
|
1399
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
1400
|
+
* @param request - the endpoint, protocol, and one-shot credential to use.
|
|
1401
|
+
* @param signal - caller cancellation.
|
|
1402
|
+
* @returns the advertised models, deduplicated in endpoint order.
|
|
1403
|
+
*/
|
|
1404
|
+
async discoverModels(settingsNs, request, signal) {
|
|
1405
|
+
const discover = this.discoveries.get(settingsNs);
|
|
1406
|
+
if (discover === void 0) throw new LlmError(`no model discovery is registered for "${settingsNs}"`, "NO_DISCOVERY");
|
|
1407
|
+
if ((request.provider ?? "").length === 0 && (request.baseURL ?? "").length === 0) throw new LlmError("model discovery needs a provider route or a baseURL", "INVALID_DISCOVERY");
|
|
1408
|
+
const discovered = signal === void 0 ? await discover(request) : await discover(request, signal);
|
|
1409
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1410
|
+
const models = [];
|
|
1411
|
+
for (const model of discovered) {
|
|
1412
|
+
if (typeof model.id !== "string" || model.id.length === 0 || seen.has(model.id)) continue;
|
|
1413
|
+
seen.add(model.id);
|
|
1414
|
+
models.push({
|
|
1415
|
+
id: model.id,
|
|
1416
|
+
...model.name === void 0 ? {} : { name: model.name },
|
|
1417
|
+
...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
|
|
1418
|
+
...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
return models;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Remote adapter for one draft provider interrogation.
|
|
1425
|
+
* @param settingsNs - namespace whose registered discovery serves this draft.
|
|
1426
|
+
* @param request - endpoint, protocol, and one-shot credential to use.
|
|
1427
|
+
* @param signal - caller cancellation supplied by the Remote carrier.
|
|
1428
|
+
* @returns advertised models in endpoint order.
|
|
1429
|
+
* @throws RemoteError with `llm/model-discovery-rejected` when discovery refuses or fails.
|
|
1430
|
+
*/
|
|
1431
|
+
async remoteDiscoverModels(settingsNs, request, signal) {
|
|
1432
|
+
try {
|
|
1433
|
+
return await this.discoverModels(settingsNs, request, signal);
|
|
1434
|
+
} catch (error) {
|
|
1435
|
+
throw new RemoteError("llm/model-discovery-rejected", error instanceof Error ? error.message : String(error), {
|
|
1436
|
+
settingsNs,
|
|
1437
|
+
...request.baseURL === void 0 ? {} : { baseURL: request.baseURL }
|
|
1438
|
+
}, { cause: error });
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Resolve the retry policy captured when one provider route was registered.
|
|
1443
|
+
* @param provider - registered provider route to inspect.
|
|
1444
|
+
* @returns the provider-owned policy, with normal defaults already resolved.
|
|
1445
|
+
*/
|
|
1446
|
+
providerRetryPolicy(provider) {
|
|
1447
|
+
return this.registration(provider).retryPolicy;
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Resolve provider-side request-image pricing for one exact route, or
|
|
1451
|
+
* `undefined` when the provider is unregistered or declares none. Unknown
|
|
1452
|
+
* providers degrade to `undefined` rather than throwing because callers
|
|
1453
|
+
* price durable history whose route may no longer be mounted.
|
|
1454
|
+
* @param provider - provider route named by a request header.
|
|
1455
|
+
* @param model - exact model id named by the same header.
|
|
1456
|
+
* @returns the owning adapter's image pricing for the route, when declared.
|
|
1457
|
+
*/
|
|
1458
|
+
imageRequestPricing(provider, model) {
|
|
1459
|
+
return this.adapters.get(provider)?.adapter.imageRequestPricing(provider, model);
|
|
1460
|
+
}
|
|
1461
|
+
/** Detach typed adapter-owned modality metadata. */
|
|
1462
|
+
detachedModalities(modalities) {
|
|
1463
|
+
return modalities === void 0 ? void 0 : [...modalities];
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Discover models advertised by one registered provider. Catalog membership
|
|
1467
|
+
* is advisory and never changes routing or request validation.
|
|
1468
|
+
* @param provider - registered provider route to inspect.
|
|
1469
|
+
* @returns detached model metadata in adapter-preferred order.
|
|
1470
|
+
*/
|
|
1471
|
+
async listModels(provider) {
|
|
1472
|
+
const models = await this.registration(provider).adapter.listModels(provider);
|
|
1473
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1474
|
+
return models.map((model) => {
|
|
1475
|
+
if (typeof model.provider !== "string" || model.provider !== provider || typeof model.id !== "string" || model.id.length === 0 || typeof model.name !== "string" || model.name.length === 0 || model.description !== void 0 && typeof model.description !== "string" || seen.has(model.id)) throw new LlmError(`adapter returned invalid or duplicate model metadata for provider "${provider}"`, "INVALID_CATALOG");
|
|
1476
|
+
seen.add(model.id);
|
|
1477
|
+
const inputModalities = this.detachedModalities(model.inputModalities);
|
|
1478
|
+
return {
|
|
1479
|
+
provider: model.provider,
|
|
1480
|
+
id: model.id,
|
|
1481
|
+
name: model.name,
|
|
1482
|
+
...model.description === void 0 ? {} : { description: model.description },
|
|
1483
|
+
...inputModalities === void 0 ? {} : { inputModalities }
|
|
1484
|
+
};
|
|
1350
1485
|
});
|
|
1351
1486
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
if (
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
...
|
|
1384
|
-
...
|
|
1487
|
+
/**
|
|
1488
|
+
* Resolve and validate all metadata from the adapter that owns one exact
|
|
1489
|
+
* route. The result is detached from adapter-owned objects; catalog
|
|
1490
|
+
* membership remains advisory and does not control request routing.
|
|
1491
|
+
* @param provider - registered provider route to inspect.
|
|
1492
|
+
* @param model - exact model id passed to the adapter.
|
|
1493
|
+
* @param signal - optional cancellation for adapter-owned asynchronous lookup.
|
|
1494
|
+
* @returns exact model identity plus available context and reasoning metadata.
|
|
1495
|
+
*/
|
|
1496
|
+
async resolveModelInfo(provider, model, signal) {
|
|
1497
|
+
return this.resolveModelInfoFor(this.registration(provider), model, signal);
|
|
1498
|
+
}
|
|
1499
|
+
async resolveModelInfoFor(registration, model, signal) {
|
|
1500
|
+
const resolved = await registration.adapter.resolveModel(registration.provider.id, model, signal);
|
|
1501
|
+
return this.normalizeModelInfo(registration, model, resolved);
|
|
1502
|
+
}
|
|
1503
|
+
/** Validate and detach one adapter-returned exact model result. */
|
|
1504
|
+
normalizeModelInfo(registration, model, resolved) {
|
|
1505
|
+
const provider = registration.provider.id;
|
|
1506
|
+
if (typeof resolved.provider !== "string" || resolved.provider !== provider || typeof resolved.id !== "string" || resolved.id !== model || typeof resolved.name !== "string" || resolved.name.length === 0 || resolved.description !== void 0 && typeof resolved.description !== "string") throw new LlmError(`adapter returned invalid exact model metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
1507
|
+
const context = resolved.context;
|
|
1508
|
+
if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
|
|
1509
|
+
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
1510
|
+
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
1511
|
+
if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
|
|
1512
|
+
const info = {
|
|
1513
|
+
provider,
|
|
1514
|
+
id: model,
|
|
1515
|
+
name: resolved.name,
|
|
1516
|
+
...resolved.description === void 0 ? {} : { description: resolved.description },
|
|
1517
|
+
...inputModalities === void 0 ? {} : { inputModalities },
|
|
1518
|
+
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
1519
|
+
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens }
|
|
1385
1520
|
};
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
async resolveModelInfoFor(registration, model, signal) {
|
|
1401
|
-
const resolved = await registration.adapter.resolveModel(registration.provider.id, model, signal);
|
|
1402
|
-
return this.normalizeModelInfo(registration, model, resolved);
|
|
1403
|
-
}
|
|
1404
|
-
/** Validate and detach one adapter-returned exact model result. */
|
|
1405
|
-
normalizeModelInfo(registration, model, resolved) {
|
|
1406
|
-
const provider = registration.provider.id;
|
|
1407
|
-
if (typeof resolved.provider !== "string" || resolved.provider !== provider || typeof resolved.id !== "string" || resolved.id !== model || typeof resolved.name !== "string" || resolved.name.length === 0 || resolved.description !== void 0 && typeof resolved.description !== "string") throw new LlmError(`adapter returned invalid exact model metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_INFO");
|
|
1408
|
-
const context = resolved.context;
|
|
1409
|
-
if (context !== void 0 && (!Number.isInteger(context.contextWindow) || context.contextWindow <= 0)) throw new LlmError(`adapter returned invalid context metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_CONTEXT");
|
|
1410
|
-
const inputModalities = this.detachedModalities(resolved.inputModalities);
|
|
1411
|
-
const defaultMaxTokens = resolved.defaultMaxTokens;
|
|
1412
|
-
if (defaultMaxTokens !== void 0 && (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0)) throw new LlmError(`adapter returned invalid default maxTokens for provider "${provider}" model "${model}"`, "INVALID_MODEL_MAX_TOKENS");
|
|
1413
|
-
const info = {
|
|
1414
|
-
provider,
|
|
1415
|
-
id: model,
|
|
1416
|
-
name: resolved.name,
|
|
1417
|
-
...resolved.description === void 0 ? {} : { description: resolved.description },
|
|
1418
|
-
...inputModalities === void 0 ? {} : { inputModalities },
|
|
1419
|
-
...context === void 0 ? {} : { context: { contextWindow: context.contextWindow } },
|
|
1420
|
-
...defaultMaxTokens === void 0 ? {} : { defaultMaxTokens }
|
|
1421
|
-
};
|
|
1422
|
-
const reasoning = resolved.reasoning;
|
|
1423
|
-
if (reasoning === void 0) return info;
|
|
1424
|
-
if (reasoning.efforts.length === 0) throw new LlmError(`adapter returned invalid reasoning metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1425
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1426
|
-
const efforts = reasoning.efforts.map((effort) => {
|
|
1427
|
-
if (typeof effort.id !== "string" || effort.id.length === 0 || typeof effort.name !== "string" || effort.name.length === 0 || effort.description !== void 0 && typeof effort.description !== "string" || seen.has(effort.id)) throw new LlmError(`adapter returned invalid or duplicate reasoning effort metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1428
|
-
seen.add(effort.id);
|
|
1521
|
+
const reasoning = resolved.reasoning;
|
|
1522
|
+
if (reasoning === void 0) return info;
|
|
1523
|
+
if (reasoning.efforts.length === 0) throw new LlmError(`adapter returned invalid reasoning metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1525
|
+
const efforts = reasoning.efforts.map((effort) => {
|
|
1526
|
+
if (typeof effort.id !== "string" || effort.id.length === 0 || typeof effort.name !== "string" || effort.name.length === 0 || effort.description !== void 0 && typeof effort.description !== "string" || seen.has(effort.id)) throw new LlmError(`adapter returned invalid or duplicate reasoning effort metadata for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1527
|
+
seen.add(effort.id);
|
|
1528
|
+
return {
|
|
1529
|
+
id: effort.id,
|
|
1530
|
+
name: effort.name,
|
|
1531
|
+
...effort.description === void 0 ? {} : { description: effort.description }
|
|
1532
|
+
};
|
|
1533
|
+
});
|
|
1534
|
+
if (reasoning.defaultEffort !== void 0 && !seen.has(reasoning.defaultEffort)) throw new LlmError(`adapter returned an unknown default reasoning effort for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1429
1535
|
return {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1536
|
+
...info,
|
|
1537
|
+
reasoning: {
|
|
1538
|
+
efforts,
|
|
1539
|
+
...reasoning.defaultEffort === void 0 ? {} : { defaultEffort: reasoning.defaultEffort }
|
|
1540
|
+
}
|
|
1433
1541
|
};
|
|
1434
|
-
});
|
|
1435
|
-
if (reasoning.defaultEffort !== void 0 && !seen.has(reasoning.defaultEffort)) throw new LlmError(`adapter returned an unknown default reasoning effort for provider "${provider}" model "${model}"`, "INVALID_MODEL_REASONING");
|
|
1436
|
-
return {
|
|
1437
|
-
...info,
|
|
1438
|
-
reasoning: {
|
|
1439
|
-
efforts,
|
|
1440
|
-
...reasoning.defaultEffort === void 0 ? {} : { defaultEffort: reasoning.defaultEffort }
|
|
1441
|
-
}
|
|
1442
|
-
};
|
|
1443
|
-
}
|
|
1444
|
-
/**
|
|
1445
|
-
* Validate a conversation call config against its exact model capability and
|
|
1446
|
-
* materialize adapter-configured defaults. Unsupported explicit efforts
|
|
1447
|
-
* reject before provider I/O; no clamping or aliasing is performed. This
|
|
1448
|
-
* standalone query does not bind a later dispatch; use {@link prepareCall}
|
|
1449
|
-
* when logging and streaming must share one adapter registration.
|
|
1450
|
-
* @param config - provider/model route and optional request controls.
|
|
1451
|
-
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
1452
|
-
* @returns a detached config only when a default must be materialized.
|
|
1453
|
-
*/
|
|
1454
|
-
async resolveCallConfig(config, signal) {
|
|
1455
|
-
return (await this.resolveCallFor(this.registration(config.provider), config, signal)).config;
|
|
1456
|
-
}
|
|
1457
|
-
async resolveCallFor(registration, config, signal) {
|
|
1458
|
-
const info = await this.resolveModelInfoFor(registration, config.model, signal);
|
|
1459
|
-
return this.resolveCallWithInfo(config, info);
|
|
1460
|
-
}
|
|
1461
|
-
/** Validate request controls against one already-bound exact model result. */
|
|
1462
|
-
resolveCallWithInfo(config, info) {
|
|
1463
|
-
const defaulted = config.maxTokens === void 0 && info.defaultMaxTokens !== void 0 ? {
|
|
1464
|
-
...config,
|
|
1465
|
-
maxTokens: info.defaultMaxTokens
|
|
1466
|
-
} : config;
|
|
1467
|
-
const reasoning = info.reasoning;
|
|
1468
|
-
const requested = defaulted.reasoningEffort;
|
|
1469
|
-
let resolvedConfig = defaulted;
|
|
1470
|
-
if (reasoning === void 0) {
|
|
1471
|
-
if (requested !== void 0) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
1472
|
-
} else {
|
|
1473
|
-
const effective = requested ?? reasoning.defaultEffort;
|
|
1474
|
-
if (effective !== void 0) {
|
|
1475
|
-
if (!reasoning.efforts.some((effort) => effort.id === effective)) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
1476
|
-
if (requested !== effective) resolvedConfig = {
|
|
1477
|
-
...defaulted,
|
|
1478
|
-
reasoningEffort: effective
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
1542
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
registration,
|
|
1520
|
-
config: resolvedConfig,
|
|
1521
|
-
modelInfo,
|
|
1522
|
-
dispatch: (options) => adapterCall.stream(options)
|
|
1523
|
-
});
|
|
1543
|
+
/**
|
|
1544
|
+
* Validate a conversation call config against its exact model capability and
|
|
1545
|
+
* materialize adapter-configured defaults. Unsupported explicit efforts
|
|
1546
|
+
* reject before provider I/O; no clamping or aliasing is performed. This
|
|
1547
|
+
* standalone query does not bind a later dispatch; use {@link prepareCall}
|
|
1548
|
+
* when logging and streaming must share one adapter registration.
|
|
1549
|
+
* @param config - provider/model route and optional request controls.
|
|
1550
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
1551
|
+
* @returns a detached config only when a default must be materialized.
|
|
1552
|
+
*/
|
|
1553
|
+
async resolveCallConfig(config, signal) {
|
|
1554
|
+
return (await this.resolveCallFor(this.registration(config.provider), config, signal)).config;
|
|
1555
|
+
}
|
|
1556
|
+
async resolveCallFor(registration, config, signal) {
|
|
1557
|
+
const info = await this.resolveModelInfoFor(registration, config.model, signal);
|
|
1558
|
+
return this.resolveCallWithInfo(config, info);
|
|
1559
|
+
}
|
|
1560
|
+
/** Validate request controls against one already-bound exact model result. */
|
|
1561
|
+
resolveCallWithInfo(config, info) {
|
|
1562
|
+
const defaulted = config.maxTokens === void 0 && info.defaultMaxTokens !== void 0 ? {
|
|
1563
|
+
...config,
|
|
1564
|
+
maxTokens: info.defaultMaxTokens
|
|
1565
|
+
} : config;
|
|
1566
|
+
const reasoning = info.reasoning;
|
|
1567
|
+
const requested = defaulted.reasoningEffort;
|
|
1568
|
+
let resolvedConfig = defaulted;
|
|
1569
|
+
if (reasoning === void 0) {
|
|
1570
|
+
if (requested !== void 0) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${requested}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
1571
|
+
} else {
|
|
1572
|
+
const effective = requested ?? reasoning.defaultEffort;
|
|
1573
|
+
if (effective !== void 0) {
|
|
1574
|
+
if (!reasoning.efforts.some((effort) => effort.id === effective)) throw new LlmError(`provider "${config.provider}" model "${config.model}" does not support reasoning effort "${effective}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
1575
|
+
if (requested !== effective) resolvedConfig = {
|
|
1576
|
+
...defaulted,
|
|
1577
|
+
reasoningEffort: effective
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1524
1580
|
}
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1581
|
+
return {
|
|
1582
|
+
config: resolvedConfig,
|
|
1583
|
+
...info.context === void 0 ? {} : { context: info.context },
|
|
1584
|
+
modelInfo: info
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Resolve one call under its current adapter registration. The returned
|
|
1589
|
+
* one-shot handle keeps that registration across header logging and dispatch,
|
|
1590
|
+
* so HMR cannot combine one adapter's capability result with another adapter.
|
|
1591
|
+
* @param config - provider/model route and optional request controls.
|
|
1592
|
+
* @param signal - optional cancellation for adapter-owned capability lookup.
|
|
1593
|
+
* @returns a prepared config and its registration-bound stream entry point.
|
|
1594
|
+
*/
|
|
1595
|
+
async prepareCall(config, signal) {
|
|
1596
|
+
const registration = this.registration(config.provider);
|
|
1597
|
+
const adapterCall = await registration.adapter.prepareCall(config.provider, config.model, signal);
|
|
1598
|
+
const modelInfo = this.normalizeModelInfo(registration, config.model, adapterCall.model);
|
|
1599
|
+
const resolved = this.resolveCallWithInfo(config, modelInfo);
|
|
1600
|
+
const resolvedConfig = deepFreeze(structuredClone(resolved.config));
|
|
1601
|
+
const context = resolved.context === void 0 ? void 0 : deepFreeze(structuredClone(resolved.context));
|
|
1602
|
+
const adapterDefaults = deepFreeze({
|
|
1603
|
+
...config.reasoningEffort === void 0 && resolvedConfig.reasoningEffort !== void 0 ? { reasoningEffort: true } : {},
|
|
1604
|
+
...config.maxTokens === void 0 && resolvedConfig.maxTokens !== void 0 ? { maxTokens: true } : {}
|
|
1605
|
+
});
|
|
1606
|
+
let dispatched = false;
|
|
1607
|
+
return Object.freeze({
|
|
1608
|
+
config: resolvedConfig,
|
|
1609
|
+
retryPolicy: registration.retryPolicy,
|
|
1610
|
+
adapterDefaults,
|
|
1611
|
+
...context === void 0 ? {} : { context },
|
|
1612
|
+
...modelInfo.inputModalities === void 0 ? {} : { inputModalities: Object.freeze([...modelInfo.inputModalities]) },
|
|
1613
|
+
stream: (options) => {
|
|
1614
|
+
if (dispatched) throw new LlmError("a prepared LLM call can only be dispatched once", "INVALID_PREPARED_CALL");
|
|
1615
|
+
if (!callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
1616
|
+
dispatched = true;
|
|
1617
|
+
return this.streamWithRegistration(options, {
|
|
1618
|
+
registration,
|
|
1619
|
+
config: resolvedConfig,
|
|
1620
|
+
modelInfo,
|
|
1621
|
+
dispatch: (options) => adapterCall.stream(options)
|
|
1622
|
+
});
|
|
1544
1623
|
}
|
|
1545
1624
|
});
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
if (
|
|
1568
|
-
|
|
1569
|
-
modelInfo = this.normalizeModelInfo(registration, options.model, adapterCall.model);
|
|
1570
|
-
resolvedConfig = this.resolveCallWithInfo(options, modelInfo).config;
|
|
1571
|
-
dispatch = (options) => adapterCall.stream(options);
|
|
1572
|
-
} else {
|
|
1573
|
-
modelInfo = prepared.modelInfo;
|
|
1574
|
-
resolvedConfig = prepared.config;
|
|
1575
|
-
dispatch = prepared.dispatch;
|
|
1576
|
-
}
|
|
1577
|
-
if (prepared !== void 0 && !callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
1578
|
-
const resolvedOptions = callConfigEquals(options, resolvedConfig) ? options : Object.isFrozen(options) ? deepFreeze({
|
|
1579
|
-
...options,
|
|
1580
|
-
...resolvedConfig
|
|
1581
|
-
}) : {
|
|
1625
|
+
}
|
|
1626
|
+
registration(provider) {
|
|
1627
|
+
const registration = this.adapters.get(provider);
|
|
1628
|
+
if (!registration) throw new LlmError(`no adapter registered for provider "${provider}"`, "NO_ADAPTER");
|
|
1629
|
+
return registration;
|
|
1630
|
+
}
|
|
1631
|
+
/** Remove replay state whose historical route is owned by another adapter. */
|
|
1632
|
+
forAdapter(options, adapter) {
|
|
1633
|
+
const messages = options.messages.map((message) => {
|
|
1634
|
+
const source = message.source;
|
|
1635
|
+
if (message.role !== "assistant" || source.kind !== "model" || source.replayState === void 0) return message;
|
|
1636
|
+
if (this.adapters.get(source.provider)?.adapter === adapter) return message;
|
|
1637
|
+
return freezeMessage({
|
|
1638
|
+
...message,
|
|
1639
|
+
source: {
|
|
1640
|
+
kind: "model",
|
|
1641
|
+
provider: source.provider,
|
|
1642
|
+
model: source.model
|
|
1643
|
+
}
|
|
1644
|
+
});
|
|
1645
|
+
});
|
|
1646
|
+
if (messages.every((message, index) => message === options.messages[index])) return options;
|
|
1647
|
+
const filtered = {
|
|
1582
1648
|
...options,
|
|
1583
|
-
|
|
1649
|
+
messages
|
|
1584
1650
|
};
|
|
1585
|
-
|
|
1586
|
-
...resolvedOptions,
|
|
1587
|
-
messages: projectImagesForTextModel(resolvedOptions.messages)
|
|
1588
|
-
}) : {
|
|
1589
|
-
...resolvedOptions,
|
|
1590
|
-
messages: projectImagesForTextModel(resolvedOptions.messages)
|
|
1591
|
-
} : resolvedOptions;
|
|
1592
|
-
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
1593
|
-
} catch (error) {
|
|
1594
|
-
yield adapterFailureChunk(error, options.signal);
|
|
1595
|
-
return;
|
|
1651
|
+
return Object.isFrozen(options) ? deepFreeze(filtered) : filtered;
|
|
1596
1652
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1653
|
+
/**
|
|
1654
|
+
* Final adapter boundary. Adapter selection, dispatch, iterator construction,
|
|
1655
|
+
* and iteration failures become one terminal failure chunk. Middleware and
|
|
1656
|
+
* downstream consumer failures remain thrown plugin or consumer errors.
|
|
1657
|
+
*/
|
|
1658
|
+
async *adapterStream(options, prepared) {
|
|
1659
|
+
let iterator;
|
|
1660
|
+
try {
|
|
1661
|
+
const registration = prepared?.registration ?? this.registration(options.provider);
|
|
1662
|
+
const adapter = registration.adapter;
|
|
1663
|
+
let modelInfo;
|
|
1664
|
+
let resolvedConfig;
|
|
1665
|
+
let dispatch;
|
|
1666
|
+
if (prepared === void 0) {
|
|
1667
|
+
const adapterCall = await adapter.prepareCall(options.provider, options.model, options.signal);
|
|
1668
|
+
modelInfo = this.normalizeModelInfo(registration, options.model, adapterCall.model);
|
|
1669
|
+
resolvedConfig = this.resolveCallWithInfo(options, modelInfo).config;
|
|
1670
|
+
dispatch = (options) => adapterCall.stream(options);
|
|
1671
|
+
} else {
|
|
1672
|
+
modelInfo = prepared.modelInfo;
|
|
1673
|
+
resolvedConfig = prepared.config;
|
|
1674
|
+
dispatch = prepared.dispatch;
|
|
1615
1675
|
}
|
|
1616
|
-
|
|
1676
|
+
if (prepared !== void 0 && !callConfigEquals(options, resolvedConfig)) throw new LlmError("prepared LLM call config changed before adapter dispatch", "INVALID_PREPARED_CALL");
|
|
1677
|
+
const resolvedOptions = callConfigEquals(options, resolvedConfig) ? options : Object.isFrozen(options) ? deepFreeze({
|
|
1678
|
+
...options,
|
|
1679
|
+
...resolvedConfig
|
|
1680
|
+
}) : {
|
|
1681
|
+
...options,
|
|
1682
|
+
...resolvedConfig
|
|
1683
|
+
};
|
|
1684
|
+
const projectedOptions = modelInfo.inputModalities !== void 0 && !modelInfo.inputModalities.includes("image") && resolvedOptions.messages.some((message) => contentHasImage(message.content)) ? Object.isFrozen(resolvedOptions) ? deepFreeze({
|
|
1685
|
+
...resolvedOptions,
|
|
1686
|
+
messages: projectImagesForTextModel(resolvedOptions.messages)
|
|
1687
|
+
}) : {
|
|
1688
|
+
...resolvedOptions,
|
|
1689
|
+
messages: projectImagesForTextModel(resolvedOptions.messages)
|
|
1690
|
+
} : resolvedOptions;
|
|
1691
|
+
iterator = dispatch(this.forAdapter(projectedOptions, adapter))[Symbol.asyncIterator]();
|
|
1692
|
+
} catch (error) {
|
|
1693
|
+
yield adapterFailureChunk(error, options.signal);
|
|
1694
|
+
return;
|
|
1617
1695
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1696
|
+
let completed = false;
|
|
1697
|
+
try {
|
|
1698
|
+
while (true) {
|
|
1699
|
+
let item;
|
|
1700
|
+
try {
|
|
1701
|
+
const next = await iterator.next();
|
|
1702
|
+
item = next.done ? { done: true } : {
|
|
1703
|
+
done: false,
|
|
1704
|
+
value: next.value
|
|
1705
|
+
};
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
completed = true;
|
|
1708
|
+
yield adapterFailureChunk(error, options.signal);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (item.done) {
|
|
1712
|
+
completed = true;
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
yield item.value;
|
|
1716
|
+
}
|
|
1717
|
+
} finally {
|
|
1718
|
+
if (!completed) {
|
|
1719
|
+
const close = iterator.return?.bind(iterator);
|
|
1720
|
+
if (close) await close();
|
|
1721
|
+
}
|
|
1622
1722
|
}
|
|
1623
1723
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
};
|
|
1724
|
+
/**
|
|
1725
|
+
* Stream one model call as raw chunks (token-level deltas). Replay state is
|
|
1726
|
+
* retained only when the same adapter instance owns its historical provider
|
|
1727
|
+
* and the target provider. Final adapter selection remains fixed through
|
|
1728
|
+
* asynchronous exact-model resolution and dispatch. Adapter selection,
|
|
1729
|
+
* dispatch, and iteration failures become terminal `error` or `aborted`
|
|
1730
|
+
* finish chunks; middleware, nested-call, cleanup, and consumer failures
|
|
1731
|
+
* remain thrown.
|
|
1732
|
+
* @param options - the full request; `options.provider` selects the adapter.
|
|
1733
|
+
* @returns the chunk stream, possibly wrapped by `llm/stream` listeners.
|
|
1734
|
+
*/
|
|
1735
|
+
stream(options) {
|
|
1736
|
+
return this.streamWithRegistration(options);
|
|
1737
|
+
}
|
|
1738
|
+
streamWithRegistration(options, prepared) {
|
|
1739
|
+
return this.ctx.waterfall(this, "llm/stream", options, () => this.adapterStream(options, prepared));
|
|
1740
|
+
}
|
|
1741
|
+
};
|
|
1742
|
+
})();
|
|
1643
1743
|
/** Convert one adapter throw into the stream protocol's terminal outcome. */
|
|
1644
1744
|
function adapterFailureChunk(error, signal) {
|
|
1645
1745
|
const failure = normalizeLlmFailure(error);
|
|
@@ -1655,4 +1755,4 @@ function adapterFailureChunk(error, signal) {
|
|
|
1655
1755
|
};
|
|
1656
1756
|
}
|
|
1657
1757
|
//#endregion
|
|
1658
|
-
export { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE,
|
|
1758
|
+
export { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, ToolCallId, assertUsableApiKey, attributionHeaders, boundContextSummary, callConfigEquals, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, errorChain, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, markAgentLoopRequest, normalizeApiKey, offloadRequestImagesWithPolicy, offloadedImagePrefixCount, offloadedImageText, projectImagesForTextModel, requestImageHandleText, resolveImageAttachmentAccess, resolveRetryPolicy, textOnlyImageText, userAgent };
|