@deepseek-ai/dsh-llm 0.1.0-rc.7 → 0.1.0-rc.8
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 +4 -0
- package/README.zh.md +4 -0
- package/lib/index.js +94 -4
- package/lib/types/assembler.d.ts +10 -1
- package/lib/types/assembler.js +20 -1
- package/lib/types/content.d.ts +13 -0
- package/lib/types/content.js +70 -0
- package/lib/types/retry-policy.d.ts +1 -1
- package/lib/types/retry-policy.js +6 -2
- package/package.json +9 -9
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/llm/llm/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: e6b3c4924ad4e7cf115abdbfb38d22d0f524e377
|
|
6
|
+
README.zh.md: 51f034af632c19ca91c1d2f91a7c9c22714870e0
|
package/README.md
CHANGED
|
@@ -8,6 +8,10 @@ Provider-neutral LLM vocabulary and abstract service. This package defines the c
|
|
|
8
8
|
|
|
9
9
|
An adapter registry plus a single streaming call API, interceptable via a waterfall event.
|
|
10
10
|
|
|
11
|
+
### Retry policy
|
|
12
|
+
|
|
13
|
+
Each provider adapter supplies its resolved route policy. Omitting provider configuration uses bounded normal mode with five retries after the first request. Layered configuration may retain `maxRetries` or `retryableCodes` after changing `mode` to `always`; resolution ignores those inactive normal-mode fields and captures a pure always policy. This service stores the effective policy but does not execute retries.
|
|
14
|
+
|
|
11
15
|
### Public API
|
|
12
16
|
|
|
13
17
|
- `ctx.llm.registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle` Register one adapter instance for the given provider routes. Registration is all-or-nothing, and is disposed with the calling fiber. The returned disposer also carries `replace(providers)`: the candidate route set is validated in full before anything moves, so a conflict with another adapter leaves the current routes registered and serving, and the swap itself is one synchronous section with no observable gap. `replace([])` is legal — a registration holding zero routes — unlike an empty initial registration.
|
package/README.zh.md
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
|
|
9
9
|
一个适配器注册表加单一流式调用接口,可通过 waterfall(瀑布式事件)拦截。
|
|
10
10
|
|
|
11
|
+
### 重试策略
|
|
12
|
+
|
|
13
|
+
每个提供方适配器都会提供解析后的路由策略。省略提供方配置时使用有界 normal mode,在首次请求后最多重试五次。分层配置把 `mode` 改为 `always` 后可能残留 `maxRetries` 或 `retryableCodes`;解析过程会忽略这些不再生效的 normal-mode 字段,并捕获纯 always 策略。本服务存储有效策略,但不执行重试。
|
|
14
|
+
|
|
11
15
|
### 公开 API
|
|
12
16
|
|
|
13
17
|
- `ctx.llm.registerAdapter(providers: string[], adapter: LlmAdapter): AdapterRegistrationHandle` 为给定提供方路由注册一个适配器实例。注册要么全部成功,要么全部不生效,并且会随调用 fiber 一起 dispose(资源释放)。返回的句柄还提供 `replace(providers)`:候选路由集合会在注册状态发生任何变化前完成整体验证,因此与另一适配器发生冲突时,当前路由仍保持注册并继续提供服务。替换会在一次同步操作中完成,不会出现可观察的空档。`replace([])` 合法,表示保留注册但不持有任何路由;初始注册则不得为空。
|
package/lib/index.js
CHANGED
|
@@ -353,7 +353,7 @@ function isHarnessError(value) {
|
|
|
353
353
|
*
|
|
354
354
|
* @module @deepseek-ai/dsh-llm/retry-policy
|
|
355
355
|
*/
|
|
356
|
-
const DEFAULT_MAX_RETRIES =
|
|
356
|
+
const DEFAULT_MAX_RETRIES = 5;
|
|
357
357
|
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
358
358
|
const DEFAULT_MAX_DELAY_MS = 1e4;
|
|
359
359
|
const DEFAULT_JITTER_RATIO = .1;
|
|
@@ -387,7 +387,12 @@ const NORMAL_POLICY_KEYS = new Set([
|
|
|
387
387
|
"retryableCodes",
|
|
388
388
|
"backoff"
|
|
389
389
|
]);
|
|
390
|
-
const ALWAYS_POLICY_KEYS = new Set([
|
|
390
|
+
const ALWAYS_POLICY_KEYS = new Set([
|
|
391
|
+
"mode",
|
|
392
|
+
"maxRetries",
|
|
393
|
+
"retryableCodes",
|
|
394
|
+
"backoff"
|
|
395
|
+
]);
|
|
391
396
|
const BACKOFF_KEYS = new Set([
|
|
392
397
|
"initialDelayMs",
|
|
393
398
|
"maxDelayMs",
|
|
@@ -639,6 +644,8 @@ function assertNever(value, context) {
|
|
|
639
644
|
//#endregion
|
|
640
645
|
//#region lib/types/content.js
|
|
641
646
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
647
|
+
/** Model-facing stand-in for an image removed to fit a provider request bound. */
|
|
648
|
+
const OFFLOADED_IMAGE_TEXT = "[image omitted to keep the request within its image limit; older images are omitted first. If this image is still needed, read its file again when a path is available; otherwise ask the user to attach it again.]";
|
|
642
649
|
/**
|
|
643
650
|
* True when typed model content contains an image block, walking nested
|
|
644
651
|
* tool-result content. This is the one recursive image walk shared by every
|
|
@@ -650,6 +657,73 @@ function assertNever(value, context) {
|
|
|
650
657
|
function contentHasImage(content) {
|
|
651
658
|
return content.some((block) => block.type === "image" || block.type === "tool-result" && contentHasImage(block.content));
|
|
652
659
|
}
|
|
660
|
+
/** Base64 length of raw image bytes, including padding. */
|
|
661
|
+
function base64Length(bytes) {
|
|
662
|
+
return Math.ceil(bytes / 3) * 4;
|
|
663
|
+
}
|
|
664
|
+
/** Collect base64 payload lengths in request and nested-block order. */
|
|
665
|
+
function collectImageLengths(blocks, lengths) {
|
|
666
|
+
for (const block of blocks) if (block.type === "image") lengths.push(base64Length(block.attachment.bytes));
|
|
667
|
+
else if (block.type === "tool-result") collectImageLengths(block.content, lengths);
|
|
668
|
+
}
|
|
669
|
+
/** Replace the first `remaining.count` image occurrences without mutating durable messages. */
|
|
670
|
+
function replaceOldestImages(blocks, remaining) {
|
|
671
|
+
let next;
|
|
672
|
+
for (const [index, block] of blocks.entries()) {
|
|
673
|
+
if (block.type === "image" && remaining.count > 0) {
|
|
674
|
+
remaining.count -= 1;
|
|
675
|
+
next ??= blocks.slice(0, index);
|
|
676
|
+
next.push({
|
|
677
|
+
type: "text",
|
|
678
|
+
text: OFFLOADED_IMAGE_TEXT
|
|
679
|
+
});
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
682
|
+
if (block.type === "tool-result") {
|
|
683
|
+
const content = replaceOldestImages(block.content, remaining);
|
|
684
|
+
if (content !== block.content) {
|
|
685
|
+
next ??= blocks.slice(0, index);
|
|
686
|
+
next.push({
|
|
687
|
+
...block,
|
|
688
|
+
content
|
|
689
|
+
});
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
next?.push(block);
|
|
694
|
+
}
|
|
695
|
+
return next ?? blocks;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Return transient request messages whose oldest images are replaced until
|
|
699
|
+
* their accumulated base64 payload fits the configured bound. The selection
|
|
700
|
+
* is deterministic from durable message order and attachment metadata; a
|
|
701
|
+
* provider can serialize the returned messages without reading omitted bytes.
|
|
702
|
+
* @param messages - complete request history, oldest first.
|
|
703
|
+
* @param maxRequestImageBytes - positive bound on total base64 image payload; undefined preserves every image.
|
|
704
|
+
* @returns the original messages when they already fit, otherwise shallow message copies with replaced content trees.
|
|
705
|
+
*/
|
|
706
|
+
function offloadRequestImages(messages, maxRequestImageBytes) {
|
|
707
|
+
if (maxRequestImageBytes === void 0) return messages;
|
|
708
|
+
const lengths = [];
|
|
709
|
+
for (const message of messages) collectImageLengths(message.content, lengths);
|
|
710
|
+
let total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
711
|
+
let count = 0;
|
|
712
|
+
for (const bytes of lengths) {
|
|
713
|
+
if (total <= maxRequestImageBytes) break;
|
|
714
|
+
total -= bytes;
|
|
715
|
+
count += 1;
|
|
716
|
+
}
|
|
717
|
+
if (count === 0) return messages;
|
|
718
|
+
const remaining = { count };
|
|
719
|
+
return messages.map((message) => {
|
|
720
|
+
const content = replaceOldestImages(message.content, remaining);
|
|
721
|
+
return content === message.content ? message : {
|
|
722
|
+
...message,
|
|
723
|
+
content
|
|
724
|
+
};
|
|
725
|
+
});
|
|
726
|
+
}
|
|
653
727
|
//#endregion
|
|
654
728
|
//#region lib/types/assembler.js
|
|
655
729
|
/**
|
|
@@ -664,7 +738,8 @@ function contentHasImage(content) {
|
|
|
664
738
|
* {@link ContentBlock}s and a final assistant {@link Message}.
|
|
665
739
|
*
|
|
666
740
|
* The agent loop feeds it while logging raw chunks for replay fidelity, then
|
|
667
|
-
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends
|
|
741
|
+
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,
|
|
742
|
+
* or `interruptedBlocks()` when cancellation cut the stream short.
|
|
668
743
|
*
|
|
669
744
|
* Tolerant of delta-only protocols (no block-start/end); deltas arriving for
|
|
670
745
|
* an index already closed by `block-end` are ignored (malformed stream) so a
|
|
@@ -797,6 +872,21 @@ var BlockAssembler = class {
|
|
|
797
872
|
blocks() {
|
|
798
873
|
return this.assembled().blocks;
|
|
799
874
|
}
|
|
875
|
+
/**
|
|
876
|
+
* Assemble the prefix an interrupted stream can safely finalize: closed and
|
|
877
|
+
* open text/reasoning blocks with non-whitespace content, in stream order.
|
|
878
|
+
* Tool calls are omitted because interruption precedes dispatch; retaining
|
|
879
|
+
* one would require a fabricated result. Open unknown blocks are also omitted.
|
|
880
|
+
* @returns the kept blocks; empty when nothing streamed before the interruption.
|
|
881
|
+
*/
|
|
882
|
+
interruptedBlocks() {
|
|
883
|
+
return this.order.map((index) => {
|
|
884
|
+
const partial = this.mustGet(index);
|
|
885
|
+
const type = partial.block?.type ?? partial.blockType;
|
|
886
|
+
if (type !== "text" && type !== "reasoning") return void 0;
|
|
887
|
+
return this.assemble(partial, index);
|
|
888
|
+
}).filter((block) => (block?.type === "text" || block?.type === "reasoning") && block.text.trim() !== "");
|
|
889
|
+
}
|
|
800
890
|
/** Usage from the `usage` chunk; undefined until one arrives. */
|
|
801
891
|
get usage() {
|
|
802
892
|
return this._usage;
|
|
@@ -1433,4 +1523,4 @@ function adapterFailureChunk(error, signal) {
|
|
|
1433
1523
|
};
|
|
1434
1524
|
}
|
|
1435
1525
|
//#endregion
|
|
1436
|
-
export { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmError, LlmRuntime, LlmRuntime as default, MessageId, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, assertNever, assertUsableApiKey, attributionHeaders, boundContextSummary, callConfigEquals, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, deepFreeze, errorChain, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, markAgentLoopRequest, normalizeApiKey, resolveRetryPolicy, userAgent };
|
|
1526
|
+
export { APP_IDENTITY, BlockAssembler, CONTEXT_SUMMARY_MAX_CHARS, CONTEXT_WINDOW_EXCEEDED_CODE, CallId, EMPTY_RESPONSE_CODE, HarnessError, INVALID_CREDENTIAL_CODE, LlmAdapter, LlmError, LlmRuntime, LlmRuntime as default, MessageId, OFFLOADED_IMAGE_TEXT, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, assertNever, assertUsableApiKey, attributionHeaders, boundContextSummary, callConfigEquals, contentHasImage, createAssistantMessage, createMessage, createToolResultMessage, createUserMessage, deepFreeze, errorChain, freezeMessage, isAgentLoopRequest, isContextWindowExceededError, isHarnessError, isQuotaExceededError, isTokenDelta, markAgentLoopRequest, normalizeApiKey, offloadRequestImages, resolveRetryPolicy, userAgent };
|
package/lib/types/assembler.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ import type { ContentBlock, FinishReason, ReplayEnvelope, StreamChunk, TokenUsag
|
|
|
12
12
|
* {@link ContentBlock}s and a final assistant {@link Message}.
|
|
13
13
|
*
|
|
14
14
|
* The agent loop feeds it while logging raw chunks for replay fidelity, then
|
|
15
|
-
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends
|
|
15
|
+
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,
|
|
16
|
+
* or `interruptedBlocks()` when cancellation cut the stream short.
|
|
16
17
|
*
|
|
17
18
|
* Tolerant of delta-only protocols (no block-start/end); deltas arriving for
|
|
18
19
|
* an index already closed by `block-end` are ignored (malformed stream) so a
|
|
@@ -46,6 +47,14 @@ export declare class BlockAssembler {
|
|
|
46
47
|
* its accumulated deltas (an unknown block type never closed by `block-end` throws).
|
|
47
48
|
*/
|
|
48
49
|
blocks(): ContentBlock[];
|
|
50
|
+
/**
|
|
51
|
+
* Assemble the prefix an interrupted stream can safely finalize: closed and
|
|
52
|
+
* open text/reasoning blocks with non-whitespace content, in stream order.
|
|
53
|
+
* Tool calls are omitted because interruption precedes dispatch; retaining
|
|
54
|
+
* one would require a fabricated result. Open unknown blocks are also omitted.
|
|
55
|
+
* @returns the kept blocks; empty when nothing streamed before the interruption.
|
|
56
|
+
*/
|
|
57
|
+
interruptedBlocks(): ContentBlock[];
|
|
49
58
|
/** Usage from the `usage` chunk; undefined until one arrives. */
|
|
50
59
|
get usage(): TokenUsage | undefined;
|
|
51
60
|
/** Finish reason from the `finish` chunk; `{kind: 'stop'}` when the stream ended without one. */
|
package/lib/types/assembler.js
CHANGED
|
@@ -13,7 +13,8 @@ import { createMessage } from "./message.js";
|
|
|
13
13
|
* {@link ContentBlock}s and a final assistant {@link Message}.
|
|
14
14
|
*
|
|
15
15
|
* The agent loop feeds it while logging raw chunks for replay fidelity, then
|
|
16
|
-
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends
|
|
16
|
+
* reads `blocks()` / `message()` / `usage` / `finish` once the stream ends,
|
|
17
|
+
* or `interruptedBlocks()` when cancellation cut the stream short.
|
|
17
18
|
*
|
|
18
19
|
* Tolerant of delta-only protocols (no block-start/end); deltas arriving for
|
|
19
20
|
* an index already closed by `block-end` are ignored (malformed stream) so a
|
|
@@ -144,6 +145,24 @@ export class BlockAssembler {
|
|
|
144
145
|
blocks() {
|
|
145
146
|
return this.assembled().blocks;
|
|
146
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Assemble the prefix an interrupted stream can safely finalize: closed and
|
|
150
|
+
* open text/reasoning blocks with non-whitespace content, in stream order.
|
|
151
|
+
* Tool calls are omitted because interruption precedes dispatch; retaining
|
|
152
|
+
* one would require a fabricated result. Open unknown blocks are also omitted.
|
|
153
|
+
* @returns the kept blocks; empty when nothing streamed before the interruption.
|
|
154
|
+
*/
|
|
155
|
+
interruptedBlocks() {
|
|
156
|
+
return this.order
|
|
157
|
+
.map((index) => {
|
|
158
|
+
const partial = this.mustGet(index);
|
|
159
|
+
const type = partial.block?.type ?? partial.blockType;
|
|
160
|
+
if (type !== 'text' && type !== 'reasoning')
|
|
161
|
+
return undefined;
|
|
162
|
+
return this.assemble(partial, index);
|
|
163
|
+
})
|
|
164
|
+
.filter((block) => (block?.type === 'text' || block?.type === 'reasoning') && block.text.trim() !== '');
|
|
165
|
+
}
|
|
147
166
|
/** Usage from the `usage` chunk; undefined until one arrives. */
|
|
148
167
|
get usage() {
|
|
149
168
|
return this._usage;
|
package/lib/types/content.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
2
2
|
import type { ContentBlock } from './types.ts';
|
|
3
|
+
import type { Message } from './message.ts';
|
|
4
|
+
/** Model-facing stand-in for an image removed to fit a provider request bound. */
|
|
5
|
+
export declare const OFFLOADED_IMAGE_TEXT = "[image omitted to keep the request within its image limit; older images are omitted first. If this image is still needed, read its file again when a path is available; otherwise ask the user to attach it again.]";
|
|
3
6
|
/**
|
|
4
7
|
* True when typed model content contains an image block, walking nested
|
|
5
8
|
* tool-result content. This is the one recursive image walk shared by every
|
|
@@ -9,4 +12,14 @@ import type { ContentBlock } from './types.ts';
|
|
|
9
12
|
* @returns whether any nested block is an image.
|
|
10
13
|
*/
|
|
11
14
|
export declare function contentHasImage(content: readonly ContentBlock[]): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Return transient request messages whose oldest images are replaced until
|
|
17
|
+
* their accumulated base64 payload fits the configured bound. The selection
|
|
18
|
+
* is deterministic from durable message order and attachment metadata; a
|
|
19
|
+
* provider can serialize the returned messages without reading omitted bytes.
|
|
20
|
+
* @param messages - complete request history, oldest first.
|
|
21
|
+
* @param maxRequestImageBytes - positive bound on total base64 image payload; undefined preserves every image.
|
|
22
|
+
* @returns the original messages when they already fit, otherwise shallow message copies with replaced content trees.
|
|
23
|
+
*/
|
|
24
|
+
export declare function offloadRequestImages(messages: readonly Message[], maxRequestImageBytes: number | undefined): readonly Message[];
|
|
12
25
|
//# sourceMappingURL=content.d.ts.map
|
package/lib/types/content.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Content-block structure helpers. @module @deepseek-ai/dsh-llm/content */
|
|
2
|
+
/** Model-facing stand-in for an image removed to fit a provider request bound. */
|
|
3
|
+
export const OFFLOADED_IMAGE_TEXT = '[image omitted to keep the request within its image limit; older images are omitted first. If this image is still needed, read its file again when a path is available; otherwise ask the user to attach it again.]';
|
|
2
4
|
/**
|
|
3
5
|
* True when typed model content contains an image block, walking nested
|
|
4
6
|
* tool-result content. This is the one recursive image walk shared by every
|
|
@@ -11,4 +13,72 @@ export function contentHasImage(content) {
|
|
|
11
13
|
return content.some(block => block.type === 'image'
|
|
12
14
|
|| (block.type === 'tool-result' && contentHasImage(block.content)));
|
|
13
15
|
}
|
|
16
|
+
/** Base64 length of raw image bytes, including padding. */
|
|
17
|
+
function base64Length(bytes) {
|
|
18
|
+
return Math.ceil(bytes / 3) * 4;
|
|
19
|
+
}
|
|
20
|
+
/** Collect base64 payload lengths in request and nested-block order. */
|
|
21
|
+
function collectImageLengths(blocks, lengths) {
|
|
22
|
+
for (const block of blocks) {
|
|
23
|
+
if (block.type === 'image') {
|
|
24
|
+
lengths.push(base64Length(block.attachment.bytes));
|
|
25
|
+
}
|
|
26
|
+
else if (block.type === 'tool-result') {
|
|
27
|
+
collectImageLengths(block.content, lengths);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Replace the first `remaining.count` image occurrences without mutating durable messages. */
|
|
32
|
+
function replaceOldestImages(blocks, remaining) {
|
|
33
|
+
let next;
|
|
34
|
+
for (const [index, block] of blocks.entries()) {
|
|
35
|
+
if (block.type === 'image' && remaining.count > 0) {
|
|
36
|
+
remaining.count -= 1;
|
|
37
|
+
next ??= blocks.slice(0, index);
|
|
38
|
+
next.push({ type: 'text', text: OFFLOADED_IMAGE_TEXT });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (block.type === 'tool-result') {
|
|
42
|
+
const content = replaceOldestImages(block.content, remaining);
|
|
43
|
+
if (content !== block.content) {
|
|
44
|
+
next ??= blocks.slice(0, index);
|
|
45
|
+
next.push({ ...block, content });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
next?.push(block);
|
|
50
|
+
}
|
|
51
|
+
return next ?? blocks;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Return transient request messages whose oldest images are replaced until
|
|
55
|
+
* their accumulated base64 payload fits the configured bound. The selection
|
|
56
|
+
* is deterministic from durable message order and attachment metadata; a
|
|
57
|
+
* provider can serialize the returned messages without reading omitted bytes.
|
|
58
|
+
* @param messages - complete request history, oldest first.
|
|
59
|
+
* @param maxRequestImageBytes - positive bound on total base64 image payload; undefined preserves every image.
|
|
60
|
+
* @returns the original messages when they already fit, otherwise shallow message copies with replaced content trees.
|
|
61
|
+
*/
|
|
62
|
+
export function offloadRequestImages(messages, maxRequestImageBytes) {
|
|
63
|
+
if (maxRequestImageBytes === undefined)
|
|
64
|
+
return messages;
|
|
65
|
+
const lengths = [];
|
|
66
|
+
for (const message of messages)
|
|
67
|
+
collectImageLengths(message.content, lengths);
|
|
68
|
+
let total = lengths.reduce((sum, bytes) => sum + bytes, 0);
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (const bytes of lengths) {
|
|
71
|
+
if (total <= maxRequestImageBytes)
|
|
72
|
+
break;
|
|
73
|
+
total -= bytes;
|
|
74
|
+
count += 1;
|
|
75
|
+
}
|
|
76
|
+
if (count === 0)
|
|
77
|
+
return messages;
|
|
78
|
+
const remaining = { count };
|
|
79
|
+
return messages.map((message) => {
|
|
80
|
+
const content = replaceOldestImages(message.content, remaining);
|
|
81
|
+
return content === message.content ? message : { ...message, content };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
14
84
|
//# sourceMappingURL=content.js.map
|
|
@@ -20,7 +20,7 @@ export interface BackoffConfig {
|
|
|
20
20
|
export interface NormalRetryPolicyConfig {
|
|
21
21
|
/** Retry only configured transient failure codes. */
|
|
22
22
|
mode: 'normal';
|
|
23
|
-
/** Maximum eligible retries after the first request (default
|
|
23
|
+
/** Maximum eligible retries after the first request (default 5). */
|
|
24
24
|
maxRetries?: number;
|
|
25
25
|
/** Stable failure codes eligible for this policy. */
|
|
26
26
|
retryableCodes?: string[];
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import z from '@deepseek-ai/schemastery';
|
|
10
10
|
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout';
|
|
11
11
|
import { EMPTY_RESPONSE_CODE } from "./error.js";
|
|
12
|
-
const DEFAULT_MAX_RETRIES =
|
|
12
|
+
const DEFAULT_MAX_RETRIES = 5;
|
|
13
13
|
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
14
14
|
const DEFAULT_MAX_DELAY_MS = 10_000;
|
|
15
15
|
const DEFAULT_JITTER_RATIO = 0.1;
|
|
@@ -43,7 +43,11 @@ export const RetryPolicySchema = z.union([
|
|
|
43
43
|
const NORMAL_POLICY_KEYS = new Set([
|
|
44
44
|
'mode', 'maxRetries', 'retryableCodes', 'backoff',
|
|
45
45
|
]);
|
|
46
|
-
|
|
46
|
+
// Layered configuration can retain normal-only fields after switching modes;
|
|
47
|
+
// always mode ignores those inactive values while still rejecting unknown keys.
|
|
48
|
+
const ALWAYS_POLICY_KEYS = new Set([
|
|
49
|
+
'mode', 'maxRetries', 'retryableCodes', 'backoff',
|
|
50
|
+
]);
|
|
47
51
|
const BACKOFF_KEYS = new Set(['initialDelayMs', 'maxDelayMs', 'jitterRatio']);
|
|
48
52
|
function validateKeys(value, allowed, path) {
|
|
49
53
|
for (const key of Object.keys(value)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-llm",
|
|
3
3
|
"description": "Provider-neutral LLM service interface for the DeepSeek Harness",
|
|
4
|
-
"version": "0.1.0-rc.
|
|
4
|
+
"version": "0.1.0-rc.8",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -45,20 +45,20 @@
|
|
|
45
45
|
],
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.
|
|
49
|
-
"@deepseek-ai/dsh-brand": "^0.1.0-rc.
|
|
50
|
-
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.
|
|
51
|
-
"@deepseek-ai/dsh-timeout": "^0.1.0-rc.
|
|
48
|
+
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.8",
|
|
49
|
+
"@deepseek-ai/dsh-brand": "^0.1.0-rc.8",
|
|
50
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
|
|
51
|
+
"@deepseek-ai/dsh-timeout": "^0.1.0-rc.8",
|
|
52
52
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
55
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.
|
|
59
|
-
"@deepseek-ai/dsh-brand": "^0.1.0-rc.
|
|
60
|
-
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.
|
|
61
|
-
"@deepseek-ai/dsh-timeout": "^0.1.0-rc.
|
|
58
|
+
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.8",
|
|
59
|
+
"@deepseek-ai/dsh-brand": "^0.1.0-rc.8",
|
|
60
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.8",
|
|
61
|
+
"@deepseek-ai/dsh-timeout": "^0.1.0-rc.8",
|
|
62
62
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
63
63
|
}
|
|
64
64
|
}
|