@x1a0f3n9/dsh-llm 0.1.2-alpha.6

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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * dsh-llm's owned branded ids: tool-call correlation and provider request
3
+ * diagnostics.
4
+ *
5
+ * The `Branded<B>` primitive and stateless constructor live in
6
+ * `@x1a0f3n9/dsh-brand` so every owner of a cross-boundary id can brand it
7
+ * without depending on dsh-llm; see that package's README for the
8
+ * nominal-typing policy.
9
+ *
10
+ * @module @x1a0f3n9/dsh-llm/brand
11
+ */
12
+ import { type Branded } from '@x1a0f3n9/dsh-brand';
13
+ /** Stable identity carried by one message across inbox, log, and model-request boundaries. */
14
+ export type MessageId = Branded<'MessageId'>;
15
+ /**
16
+ * Brand a message identifier.
17
+ * @param id - the opaque message identifier.
18
+ * @returns the same string with the message-id brand.
19
+ */
20
+ export declare function MessageId(id: string): MessageId;
21
+ /**
22
+ * Correlates a model-issued tool call with its result. Provider-issued for
23
+ * real adapters; synthesized by mocks/assembler fallbacks.
24
+ */
25
+ export type ToolCallId = Branded<'ToolCallId'>;
26
+ /**
27
+ * Brand a string as a {@link ToolCallId}.
28
+ * @param id - the provider-issued or synthesized call id.
29
+ * @returns the same string with the tool-call-id brand.
30
+ */
31
+ export declare function ToolCallId(id: string): ToolCallId;
32
+ /** Provider-issued request identifier retained for diagnostics across package boundaries. */
33
+ export type ProviderRequestId = Branded<'ProviderRequestId'>;
34
+ /**
35
+ * Brand a provider-issued request identifier.
36
+ * @param id - the opaque provider-issued string.
37
+ * @returns the same string, branded; no validation is performed.
38
+ */
39
+ export declare function ProviderRequestId(id: string): ProviderRequestId;
40
+ /** Adapter-owned identifier for one model's selectable reasoning effort. */
41
+ export type ReasoningEffortId = Branded<'ReasoningEffortId'>;
42
+ /**
43
+ * Brand an adapter-owned reasoning-effort identifier.
44
+ * @param id - the opaque identifier exposed by one model capability.
45
+ * @returns the same string, branded; no validation is performed.
46
+ */
47
+ export declare function ReasoningEffortId(id: string): ReasoningEffortId;
48
+ //# sourceMappingURL=brand.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * dsh-llm's owned branded ids: tool-call correlation and provider request
3
+ * diagnostics.
4
+ *
5
+ * The `Branded<B>` primitive and stateless constructor live in
6
+ * `@x1a0f3n9/dsh-brand` so every owner of a cross-boundary id can brand it
7
+ * without depending on dsh-llm; see that package's README for the
8
+ * nominal-typing policy.
9
+ *
10
+ * @module @x1a0f3n9/dsh-llm/brand
11
+ */
12
+ import { brandString } from '@x1a0f3n9/dsh-brand';
13
+ /**
14
+ * Brand a message identifier.
15
+ * @param id - the opaque message identifier.
16
+ * @returns the same string with the message-id brand.
17
+ */
18
+ export function MessageId(id) {
19
+ return brandString(id);
20
+ }
21
+ /**
22
+ * Brand a string as a {@link ToolCallId}.
23
+ * @param id - the provider-issued or synthesized call id.
24
+ * @returns the same string with the tool-call-id brand.
25
+ */
26
+ export function ToolCallId(id) {
27
+ return brandString(id);
28
+ }
29
+ /**
30
+ * Brand a provider-issued request identifier.
31
+ * @param id - the opaque provider-issued string.
32
+ * @returns the same string, branded; no validation is performed.
33
+ */
34
+ export function ProviderRequestId(id) {
35
+ return brandString(id);
36
+ }
37
+ /**
38
+ * Brand an adapter-owned reasoning-effort identifier.
39
+ * @param id - the opaque identifier exposed by one model capability.
40
+ * @returns the same string, branded; no validation is performed.
41
+ */
42
+ export function ReasoningEffortId(id) {
43
+ return brandString(id);
44
+ }
45
+ //# sourceMappingURL=brand.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Conversation call configuration and freeze utilities. Provider routing,
3
+ * model, reasoning effort, and sampling values are request-header state that
4
+ * can affect cache reuse; request waterfalls replace them and the loop logs
5
+ * changed snapshots instead of allowing silent per-call drift.
6
+ * @module dsh-llm/call-config
7
+ */
8
+ import type { GenerateOptions } from './types.ts';
9
+ import type { ReasoningEffortId } from './brand.ts';
10
+ /**
11
+ * Provider, model, reasoning effort, and sampling scalars of one conversation's
12
+ * requests. Every field maps 1:1 onto the same-named `GenerateOptions` field;
13
+ * the loop builds requests from the logged header rather than accepting these
14
+ * per call.
15
+ */
16
+ export interface LlmCallConfig {
17
+ provider: string;
18
+ model: string;
19
+ reasoningEffort?: ReasoningEffortId;
20
+ temperature?: number;
21
+ maxTokens?: number;
22
+ stop?: string[];
23
+ }
24
+ /**
25
+ * Effective config fields supplied by exact-model adapter resolution rather
26
+ * than by the caller's request proposal.
27
+ */
28
+ export interface LlmCallConfigAdapterDefaults {
29
+ reasoningEffort?: true;
30
+ maxTokens?: true;
31
+ }
32
+ /**
33
+ * Field-wise equality over {@link LlmCallConfig} — the comparison a caller
34
+ * runs to decide whether a proposed configuration is a real change (worth a
35
+ * logged header snapshot) or the held one restated.
36
+ * @param a - one configuration.
37
+ * @param b - the other.
38
+ * @returns whether every field (including the `stop` list, element-wise) matches.
39
+ */
40
+ export declare function callConfigEquals(a: LlmCallConfig, b: LlmCallConfig): boolean;
41
+ /**
42
+ * Mark one exact request object as assembled by dsh-agent-loop.
43
+ * @param request - loop-owned request envelope before LLM dispatch.
44
+ * @returns the same request object marked as created by the process-local agent loop.
45
+ */
46
+ export declare function markAgentLoopRequest<T extends GenerateOptions>(request: T): T;
47
+ /**
48
+ * Test whether the exact request object was assembled by dsh-agent-loop.
49
+ * @param request - request envelope observed at the LLM waterfall.
50
+ * @returns whether {@link markAgentLoopRequest} recorded this object.
51
+ */
52
+ export declare function isAgentLoopRequest(request: GenerateOptions): boolean;
53
+ //# sourceMappingURL=call-config.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Conversation call configuration and freeze utilities. Provider routing,
3
+ * model, reasoning effort, and sampling values are request-header state that
4
+ * can affect cache reuse; request waterfalls replace them and the loop logs
5
+ * changed snapshots instead of allowing silent per-call drift.
6
+ * @module dsh-llm/call-config
7
+ */
8
+ /** Process-local identities of request objects assembled by dsh-agent-loop. */
9
+ const AGENT_LOOP_REQUESTS = new WeakSet();
10
+ /**
11
+ * Field-wise equality over {@link LlmCallConfig} — the comparison a caller
12
+ * runs to decide whether a proposed configuration is a real change (worth a
13
+ * logged header snapshot) or the held one restated.
14
+ * @param a - one configuration.
15
+ * @param b - the other.
16
+ * @returns whether every field (including the `stop` list, element-wise) matches.
17
+ */
18
+ export function callConfigEquals(a, b) {
19
+ if (a.provider !== b.provider
20
+ || a.model !== b.model
21
+ || a.reasoningEffort !== b.reasoningEffort
22
+ || a.temperature !== b.temperature
23
+ || a.maxTokens !== b.maxTokens)
24
+ return false;
25
+ if (a.stop === undefined || b.stop === undefined)
26
+ return a.stop === b.stop;
27
+ return a.stop.length === b.stop.length && a.stop.every((s, i) => s === b.stop?.[i]);
28
+ }
29
+ /**
30
+ * Mark one exact request object as assembled by dsh-agent-loop.
31
+ * @param request - loop-owned request envelope before LLM dispatch.
32
+ * @returns the same request object marked as created by the process-local agent loop.
33
+ */
34
+ export function markAgentLoopRequest(request) {
35
+ AGENT_LOOP_REQUESTS.add(request);
36
+ return request;
37
+ }
38
+ /**
39
+ * Test whether the exact request object was assembled by dsh-agent-loop.
40
+ * @param request - request envelope observed at the LLM waterfall.
41
+ * @returns whether {@link markAgentLoopRequest} recorded this object.
42
+ */
43
+ export function isAgentLoopRequest(request) {
44
+ return AGENT_LOOP_REQUESTS.has(request);
45
+ }
46
+ //# sourceMappingURL=call-config.js.map
@@ -0,0 +1,105 @@
1
+ /** Content-block structure helpers. @module @x1a0f3n9/dsh-llm/content */
2
+ import type { ContentBlock } from './types.ts';
3
+ import type { Message } from './message.ts';
4
+ import type { AttachmentStore, ImageAttachmentRef, RequestImageAttachment } from '@x1a0f3n9/dsh-attachment';
5
+ /** Execution-world path that model tools can use to read one normalized attachment. */
6
+ export interface ImageAttachmentAccess {
7
+ /** Absolute path to immutable normalized bytes; callers must treat it as read-only. */
8
+ readonlyPath: string;
9
+ }
10
+ /**
11
+ * Resolve current execution-world access for one durable image reference.
12
+ * @param ref - durable normalized attachment reference.
13
+ * @returns a read-only execution-world path, or undefined when unavailable.
14
+ */
15
+ export type ImageAttachmentAccessResolver = (ref: ImageAttachmentRef) => ImageAttachmentAccess | undefined;
16
+ /**
17
+ * Bridge one attachment provider's host object location into the mounted
18
+ * tool execution world. The consumer supplies the current filesystem
19
+ * provider's mapping without making attachment or LLM definitions depend on it.
20
+ * @param attachments - provider that owns the normalized attachment object.
21
+ * @param mapHostPath - map one absolute host path into the current tool execution world.
22
+ * @param ref - durable normalized attachment reference.
23
+ * @returns a read-only execution-world path, or undefined when either provider exposes no mapping.
24
+ * @throws an attachment error when the durable reference is invalid.
25
+ */
26
+ export declare function resolveImageAttachmentAccess(attachments: AttachmentStore, mapHostPath: (hostPath: string) => string | undefined, ref: ImageAttachmentRef): ImageAttachmentAccess | undefined;
27
+ /**
28
+ * Stable text shown to a model that cannot accept one durable image reference.
29
+ * @param ref - durable normalized attachment omitted from the request.
30
+ * @returns deterministic text-only placeholder.
31
+ */
32
+ export declare function textOnlyImageText(ref: ImageAttachmentRef): string;
33
+ /**
34
+ * Stable model-facing handle for one exact request image. Identity comes from
35
+ * the occurrence's own durable reference: request versions are prepared per
36
+ * attachment id, so one shared version may serve occurrences whose display
37
+ * names differ.
38
+ * @param ref - the occurrence's durable normalized attachment.
39
+ * @param version - exact request-image dimensions shown beside the text.
40
+ * @param access - optional path resolved for the current tool execution world.
41
+ * @returns attachment handle and request-image dimensions.
42
+ */
43
+ export declare function requestImageHandleText(ref: ImageAttachmentRef, version: Pick<RequestImageAttachment, 'width' | 'height'>, access?: ImageAttachmentAccess): string;
44
+ /**
45
+ * Stable per-image placeholder for a request-limit omission.
46
+ * @param ref - durable normalized attachment omitted from this request.
47
+ * @param access - optional provider-resolved path for model tools.
48
+ * @returns identity, normalized metadata, and the available recovery path.
49
+ */
50
+ export declare function offloadedImageText(ref: ImageAttachmentRef, access?: ImageAttachmentAccess): string;
51
+ /**
52
+ * True when typed model content contains an image block, walking nested
53
+ * tool-result content. This is the one recursive image walk shared by every
54
+ * image policy (capability gating, text-only serialization, compaction
55
+ * survey), so a consumer cannot silently diverge on nesting depth.
56
+ * @param content - typed model content blocks.
57
+ * @returns whether any nested block is an image.
58
+ */
59
+ export declare function contentHasImage(content: readonly ContentBlock[]): boolean;
60
+ /** Byte accounting and quantized removal policy for one request representation. */
61
+ export interface RequestImageOffloadPolicy {
62
+ /** Image count accepted by the route; omission leaves count unbounded. */
63
+ maxImages?: number;
64
+ /** Accumulated image bytes accepted by the route; omission leaves bytes unbounded. */
65
+ maxBytes?: number;
66
+ /** Number of excess images removed as one deterministic step. */
67
+ countQuantum?: number;
68
+ /** Number of excess bytes removed as one deterministic step. */
69
+ byteQuantum?: number;
70
+ /** Whether byte accounting uses raw file bytes or inline base64 length. */
71
+ representation: 'raw' | 'base64';
72
+ /** Resolve the encoded request-version length; omission uses normalized attachment bytes. */
73
+ byteLength?: (ref: ImageAttachmentRef) => number;
74
+ /** Build the model-visible replacement for each omitted attachment. */
75
+ placeholder: (ref: ImageAttachmentRef) => string;
76
+ }
77
+ /**
78
+ * Project durable image history into deterministic text for an exact text-only model.
79
+ * @param messages - complete request history.
80
+ * @returns the original list without images, otherwise shallow message copies with stable placeholders.
81
+ */
82
+ export declare function projectImagesForTextModel(messages: readonly Message[]): readonly Message[];
83
+ /**
84
+ * Number of oldest image occurrences one request projection removes, in whole
85
+ * count and byte quanta, once a route budget is exceeded. The result depends
86
+ * only on the represented lengths, so provider request pricing reproduces the
87
+ * exact serialization decision without building the projected messages.
88
+ * @param lengths - represented byte length of every occurrence, in request order.
89
+ * @param policy - count/byte budgets and removal quanta; unbounded when absent.
90
+ * @returns how many leading occurrences the projection replaces with placeholders.
91
+ */
92
+ export declare function offloadedImagePrefixCount(lengths: readonly number[], policy: Pick<RequestImageOffloadPolicy, 'maxImages' | 'maxBytes' | 'countQuantum' | 'byteQuantum'>): number;
93
+ /**
94
+ * Return a deterministic transient projection whose oldest images are replaced
95
+ * in whole count and byte quanta after a route budget is exceeded. The target
96
+ * depends only on complete durable history: at 129 one-megabyte images under
97
+ * a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so
98
+ * 64 MiB remain; that removed prefix stays fixed until total history exceeds
99
+ * 192 MiB.
100
+ * @param messages - complete request history, oldest first.
101
+ * @param policy - route representation, budgets, and removal quanta.
102
+ * @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.
103
+ */
104
+ export declare function offloadRequestImagesWithPolicy(messages: readonly Message[], policy: RequestImageOffloadPolicy): readonly Message[];
105
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1,221 @@
1
+ /** Content-block structure helpers. @module @x1a0f3n9/dsh-llm/content */
2
+ import { assertNever } from '@x1a0f3n9/dsh-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, walking nested
82
+ * tool-result content. This is the one recursive image walk shared by every
83
+ * image policy (capability gating, text-only serialization, compaction
84
+ * survey), so a consumer cannot silently diverge on nesting depth.
85
+ * @param content - typed model content blocks.
86
+ * @returns whether any nested block is an image.
87
+ */
88
+ export function contentHasImage(content) {
89
+ return content.some(block => block.type === 'image'
90
+ || (block.type === 'tool-result' && contentHasImage(block.content)));
91
+ }
92
+ /** Base64 length of raw image bytes, including padding. */
93
+ function base64Length(bytes) {
94
+ return Math.ceil(bytes / 3) * 4;
95
+ }
96
+ /** Collect represented image lengths in request and nested-block order. */
97
+ function collectImageLengths(blocks, lengths, policy) {
98
+ for (const block of blocks) {
99
+ if (block.type === 'image') {
100
+ const bytes = policy.byteLength === undefined
101
+ ? block.attachment.bytes
102
+ : policy.byteLength(block.attachment);
103
+ lengths.push(policy.representation === 'base64' ? base64Length(bytes) : bytes);
104
+ }
105
+ else if (block.type === 'tool-result') {
106
+ collectImageLengths(block.content, lengths, policy);
107
+ }
108
+ }
109
+ }
110
+ /** Replace the first `remaining.count` image occurrences without mutating durable messages. */
111
+ function replaceOldestImages(blocks, remaining, placeholder) {
112
+ let next;
113
+ for (const [index, block] of blocks.entries()) {
114
+ if (block.type === 'image' && remaining.count > 0) {
115
+ remaining.count -= 1;
116
+ next ??= blocks.slice(0, index);
117
+ next.push({ type: 'text', text: placeholder(block.attachment) });
118
+ continue;
119
+ }
120
+ if (block.type === 'tool-result') {
121
+ const content = replaceOldestImages(block.content, remaining, placeholder);
122
+ if (content !== block.content) {
123
+ next ??= blocks.slice(0, index);
124
+ next.push({ ...block, content });
125
+ continue;
126
+ }
127
+ }
128
+ next?.push(block);
129
+ }
130
+ return next ?? blocks;
131
+ }
132
+ /** Replace every image occurrence, including nested tool results, for a text-only model. */
133
+ function replaceImagesForTextModel(blocks) {
134
+ let next;
135
+ for (const [index, block] of blocks.entries()) {
136
+ if (block.type === 'image') {
137
+ next ??= blocks.slice(0, index);
138
+ next.push({ type: 'text', text: textOnlyImageText(block.attachment) });
139
+ continue;
140
+ }
141
+ if (block.type === 'tool-result') {
142
+ const content = replaceImagesForTextModel(block.content);
143
+ if (content !== block.content) {
144
+ next ??= blocks.slice(0, index);
145
+ next.push({ ...block, content });
146
+ continue;
147
+ }
148
+ }
149
+ next?.push(block);
150
+ }
151
+ return next ?? blocks;
152
+ }
153
+ /**
154
+ * Project durable image history into deterministic text for an exact text-only model.
155
+ * @param messages - complete request history.
156
+ * @returns the original list without images, otherwise shallow message copies with stable placeholders.
157
+ */
158
+ export function projectImagesForTextModel(messages) {
159
+ if (!messages.some(message => contentHasImage(message.content)))
160
+ return messages;
161
+ return messages.map((message) => {
162
+ const content = replaceImagesForTextModel(message.content);
163
+ return content === message.content ? message : { ...message, content };
164
+ });
165
+ }
166
+ /**
167
+ * Number of oldest image occurrences one request projection removes, in whole
168
+ * count and byte quanta, once a route budget is exceeded. The result depends
169
+ * only on the represented lengths, so provider request pricing reproduces the
170
+ * exact serialization decision without building the projected messages.
171
+ * @param lengths - represented byte length of every occurrence, in request order.
172
+ * @param policy - count/byte budgets and removal quanta; unbounded when absent.
173
+ * @returns how many leading occurrences the projection replaces with placeholders.
174
+ */
175
+ export function offloadedImagePrefixCount(lengths, policy) {
176
+ const total = lengths.reduce((sum, bytes) => sum + bytes, 0);
177
+ const excessCount = policy.maxImages === undefined ? 0 : Math.max(0, lengths.length - policy.maxImages);
178
+ const excessBytes = policy.maxBytes === undefined ? 0 : Math.max(0, total - policy.maxBytes);
179
+ if (excessCount === 0 && excessBytes === 0)
180
+ return 0;
181
+ const countQuantum = policy.countQuantum ?? 1;
182
+ const byteQuantum = policy.byteQuantum ?? 1;
183
+ const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
184
+ const removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;
185
+ let count = 0;
186
+ let removedBytes = 0;
187
+ for (const imageBytes of lengths) {
188
+ const byteTargetMet = removeBytes === 0
189
+ || (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes);
190
+ if (count >= removeCount && byteTargetMet)
191
+ break;
192
+ removedBytes += imageBytes;
193
+ count += 1;
194
+ }
195
+ return count;
196
+ }
197
+ /**
198
+ * Return a deterministic transient projection whose oldest images are replaced
199
+ * in whole count and byte quanta after a route budget is exceeded. The target
200
+ * depends only on complete durable history: at 129 one-megabyte images under
201
+ * a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so
202
+ * 64 MiB remain; that removed prefix stays fixed until total history exceeds
203
+ * 192 MiB.
204
+ * @param messages - complete request history, oldest first.
205
+ * @param policy - route representation, budgets, and removal quanta.
206
+ * @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.
207
+ */
208
+ export function offloadRequestImagesWithPolicy(messages, policy) {
209
+ const lengths = [];
210
+ for (const message of messages)
211
+ collectImageLengths(message.content, lengths, policy);
212
+ const count = offloadedImagePrefixCount(lengths, policy);
213
+ if (count === 0)
214
+ return messages;
215
+ const remaining = { count };
216
+ return messages.map((message) => {
217
+ const content = replaceOldestImages(message.content, remaining, policy.placeholder);
218
+ return content === message.content ? message : { ...message, content };
219
+ });
220
+ }
221
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,73 @@
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 @x1a0f3n9/dsh-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
+ //# sourceMappingURL=error.d.ts.map