@xlaunch/llm 0.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +174 -0
- package/lib/index.js +2300 -0
- package/lib/invariant.js +84 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +547 -0
- package/lib/typert.remote-client.d.ts +26 -0
- package/lib/typert.remote-client.js +102 -0
- package/lib/types/adapter-failure.d.ts +14 -0
- package/lib/types/adapter-failure.js +105 -0
- package/lib/types/api-key.d.ts +28 -0
- package/lib/types/api-key.js +34 -0
- package/lib/types/assembler.d.ts +75 -0
- package/lib/types/assembler.js +191 -0
- package/lib/types/assistant-stream.d.ts +166 -0
- package/lib/types/assistant-stream.js +458 -0
- package/lib/types/attribution.d.ts +47 -0
- package/lib/types/attribution.js +46 -0
- package/lib/types/brand.d.ts +56 -0
- package/lib/types/brand.js +53 -0
- package/lib/types/call-config.d.ts +53 -0
- package/lib/types/call-config.js +46 -0
- package/lib/types/content.d.ts +130 -0
- package/lib/types/content.js +284 -0
- package/lib/types/error.d.ts +73 -0
- package/lib/types/error.js +145 -0
- package/lib/types/index.d.ts +408 -0
- package/lib/types/index.js +920 -0
- package/lib/types/invariant.d.ts +13 -0
- package/lib/types/invariant.js +100 -0
- package/lib/types/message.d.ts +197 -0
- package/lib/types/message.js +82 -0
- package/lib/types/retry-policy.d.ts +66 -0
- package/lib/types/retry-policy.js +127 -0
- package/lib/types/types.d.ts +430 -0
- package/lib/types/types.js +7 -0
- package/package.json +81 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralize the non-secret product identity every provider request sends as `User-Agent`, keeping
|
|
3
|
+
* adapters from drifting. See
|
|
4
|
+
* `.agents/notes/implemented/architecture/2026-06-21-mandatory-app-attribution-headers.md`.
|
|
5
|
+
*
|
|
6
|
+
* App-attribution vocabulary for provider requests.
|
|
7
|
+
* @module @xlaunch/llm/attribution
|
|
8
|
+
*/
|
|
9
|
+
import { createRequire } from 'node:module';
|
|
10
|
+
// The package's own manifest is the single source of the version so the
|
|
11
|
+
// User-Agent cannot drift from what is published (`./package.json` is an
|
|
12
|
+
// export of this package; the relative path resolves from both `src/` and
|
|
13
|
+
// the bundled `lib/`).
|
|
14
|
+
const { version } = createRequire(import.meta.url)('../package.json');
|
|
15
|
+
/**
|
|
16
|
+
* The harness's own identity: the default every adapter sends. Deployments
|
|
17
|
+
* that need a white-label identity pass their own {@link AppIdentity} to
|
|
18
|
+
* {@link attributionHeaders} — omission falls back to this default; nothing
|
|
19
|
+
* can suppress attribution entirely.
|
|
20
|
+
*/
|
|
21
|
+
export const APP_IDENTITY = {
|
|
22
|
+
product: 'xlaunch',
|
|
23
|
+
version,
|
|
24
|
+
url: 'https://github.com/Northlatch-Labs-LLC/xlaunch-agent',
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The standard `User-Agent` value: `product/version (+url)`. The
|
|
28
|
+
* parenthesized `+url` comment is the conventional self-identification form
|
|
29
|
+
* (RFC 9110 §10.1.5 product + comment syntax).
|
|
30
|
+
* @param identity - the identity to render; defaults to {@link APP_IDENTITY}.
|
|
31
|
+
* @returns the ready-to-send header value.
|
|
32
|
+
*/
|
|
33
|
+
export function userAgent(identity = APP_IDENTITY) {
|
|
34
|
+
return `${identity.product}/${identity.version} (+${identity.url})`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the attribution headers an adapter must send on every provider
|
|
38
|
+
* request. Header names are lowercase (HTTP field names are case-insensitive
|
|
39
|
+
* on the wire).
|
|
40
|
+
* @param identity - the identity to send; defaults to {@link APP_IDENTITY} — omission cannot suppress attribution.
|
|
41
|
+
* @returns headers to merge into the provider request (currently just `user-agent`).
|
|
42
|
+
*/
|
|
43
|
+
export function attributionHeaders(identity = APP_IDENTITY) {
|
|
44
|
+
return { 'user-agent': userAgent(identity) };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=attribution.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xlaunch-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
|
+
* `@xlaunch/brand` so every owner of a cross-boundary id can brand it
|
|
7
|
+
* without depending on xlaunch-llm; see that package's README for the
|
|
8
|
+
* nominal-typing policy.
|
|
9
|
+
*
|
|
10
|
+
* @module @xlaunch/llm/brand
|
|
11
|
+
*/
|
|
12
|
+
import { type Branded } from '@xlaunch/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
|
+
/** Identity of one model streaming attempt, unique within one Agent lifecycle. */
|
|
41
|
+
export type LlmAttemptId = Branded<'LlmAttemptId'>;
|
|
42
|
+
/**
|
|
43
|
+
* Brand one loop-owned streaming attempt identifier.
|
|
44
|
+
* @param id - the opaque Agent-lifecycle-local identifier.
|
|
45
|
+
* @returns the same string with the attempt-id brand.
|
|
46
|
+
*/
|
|
47
|
+
export declare function LlmAttemptId(id: string): LlmAttemptId;
|
|
48
|
+
/** Adapter-owned identifier for one model's selectable reasoning effort. */
|
|
49
|
+
export type ReasoningEffortId = Branded<'ReasoningEffortId'>;
|
|
50
|
+
/**
|
|
51
|
+
* Brand an adapter-owned reasoning-effort identifier.
|
|
52
|
+
* @param id - the opaque identifier exposed by one model capability.
|
|
53
|
+
* @returns the same string, branded; no validation is performed.
|
|
54
|
+
*/
|
|
55
|
+
export declare function ReasoningEffortId(id: string): ReasoningEffortId;
|
|
56
|
+
//# sourceMappingURL=brand.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xlaunch-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
|
+
* `@xlaunch/brand` so every owner of a cross-boundary id can brand it
|
|
7
|
+
* without depending on xlaunch-llm; see that package's README for the
|
|
8
|
+
* nominal-typing policy.
|
|
9
|
+
*
|
|
10
|
+
* @module @xlaunch/llm/brand
|
|
11
|
+
*/
|
|
12
|
+
import { brandString } from '@xlaunch/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 one loop-owned streaming attempt identifier.
|
|
39
|
+
* @param id - the opaque Agent-lifecycle-local identifier.
|
|
40
|
+
* @returns the same string with the attempt-id brand.
|
|
41
|
+
*/
|
|
42
|
+
export function LlmAttemptId(id) {
|
|
43
|
+
return brandString(id);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Brand an adapter-owned reasoning-effort identifier.
|
|
47
|
+
* @param id - the opaque identifier exposed by one model capability.
|
|
48
|
+
* @returns the same string, branded; no validation is performed.
|
|
49
|
+
*/
|
|
50
|
+
export function ReasoningEffortId(id) {
|
|
51
|
+
return brandString(id);
|
|
52
|
+
}
|
|
53
|
+
//# 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 xlaunch-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 xlaunch-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 xlaunch-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 xlaunch-llm/call-config
|
|
7
|
+
*/
|
|
8
|
+
/** Process-local identities of request objects assembled by xlaunch-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 xlaunch-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 xlaunch-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,130 @@
|
|
|
1
|
+
/** Content-block structure helpers. @module @xlaunch/llm/content */
|
|
2
|
+
import type { ContentBlock } from './types.ts';
|
|
3
|
+
import type { Message } from './message.ts';
|
|
4
|
+
import type { AttachmentStore, FileAttachmentRef, ImageAttachmentRef, RequestImageAttachment } from '@xlaunch/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
|
+
/**
|
|
61
|
+
* True when typed model content contains a file block, walking nested
|
|
62
|
+
* tool-result content on the same recursion every file policy shares.
|
|
63
|
+
* @param content - typed model content blocks.
|
|
64
|
+
* @returns whether any nested block is a file.
|
|
65
|
+
*/
|
|
66
|
+
export declare function contentHasFile(content: readonly ContentBlock[]): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Stable model-facing handle for one durable file reference: the address of
|
|
69
|
+
* the verbatim stored copy and the instruction to read it on demand. This is
|
|
70
|
+
* the only representation a provider ever receives for a file.
|
|
71
|
+
* @param ref - durable verbatim file reference.
|
|
72
|
+
* @param readonlyPath - execution-world path of the stored copy, when resolvable.
|
|
73
|
+
* @returns deterministic handle text naming the file, its size, and its address.
|
|
74
|
+
*/
|
|
75
|
+
export declare function fileHandleText(ref: FileAttachmentRef, readonlyPath: string | undefined): string;
|
|
76
|
+
/**
|
|
77
|
+
* Project durable file history into deterministic handle text for every model
|
|
78
|
+
* route. Unlike images, no provider receives file blocks natively, so this
|
|
79
|
+
* projection is unconditional in request assembly.
|
|
80
|
+
* @param messages - complete request history.
|
|
81
|
+
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
82
|
+
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
83
|
+
*/
|
|
84
|
+
export declare function projectFilesToText(messages: readonly Message[], resolvePath: (ref: FileAttachmentRef) => string | undefined): readonly Message[];
|
|
85
|
+
/** Byte accounting and quantized removal policy for one request representation. */
|
|
86
|
+
export interface RequestImageOffloadPolicy {
|
|
87
|
+
/** Image count accepted by the route; omission leaves count unbounded. */
|
|
88
|
+
maxImages?: number;
|
|
89
|
+
/** Accumulated image bytes accepted by the route; omission leaves bytes unbounded. */
|
|
90
|
+
maxBytes?: number;
|
|
91
|
+
/** Number of excess images removed as one deterministic step. */
|
|
92
|
+
countQuantum?: number;
|
|
93
|
+
/** Number of excess bytes removed as one deterministic step. */
|
|
94
|
+
byteQuantum?: number;
|
|
95
|
+
/** Whether byte accounting uses raw file bytes or inline base64 length. */
|
|
96
|
+
representation: 'raw' | 'base64';
|
|
97
|
+
/** Resolve the encoded request-version length; omission uses normalized attachment bytes. */
|
|
98
|
+
byteLength?: (ref: ImageAttachmentRef) => number;
|
|
99
|
+
/** Build the model-visible replacement for each omitted attachment. */
|
|
100
|
+
placeholder: (ref: ImageAttachmentRef) => string;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Project durable image history into deterministic text for an exact text-only model.
|
|
104
|
+
* @param messages - complete request history.
|
|
105
|
+
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
106
|
+
*/
|
|
107
|
+
export declare function projectImagesForTextModel(messages: readonly Message[]): readonly Message[];
|
|
108
|
+
/**
|
|
109
|
+
* Number of oldest image occurrences one request projection removes, in whole
|
|
110
|
+
* count and byte quanta, once a route budget is exceeded. The result depends
|
|
111
|
+
* only on the represented lengths, so provider request pricing reproduces the
|
|
112
|
+
* exact serialization decision without building the projected messages.
|
|
113
|
+
* @param lengths - represented byte length of every occurrence, in request order.
|
|
114
|
+
* @param policy - count/byte budgets and removal quanta; unbounded when absent.
|
|
115
|
+
* @returns how many leading occurrences the projection replaces with placeholders.
|
|
116
|
+
*/
|
|
117
|
+
export declare function offloadedImagePrefixCount(lengths: readonly number[], policy: Pick<RequestImageOffloadPolicy, 'maxImages' | 'maxBytes' | 'countQuantum' | 'byteQuantum'>): number;
|
|
118
|
+
/**
|
|
119
|
+
* Return a deterministic transient projection whose oldest images are replaced
|
|
120
|
+
* in whole count and byte quanta after a route budget is exceeded. The target
|
|
121
|
+
* depends only on complete durable history: at 129 one-megabyte images under
|
|
122
|
+
* a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so
|
|
123
|
+
* 64 MiB remain; that removed prefix stays fixed until total history exceeds
|
|
124
|
+
* 192 MiB.
|
|
125
|
+
* @param messages - complete request history, oldest first.
|
|
126
|
+
* @param policy - route representation, budgets, and removal quanta.
|
|
127
|
+
* @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.
|
|
128
|
+
*/
|
|
129
|
+
export declare function offloadRequestImagesWithPolicy(messages: readonly Message[], policy: RequestImageOffloadPolicy): readonly Message[];
|
|
130
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/** Content-block structure helpers. @module @xlaunch/llm/content */
|
|
2
|
+
import { assertNever } from '@xlaunch/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
|
+
/**
|
|
93
|
+
* True when typed model content contains a file block, walking nested
|
|
94
|
+
* tool-result content on the same recursion every file policy shares.
|
|
95
|
+
* @param content - typed model content blocks.
|
|
96
|
+
* @returns whether any nested block is a file.
|
|
97
|
+
*/
|
|
98
|
+
export function contentHasFile(content) {
|
|
99
|
+
return content.some(block => block.type === 'file'
|
|
100
|
+
|| (block.type === 'tool-result' && contentHasFile(block.content)));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Stable model-facing handle for one durable file reference: the address of
|
|
104
|
+
* the verbatim stored copy and the instruction to read it on demand. This is
|
|
105
|
+
* the only representation a provider ever receives for a file.
|
|
106
|
+
* @param ref - durable verbatim file reference.
|
|
107
|
+
* @param readonlyPath - execution-world path of the stored copy, when resolvable.
|
|
108
|
+
* @returns deterministic handle text naming the file, its size, and its address.
|
|
109
|
+
*/
|
|
110
|
+
export function fileHandleText(ref, readonlyPath) {
|
|
111
|
+
const digest = String(ref.attachmentId).slice('sha256:'.length, 'sha256:'.length + 8);
|
|
112
|
+
const identity = `File ${quoted(ref.name)} (${ref.bytes} bytes, sha256:${digest})`;
|
|
113
|
+
if (readonlyPath === undefined) {
|
|
114
|
+
return `[${identity} was uploaded, but the current execution environment cannot access a readable path. Report that limitation if its contents are needed; do not claim to have read it.]`;
|
|
115
|
+
}
|
|
116
|
+
return `[${identity}: verbatim read-only copy saved at ${quoted(readonlyPath)}. Read that path with your file tools when its contents are needed; copy it to a writable location before modifying it. When delegating file work, include this saved path in the delegation prompt; only subagents sharing this execution environment can read it.]`;
|
|
117
|
+
}
|
|
118
|
+
/** Replace every file occurrence, including nested tool results, with handle text. */
|
|
119
|
+
function replaceFilesWithHandles(blocks, resolvePath) {
|
|
120
|
+
let next;
|
|
121
|
+
for (const [index, block] of blocks.entries()) {
|
|
122
|
+
if (block.type === 'file') {
|
|
123
|
+
next ??= blocks.slice(0, index);
|
|
124
|
+
next.push({ type: 'text', text: fileHandleText(block.attachment, resolvePath(block.attachment)) });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (block.type === 'tool-result') {
|
|
128
|
+
const content = replaceFilesWithHandles(block.content, resolvePath);
|
|
129
|
+
if (content !== block.content) {
|
|
130
|
+
next ??= blocks.slice(0, index);
|
|
131
|
+
next.push({ ...block, content });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
next?.push(block);
|
|
136
|
+
}
|
|
137
|
+
return next ?? blocks;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Project durable file history into deterministic handle text for every model
|
|
141
|
+
* route. Unlike images, no provider receives file blocks natively, so this
|
|
142
|
+
* projection is unconditional in request assembly.
|
|
143
|
+
* @param messages - complete request history.
|
|
144
|
+
* @param resolvePath - resolve one reference's current execution-world read path.
|
|
145
|
+
* @returns the original list without files, otherwise shallow message copies with handle text.
|
|
146
|
+
*/
|
|
147
|
+
export function projectFilesToText(messages, resolvePath) {
|
|
148
|
+
if (!messages.some(message => contentHasFile(message.content)))
|
|
149
|
+
return messages;
|
|
150
|
+
return messages.map((message) => {
|
|
151
|
+
const content = replaceFilesWithHandles(message.content, resolvePath);
|
|
152
|
+
return content === message.content ? message : { ...message, content };
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/** Base64 length of raw image bytes, including padding. */
|
|
156
|
+
function base64Length(bytes) {
|
|
157
|
+
return Math.ceil(bytes / 3) * 4;
|
|
158
|
+
}
|
|
159
|
+
/** Collect represented image lengths in request and nested-block order. */
|
|
160
|
+
function collectImageLengths(blocks, lengths, policy) {
|
|
161
|
+
for (const block of blocks) {
|
|
162
|
+
if (block.type === 'image') {
|
|
163
|
+
const bytes = policy.byteLength === undefined
|
|
164
|
+
? block.attachment.bytes
|
|
165
|
+
: policy.byteLength(block.attachment);
|
|
166
|
+
lengths.push(policy.representation === 'base64' ? base64Length(bytes) : bytes);
|
|
167
|
+
}
|
|
168
|
+
else if (block.type === 'tool-result') {
|
|
169
|
+
collectImageLengths(block.content, lengths, policy);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** Replace the first `remaining.count` image occurrences without mutating durable messages. */
|
|
174
|
+
function replaceOldestImages(blocks, remaining, placeholder) {
|
|
175
|
+
let next;
|
|
176
|
+
for (const [index, block] of blocks.entries()) {
|
|
177
|
+
if (block.type === 'image' && remaining.count > 0) {
|
|
178
|
+
remaining.count -= 1;
|
|
179
|
+
next ??= blocks.slice(0, index);
|
|
180
|
+
next.push({ type: 'text', text: placeholder(block.attachment) });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (block.type === 'tool-result') {
|
|
184
|
+
const content = replaceOldestImages(block.content, remaining, placeholder);
|
|
185
|
+
if (content !== block.content) {
|
|
186
|
+
next ??= blocks.slice(0, index);
|
|
187
|
+
next.push({ ...block, content });
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
next?.push(block);
|
|
192
|
+
}
|
|
193
|
+
return next ?? blocks;
|
|
194
|
+
}
|
|
195
|
+
/** Replace every image occurrence, including nested tool results, for a text-only model. */
|
|
196
|
+
function replaceImagesForTextModel(blocks) {
|
|
197
|
+
let next;
|
|
198
|
+
for (const [index, block] of blocks.entries()) {
|
|
199
|
+
if (block.type === 'image') {
|
|
200
|
+
next ??= blocks.slice(0, index);
|
|
201
|
+
next.push({ type: 'text', text: textOnlyImageText(block.attachment) });
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (block.type === 'tool-result') {
|
|
205
|
+
const content = replaceImagesForTextModel(block.content);
|
|
206
|
+
if (content !== block.content) {
|
|
207
|
+
next ??= blocks.slice(0, index);
|
|
208
|
+
next.push({ ...block, content });
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
next?.push(block);
|
|
213
|
+
}
|
|
214
|
+
return next ?? blocks;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Project durable image history into deterministic text for an exact text-only model.
|
|
218
|
+
* @param messages - complete request history.
|
|
219
|
+
* @returns the original list without images, otherwise shallow message copies with stable placeholders.
|
|
220
|
+
*/
|
|
221
|
+
export function projectImagesForTextModel(messages) {
|
|
222
|
+
if (!messages.some(message => contentHasImage(message.content)))
|
|
223
|
+
return messages;
|
|
224
|
+
return messages.map((message) => {
|
|
225
|
+
const content = replaceImagesForTextModel(message.content);
|
|
226
|
+
return content === message.content ? message : { ...message, content };
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Number of oldest image occurrences one request projection removes, in whole
|
|
231
|
+
* count and byte quanta, once a route budget is exceeded. The result depends
|
|
232
|
+
* only on the represented lengths, so provider request pricing reproduces the
|
|
233
|
+
* exact serialization decision without building the projected messages.
|
|
234
|
+
* @param lengths - represented byte length of every occurrence, in request order.
|
|
235
|
+
* @param policy - count/byte budgets and removal quanta; unbounded when absent.
|
|
236
|
+
* @returns how many leading occurrences the projection replaces with placeholders.
|
|
237
|
+
*/
|
|
238
|
+
export function offloadedImagePrefixCount(lengths, policy) {
|
|
239
|
+
const total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
240
|
+
const excessCount = policy.maxImages === undefined ? 0 : Math.max(0, lengths.length - policy.maxImages);
|
|
241
|
+
const excessBytes = policy.maxBytes === undefined ? 0 : Math.max(0, total - policy.maxBytes);
|
|
242
|
+
if (excessCount === 0 && excessBytes === 0)
|
|
243
|
+
return 0;
|
|
244
|
+
const countQuantum = policy.countQuantum ?? 1;
|
|
245
|
+
const byteQuantum = policy.byteQuantum ?? 1;
|
|
246
|
+
const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
|
|
247
|
+
const removeBytes = excessBytes === 0 ? 0 : Math.ceil(excessBytes / byteQuantum) * byteQuantum;
|
|
248
|
+
let count = 0;
|
|
249
|
+
let removedBytes = 0;
|
|
250
|
+
for (const imageBytes of lengths) {
|
|
251
|
+
const byteTargetMet = removeBytes === 0
|
|
252
|
+
|| (byteQuantum === 1 ? removedBytes >= removeBytes : removedBytes > removeBytes);
|
|
253
|
+
if (count >= removeCount && byteTargetMet)
|
|
254
|
+
break;
|
|
255
|
+
removedBytes += imageBytes;
|
|
256
|
+
count += 1;
|
|
257
|
+
}
|
|
258
|
+
return count;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Return a deterministic transient projection whose oldest images are replaced
|
|
262
|
+
* in whole count and byte quanta after a route budget is exceeded. The target
|
|
263
|
+
* depends only on complete durable history: at 129 one-megabyte images under
|
|
264
|
+
* a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so
|
|
265
|
+
* 64 MiB remain; that removed prefix stays fixed until total history exceeds
|
|
266
|
+
* 192 MiB.
|
|
267
|
+
* @param messages - complete request history, oldest first.
|
|
268
|
+
* @param policy - route representation, budgets, and removal quanta.
|
|
269
|
+
* @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.
|
|
270
|
+
*/
|
|
271
|
+
export function offloadRequestImagesWithPolicy(messages, policy) {
|
|
272
|
+
const lengths = [];
|
|
273
|
+
for (const message of messages)
|
|
274
|
+
collectImageLengths(message.content, lengths, policy);
|
|
275
|
+
const count = offloadedImagePrefixCount(lengths, policy);
|
|
276
|
+
if (count === 0)
|
|
277
|
+
return messages;
|
|
278
|
+
const remaining = { count };
|
|
279
|
+
return messages.map((message) => {
|
|
280
|
+
const content = replaceOldestImages(message.content, remaining, policy.placeholder);
|
|
281
|
+
return content === message.content ? message : { ...message, content };
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=content.js.map
|