@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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* Generated by @deepseek-ai/dsh-typert-generator from the Host FaceModel — do not edit. */
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
const _deepseek_ai_dsh_llm_llm_discoverModels_parameter_0$schema = z.string()
|
|
5
|
+
const _deepseek_ai_dsh_llm_llm_discoverModels_parameter_1$schema = z.object({
|
|
6
|
+
'provider': z.string().optional(),
|
|
7
|
+
'baseURL': z.string().optional(),
|
|
8
|
+
'api': z.string().optional(),
|
|
9
|
+
'apiKey': z.string().optional(),
|
|
10
|
+
})
|
|
11
|
+
const _deepseek_ai_dsh_llm_llm_discoverModels_result$schema = z.array(z.object({
|
|
12
|
+
'id': z.string(),
|
|
13
|
+
'name': z.string().optional(),
|
|
14
|
+
'contextWindow': z.number().optional(),
|
|
15
|
+
'maxTokens': z.number().optional(),
|
|
16
|
+
}))
|
|
17
|
+
const _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema = z.array(z.object({
|
|
18
|
+
'provider': z.string(),
|
|
19
|
+
'displayName': z.string(),
|
|
20
|
+
'settingsNs': z.string(),
|
|
21
|
+
'settingsPath': z.array(z.string()),
|
|
22
|
+
'declared': z.boolean().optional(),
|
|
23
|
+
}))
|
|
24
|
+
const _deepseek_ai_dsh_llm_llm_listProviders_result$schema = z.array(z.object({
|
|
25
|
+
'id': z.string(),
|
|
26
|
+
'name': z.string(),
|
|
27
|
+
}))
|
|
28
|
+
|
|
29
|
+
export const TYPERT_REMOTE = {
|
|
30
|
+
package: '@deepseek-ai/dsh-llm',
|
|
31
|
+
descriptors: [
|
|
32
|
+
{
|
|
33
|
+
id: '@deepseek-ai/dsh-llm#llm/discoverModels',
|
|
34
|
+
service: 'llm',
|
|
35
|
+
namespace: 'llm',
|
|
36
|
+
method: 'discoverModels',
|
|
37
|
+
implementation: 'remoteDiscoverModels',
|
|
38
|
+
invocation: { kind: 'direct' },
|
|
39
|
+
parameters: [
|
|
40
|
+
{
|
|
41
|
+
name: 'settingsNs',
|
|
42
|
+
wire: 'settingsNs',
|
|
43
|
+
source: 'json',
|
|
44
|
+
codec: {
|
|
45
|
+
mode: 'strict',
|
|
46
|
+
typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:settingsNs',
|
|
47
|
+
schema: _deepseek_ai_dsh_llm_llm_discoverModels_parameter_0$schema,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'request',
|
|
52
|
+
wire: 'request',
|
|
53
|
+
source: 'json',
|
|
54
|
+
codec: {
|
|
55
|
+
mode: 'strict',
|
|
56
|
+
typeSymbol: '@deepseek-ai/dsh-llm/types#LlmModelDiscoveryRequest',
|
|
57
|
+
schema: _deepseek_ai_dsh_llm_llm_discoverModels_parameter_1$schema,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
cancellation: { parameter: 'signal' },
|
|
62
|
+
result: {
|
|
63
|
+
mode: 'strict',
|
|
64
|
+
typeSymbol: '@deepseek-ai/dsh-llm#llm/discoverModels:result',
|
|
65
|
+
schema: _deepseek_ai_dsh_llm_llm_discoverModels_result$schema,
|
|
66
|
+
},
|
|
67
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":621,"column":9},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders',
|
|
71
|
+
service: 'llm',
|
|
72
|
+
namespace: 'llm',
|
|
73
|
+
method: 'listConfigurableProviders',
|
|
74
|
+
invocation: { kind: 'direct' },
|
|
75
|
+
parameters: [
|
|
76
|
+
],
|
|
77
|
+
result: {
|
|
78
|
+
mode: 'strict',
|
|
79
|
+
typeSymbol: '@deepseek-ai/dsh-llm#llm/listConfigurableProviders:result',
|
|
80
|
+
schema: _deepseek_ai_dsh_llm_llm_listConfigurableProviders_result$schema,
|
|
81
|
+
},
|
|
82
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":534,"column":3},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: '@deepseek-ai/dsh-llm#llm/listProviders',
|
|
86
|
+
service: 'llm',
|
|
87
|
+
namespace: 'llm',
|
|
88
|
+
method: 'listProviders',
|
|
89
|
+
invocation: { kind: 'direct' },
|
|
90
|
+
parameters: [
|
|
91
|
+
],
|
|
92
|
+
result: {
|
|
93
|
+
mode: 'strict',
|
|
94
|
+
typeSymbol: '@deepseek-ai/dsh-llm#llm/listProviders:result',
|
|
95
|
+
schema: _deepseek_ai_dsh_llm_llm_listProviders_result$schema,
|
|
96
|
+
},
|
|
97
|
+
sourceLocation: {"file":"packages/llm/llm/src/index.ts","line":462,"column":3},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default TYPERT_REMOTE
|
package/lib/types/assembler.js
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module @deepseek-ai/dsh-llm/assembler
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
9
|
-
import { assertNever } from
|
|
8
|
+
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
9
|
+
import { assertNever } from '@deepseek-ai/dsh-util-values';
|
|
10
10
|
import { createMessage } from "./message.js";
|
|
11
11
|
/**
|
|
12
12
|
* Incrementally assembles raw {@link StreamChunk}s into complete
|
|
@@ -99,7 +99,7 @@ export class BlockAssembler {
|
|
|
99
99
|
case 'reasoning': return { type: 'reasoning', text: partial.text };
|
|
100
100
|
case 'tool-call': return {
|
|
101
101
|
type: 'tool-call',
|
|
102
|
-
id: partial.toolCallId ??
|
|
102
|
+
id: partial.toolCallId ?? brandString(`call-${index}`),
|
|
103
103
|
name: partial.toolCallName ?? '',
|
|
104
104
|
arguments: partial.toolCallArguments,
|
|
105
105
|
};
|
package/lib/types/brand.d.ts
CHANGED
|
@@ -2,33 +2,33 @@
|
|
|
2
2
|
* dsh-llm's owned branded ids: tool-call correlation and provider request
|
|
3
3
|
* diagnostics.
|
|
4
4
|
*
|
|
5
|
-
* The `Branded<B>` primitive
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The `Branded<B>` primitive and stateless constructor live in
|
|
6
|
+
* `@deepseek-ai/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
8
|
* nominal-typing policy.
|
|
9
9
|
*
|
|
10
10
|
* @module @deepseek-ai/dsh-llm/brand
|
|
11
11
|
*/
|
|
12
|
-
import type
|
|
12
|
+
import { type Branded } from '@deepseek-ai/dsh-brand';
|
|
13
13
|
/** Stable identity carried by one message across inbox, log, and model-request boundaries. */
|
|
14
14
|
export type MessageId = Branded<'MessageId'>;
|
|
15
15
|
/**
|
|
16
16
|
* Brand a message identifier.
|
|
17
17
|
* @param id - the opaque message identifier.
|
|
18
|
-
* @returns the same string
|
|
18
|
+
* @returns the same string with the message-id brand.
|
|
19
19
|
*/
|
|
20
20
|
export declare function MessageId(id: string): MessageId;
|
|
21
21
|
/**
|
|
22
22
|
* Correlates a model-issued tool call with its result. Provider-issued for
|
|
23
23
|
* real adapters; synthesized by mocks/assembler fallbacks.
|
|
24
24
|
*/
|
|
25
|
-
export type
|
|
25
|
+
export type ToolCallId = Branded<'ToolCallId'>;
|
|
26
26
|
/**
|
|
27
|
-
* Brand a string as a {@link
|
|
28
|
-
* @param id - the provider-issued
|
|
29
|
-
* @returns the same string
|
|
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
30
|
*/
|
|
31
|
-
export declare function
|
|
31
|
+
export declare function ToolCallId(id: string): ToolCallId;
|
|
32
32
|
/** Provider-issued request identifier retained for diagnostics across package boundaries. */
|
|
33
33
|
export type ProviderRequestId = Branded<'ProviderRequestId'>;
|
|
34
34
|
/**
|
package/lib/types/brand.js
CHANGED
|
@@ -2,28 +2,29 @@
|
|
|
2
2
|
* dsh-llm's owned branded ids: tool-call correlation and provider request
|
|
3
3
|
* diagnostics.
|
|
4
4
|
*
|
|
5
|
-
* The `Branded<B>` primitive
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The `Branded<B>` primitive and stateless constructor live in
|
|
6
|
+
* `@deepseek-ai/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
8
|
* nominal-typing policy.
|
|
9
9
|
*
|
|
10
10
|
* @module @deepseek-ai/dsh-llm/brand
|
|
11
11
|
*/
|
|
12
|
+
import { brandString } from '@deepseek-ai/dsh-brand';
|
|
12
13
|
/**
|
|
13
14
|
* Brand a message identifier.
|
|
14
15
|
* @param id - the opaque message identifier.
|
|
15
|
-
* @returns the same string
|
|
16
|
+
* @returns the same string with the message-id brand.
|
|
16
17
|
*/
|
|
17
18
|
export function MessageId(id) {
|
|
18
|
-
return id;
|
|
19
|
+
return brandString(id);
|
|
19
20
|
}
|
|
20
21
|
/**
|
|
21
|
-
* Brand a string as a {@link
|
|
22
|
-
* @param id - the provider-issued
|
|
23
|
-
* @returns the same string
|
|
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.
|
|
24
25
|
*/
|
|
25
|
-
export function
|
|
26
|
-
return id;
|
|
26
|
+
export function ToolCallId(id) {
|
|
27
|
+
return brandString(id);
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Brand a provider-issued request identifier.
|
|
@@ -31,7 +32,7 @@ export function CallId(id) {
|
|
|
31
32
|
* @returns the same string, branded; no validation is performed.
|
|
32
33
|
*/
|
|
33
34
|
export function ProviderRequestId(id) {
|
|
34
|
-
return id;
|
|
35
|
+
return brandString(id);
|
|
35
36
|
}
|
|
36
37
|
/**
|
|
37
38
|
* Brand an adapter-owned reasoning-effort identifier.
|
|
@@ -39,6 +40,6 @@ export function ProviderRequestId(id) {
|
|
|
39
40
|
* @returns the same string, branded; no validation is performed.
|
|
40
41
|
*/
|
|
41
42
|
export function ReasoningEffortId(id) {
|
|
42
|
-
return id;
|
|
43
|
+
return brandString(id);
|
|
43
44
|
}
|
|
44
45
|
//# sourceMappingURL=brand.js.map
|
|
@@ -50,13 +50,4 @@ export declare function markAgentLoopRequest<T extends GenerateOptions>(request:
|
|
|
50
50
|
* @returns whether {@link markAgentLoopRequest} recorded this object.
|
|
51
51
|
*/
|
|
52
52
|
export declare function isAgentLoopRequest(request: GenerateOptions): boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Deep-freeze a value in place with an iterative traversal, guarding cycles,
|
|
55
|
-
* so later mutation throws without imposing a JavaScript call-stack depth cap.
|
|
56
|
-
* {@link AbortSignal} objects are deliberately skipped because they are the
|
|
57
|
-
* request's live cancellation channel and freezing them breaks abort.
|
|
58
|
-
* @param value - the value to freeze in place.
|
|
59
|
-
* @returns the same value, frozen.
|
|
60
|
-
*/
|
|
61
|
-
export declare function deepFreeze<T>(value: T): T;
|
|
62
53
|
//# sourceMappingURL=call-config.d.ts.map
|
package/lib/types/call-config.js
CHANGED
|
@@ -43,44 +43,4 @@ export function markAgentLoopRequest(request) {
|
|
|
43
43
|
export function isAgentLoopRequest(request) {
|
|
44
44
|
return AGENT_LOOP_REQUESTS.has(request);
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
47
|
-
* Deep-freeze a value in place with an iterative traversal, guarding cycles,
|
|
48
|
-
* so later mutation throws without imposing a JavaScript call-stack depth cap.
|
|
49
|
-
* {@link AbortSignal} objects are deliberately skipped because they are the
|
|
50
|
-
* request's live cancellation channel and freezing them breaks abort.
|
|
51
|
-
* @param value - the value to freeze in place.
|
|
52
|
-
* @returns the same value, frozen.
|
|
53
|
-
*/
|
|
54
|
-
export function deepFreeze(value) {
|
|
55
|
-
const seen = new WeakSet();
|
|
56
|
-
const pending = [{ kind: 'visit', node: value }];
|
|
57
|
-
while (pending.length > 0) {
|
|
58
|
-
const task = pending.pop();
|
|
59
|
-
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
60
|
-
if (task === undefined)
|
|
61
|
-
continue;
|
|
62
|
-
if (task.kind === 'property') {
|
|
63
|
-
pending.push({ kind: 'visit', node: task.source[task.key] });
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const node = task.node;
|
|
67
|
-
if (node === null || typeof node !== 'object')
|
|
68
|
-
continue;
|
|
69
|
-
if (node instanceof AbortSignal)
|
|
70
|
-
continue;
|
|
71
|
-
if (seen.has(node))
|
|
72
|
-
continue;
|
|
73
|
-
seen.add(node);
|
|
74
|
-
Object.freeze(node);
|
|
75
|
-
const keys = Object.keys(node);
|
|
76
|
-
for (let index = keys.length - 1; index >= 0; index--) {
|
|
77
|
-
const key = keys[index];
|
|
78
|
-
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
79
|
-
if (key === undefined)
|
|
80
|
-
continue;
|
|
81
|
-
pending.push({ kind: 'property', source: node, key });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
86
46
|
//# sourceMappingURL=call-config.js.map
|
package/lib/types/content.d.ts
CHANGED
|
@@ -1,21 +1,53 @@
|
|
|
1
1
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
2
2
|
import type { ContentBlock } from './types.ts';
|
|
3
3
|
import type { Message } from './message.ts';
|
|
4
|
-
import type { ImageAttachmentRef, RequestImageAttachment } from '@deepseek-ai/dsh-attachment';
|
|
5
|
-
/**
|
|
6
|
-
export
|
|
4
|
+
import type { AttachmentStore, ImageAttachmentRef, RequestImageAttachment } from '@deepseek-ai/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;
|
|
7
27
|
/**
|
|
8
28
|
* Stable text shown to a model that cannot accept one durable image reference.
|
|
9
|
-
* @param ref - durable
|
|
29
|
+
* @param ref - durable normalized attachment omitted from the request.
|
|
10
30
|
* @returns deterministic text-only placeholder.
|
|
11
31
|
*/
|
|
12
32
|
export declare function textOnlyImageText(ref: ImageAttachmentRef): string;
|
|
13
33
|
/**
|
|
14
|
-
* Stable model-facing handle for one exact request image.
|
|
15
|
-
*
|
|
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.
|
|
16
41
|
* @returns attachment handle and request-image dimensions.
|
|
17
42
|
*/
|
|
18
|
-
export declare function requestImageHandleText(version: RequestImageAttachment): string;
|
|
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;
|
|
19
51
|
/**
|
|
20
52
|
* True when typed model content contains an image block, walking nested
|
|
21
53
|
* tool-result content. This is the one recursive image walk shared by every
|
|
@@ -37,8 +69,10 @@ export interface RequestImageOffloadPolicy {
|
|
|
37
69
|
byteQuantum?: number;
|
|
38
70
|
/** Whether byte accounting uses raw file bytes or inline base64 length. */
|
|
39
71
|
representation: 'raw' | 'base64';
|
|
40
|
-
/** Resolve the encoded request-version length; omission uses
|
|
72
|
+
/** Resolve the encoded request-version length; omission uses normalized attachment bytes. */
|
|
41
73
|
byteLength?: (ref: ImageAttachmentRef) => number;
|
|
74
|
+
/** Build the model-visible replacement for each omitted attachment. */
|
|
75
|
+
placeholder: (ref: ImageAttachmentRef) => string;
|
|
42
76
|
}
|
|
43
77
|
/**
|
|
44
78
|
* Project durable image history into deterministic text for an exact text-only model.
|
|
@@ -47,15 +81,15 @@ export interface RequestImageOffloadPolicy {
|
|
|
47
81
|
*/
|
|
48
82
|
export declare function projectImagesForTextModel(messages: readonly Message[]): readonly Message[];
|
|
49
83
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* @param
|
|
55
|
-
* @param
|
|
56
|
-
* @returns
|
|
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.
|
|
57
91
|
*/
|
|
58
|
-
export declare function
|
|
92
|
+
export declare function offloadedImagePrefixCount(lengths: readonly number[], policy: Pick<RequestImageOffloadPolicy, 'maxImages' | 'maxBytes' | 'countQuantum' | 'byteQuantum'>): number;
|
|
59
93
|
/**
|
|
60
94
|
* Return a deterministic transient projection whose oldest images are replaced
|
|
61
95
|
* in whole count and byte quanta after a route budget is exceeded. The target
|
package/lib/types/content.js
CHANGED
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { assertNever } from '@deepseek-ai/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
|
+
}
|
|
4
42
|
/**
|
|
5
43
|
* Stable text shown to a model that cannot accept one durable image reference.
|
|
6
|
-
* @param ref - durable
|
|
44
|
+
* @param ref - durable normalized attachment omitted from the request.
|
|
7
45
|
* @returns deterministic text-only placeholder.
|
|
8
46
|
*/
|
|
9
47
|
export function textOnlyImageText(ref) {
|
|
@@ -11,12 +49,33 @@ export function textOnlyImageText(ref) {
|
|
|
11
49
|
return `[image omitted because this model accepts text only; attachment sha256:${digest}]`;
|
|
12
50
|
}
|
|
13
51
|
/**
|
|
14
|
-
* Stable model-facing handle for one exact request image.
|
|
15
|
-
*
|
|
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.
|
|
16
59
|
* @returns attachment handle and request-image dimensions.
|
|
17
60
|
*/
|
|
18
|
-
export function requestImageHandleText(version) {
|
|
19
|
-
|
|
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)}]`;
|
|
20
79
|
}
|
|
21
80
|
/**
|
|
22
81
|
* True when typed model content contains an image block, walking nested
|
|
@@ -49,17 +108,17 @@ function collectImageLengths(blocks, lengths, policy) {
|
|
|
49
108
|
}
|
|
50
109
|
}
|
|
51
110
|
/** Replace the first `remaining.count` image occurrences without mutating durable messages. */
|
|
52
|
-
function replaceOldestImages(blocks, remaining) {
|
|
111
|
+
function replaceOldestImages(blocks, remaining, placeholder) {
|
|
53
112
|
let next;
|
|
54
113
|
for (const [index, block] of blocks.entries()) {
|
|
55
114
|
if (block.type === 'image' && remaining.count > 0) {
|
|
56
115
|
remaining.count -= 1;
|
|
57
116
|
next ??= blocks.slice(0, index);
|
|
58
|
-
next.push({ type: 'text', text:
|
|
117
|
+
next.push({ type: 'text', text: placeholder(block.attachment) });
|
|
59
118
|
continue;
|
|
60
119
|
}
|
|
61
120
|
if (block.type === 'tool-result') {
|
|
62
|
-
const content = replaceOldestImages(block.content, remaining);
|
|
121
|
+
const content = replaceOldestImages(block.content, remaining, placeholder);
|
|
63
122
|
if (content !== block.content) {
|
|
64
123
|
next ??= blocks.slice(0, index);
|
|
65
124
|
next.push({ ...block, content });
|
|
@@ -105,41 +164,20 @@ export function projectImagesForTextModel(messages) {
|
|
|
105
164
|
});
|
|
106
165
|
}
|
|
107
166
|
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param
|
|
113
|
-
* @param
|
|
114
|
-
* @returns
|
|
115
|
-
*/
|
|
116
|
-
export function offloadRequestImages(messages, maxRequestImageBytes) {
|
|
117
|
-
return offloadRequestImagesWithPolicy(messages, {
|
|
118
|
-
representation: 'base64',
|
|
119
|
-
...maxRequestImageBytes === undefined ? {} : { maxBytes: maxRequestImageBytes },
|
|
120
|
-
byteQuantum: 1,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Return a deterministic transient projection whose oldest images are replaced
|
|
125
|
-
* in whole count and byte quanta after a route budget is exceeded. The target
|
|
126
|
-
* depends only on complete durable history: at 129 one-megabyte images under
|
|
127
|
-
* a 128 MiB bound with a 64 MiB quantum, the oldest 65 images are removed so
|
|
128
|
-
* 64 MiB remain; that removed prefix stays fixed until total history exceeds
|
|
129
|
-
* 192 MiB.
|
|
130
|
-
* @param messages - complete request history, oldest first.
|
|
131
|
-
* @param policy - route representation, budgets, and removal quanta.
|
|
132
|
-
* @returns original messages below both bounds, otherwise shallow copies with deterministic placeholders.
|
|
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.
|
|
133
174
|
*/
|
|
134
|
-
export function
|
|
135
|
-
const lengths = [];
|
|
136
|
-
for (const message of messages)
|
|
137
|
-
collectImageLengths(message.content, lengths, policy);
|
|
175
|
+
export function offloadedImagePrefixCount(lengths, policy) {
|
|
138
176
|
const total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
139
177
|
const excessCount = policy.maxImages === undefined ? 0 : Math.max(0, lengths.length - policy.maxImages);
|
|
140
178
|
const excessBytes = policy.maxBytes === undefined ? 0 : Math.max(0, total - policy.maxBytes);
|
|
141
179
|
if (excessCount === 0 && excessBytes === 0)
|
|
142
|
-
return
|
|
180
|
+
return 0;
|
|
143
181
|
const countQuantum = policy.countQuantum ?? 1;
|
|
144
182
|
const byteQuantum = policy.byteQuantum ?? 1;
|
|
145
183
|
const removeCount = excessCount === 0 ? 0 : Math.ceil(excessCount / countQuantum) * countQuantum;
|
|
@@ -154,9 +192,29 @@ export function offloadRequestImagesWithPolicy(messages, policy) {
|
|
|
154
192
|
removedBytes += imageBytes;
|
|
155
193
|
count += 1;
|
|
156
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;
|
|
157
215
|
const remaining = { count };
|
|
158
216
|
return messages.map((message) => {
|
|
159
|
-
const content = replaceOldestImages(message.content, remaining);
|
|
217
|
+
const content = replaceOldestImages(message.content, remaining, policy.placeholder);
|
|
160
218
|
return content === message.content ? message : { ...message, content };
|
|
161
219
|
});
|
|
162
220
|
}
|