@toddzheng024/dscode-bundle 0.1.0

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.
Files changed (98) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/THIRD_PARTY_NOTICES.md +11 -0
  4. package/bin/apply_patch +4 -0
  5. package/bootstrap.mjs +21 -0
  6. package/cordis.patch.yml +756 -0
  7. package/package.json +327 -0
  8. package/plugins/auto-review/audit.mjs +20 -0
  9. package/plugins/auto-review/index.mjs +181 -0
  10. package/plugins/auto-review/policy.mjs +59 -0
  11. package/plugins/dscode/index.mjs +35 -0
  12. package/plugins/session-metrics/index.mjs +44 -0
  13. package/plugins/session-metrics/pricing.mjs +18 -0
  14. package/plugins/session-metrics/store.mjs +24 -0
  15. package/plugins/session-metrics/view.mjs +58 -0
  16. package/plugins/tui-tools/hooks.mjs +23 -0
  17. package/plugins/tui-tools/index.mjs +164 -0
  18. package/plugins/ultra/policy.mjs +11 -0
  19. package/presets/dscode/agent.cordis.yml +288 -0
  20. package/presets/dscode/preset.yml +3 -0
  21. package/vendor/bash/LICENSE +21 -0
  22. package/vendor/bash/index.js +449 -0
  23. package/vendor/bash/types/background.d.ts +19 -0
  24. package/vendor/bash/types/index.d.ts +22 -0
  25. package/vendor/bash/types/render.d.ts +38 -0
  26. package/vendor/deepseek/LICENSE +21 -0
  27. package/vendor/deepseek/index.js +2102 -0
  28. package/vendor/deepseek/types/adapter.d.ts +163 -0
  29. package/vendor/deepseek/types/file-id.d.ts +19 -0
  30. package/vendor/deepseek/types/file-store.d.ts +83 -0
  31. package/vendor/deepseek/types/files-api.d.ts +103 -0
  32. package/vendor/deepseek/types/image-tokens.d.ts +19 -0
  33. package/vendor/deepseek/types/index.d.ts +106 -0
  34. package/vendor/deepseek/types/request-pricing.d.ts +48 -0
  35. package/vendor/deepseek/types/serialize.d.ts +85 -0
  36. package/vendor/deepseek/types/sse.d.ts +24 -0
  37. package/vendor/deepseek/types/translate.d.ts +37 -0
  38. package/vendor/deepseek/types/types.d.ts +178 -0
  39. package/vendor/deepseek/types/upload-index.d.ts +68 -0
  40. package/vendor/persistent/LICENSE +21 -0
  41. package/vendor/persistent/index.js +386 -0
  42. package/vendor/persistent/types/index.d.ts +24 -0
  43. package/vendor/tui/LICENSE +21 -0
  44. package/vendor/tui/devtools-CdTl3MNy.mjs +3643 -0
  45. package/vendor/tui/index.mjs +39105 -0
  46. package/vendor/tui/invariant.mjs +21 -0
  47. package/vendor/tui/rolldown-runtime-CMFfr-1z.mjs +26 -0
  48. package/vendor/tui/startup.mjs +109 -0
  49. package/vendor/tui/theme-DCT8Y2xf.mjs +628 -0
  50. package/vendor/tui/types/app.d.ts +304 -0
  51. package/vendor/tui/types/approval.d.ts +59 -0
  52. package/vendor/tui/types/attachments.d.ts +52 -0
  53. package/vendor/tui/types/authorization-panel.d.ts +22 -0
  54. package/vendor/tui/types/authorization.d.ts +36 -0
  55. package/vendor/tui/types/commands.d.ts +52 -0
  56. package/vendor/tui/types/editor-keys.d.ts +105 -0
  57. package/vendor/tui/types/editor.d.ts +6 -0
  58. package/vendor/tui/types/fork.d.ts +8 -0
  59. package/vendor/tui/types/git-workflow.d.ts +32 -0
  60. package/vendor/tui/types/history.d.ts +97 -0
  61. package/vendor/tui/types/index.d.ts +124 -0
  62. package/vendor/tui/types/input-split.d.ts +54 -0
  63. package/vendor/tui/types/internals.d.ts +26 -0
  64. package/vendor/tui/types/invariant.d.ts +15 -0
  65. package/vendor/tui/types/kernel-panels.d.ts +167 -0
  66. package/vendor/tui/types/keyboard.d.ts +80 -0
  67. package/vendor/tui/types/mentions.d.ts +81 -0
  68. package/vendor/tui/types/model-capabilities.d.ts +82 -0
  69. package/vendor/tui/types/models.d.ts +119 -0
  70. package/vendor/tui/types/permissions.d.ts +27 -0
  71. package/vendor/tui/types/plugin-inventory.d.ts +11 -0
  72. package/vendor/tui/types/presets.d.ts +22 -0
  73. package/vendor/tui/types/provider-settings.d.ts +239 -0
  74. package/vendor/tui/types/questions.d.ts +54 -0
  75. package/vendor/tui/types/render/animations.d.ts +265 -0
  76. package/vendor/tui/types/render/editor.d.ts +162 -0
  77. package/vendor/tui/types/render/export.d.ts +9 -0
  78. package/vendor/tui/types/render/fuzzy.d.ts +21 -0
  79. package/vendor/tui/types/render/inspector.d.ts +36 -0
  80. package/vendor/tui/types/render/lines.d.ts +66 -0
  81. package/vendor/tui/types/render/markdown.d.ts +29 -0
  82. package/vendor/tui/types/render/projection.d.ts +461 -0
  83. package/vendor/tui/types/render/status.d.ts +196 -0
  84. package/vendor/tui/types/render/text.d.ts +58 -0
  85. package/vendor/tui/types/render/tool-detail.d.ts +94 -0
  86. package/vendor/tui/types/render/tool-preview.d.ts +28 -0
  87. package/vendor/tui/types/render/width.d.ts +29 -0
  88. package/vendor/tui/types/session-directory.d.ts +173 -0
  89. package/vendor/tui/types/session-switch.d.ts +17 -0
  90. package/vendor/tui/types/settings-file.d.ts +41 -0
  91. package/vendor/tui/types/skills.d.ts +47 -0
  92. package/vendor/tui/types/startup.d.ts +65 -0
  93. package/vendor/tui/types/store.d.ts +58 -0
  94. package/vendor/tui/types/subagents.d.ts +70 -0
  95. package/vendor/tui/types/theme-panel.d.ts +24 -0
  96. package/vendor/tui/types/theme.d.ts +215 -0
  97. package/vendor/tui/types/version.d.ts +18 -0
  98. package/vendor/tui/types/whale-glyph.d.ts +6 -0
@@ -0,0 +1,2102 @@
1
+ // dscode-ultra-v1
2
+ const ULTRA_POLICY = "DSCODE ULTRA — max reasoning with deliberate collaboration.\nActively look for useful independent tasks: investigation, implementation in disjoint files, and independent review. Delegate only when it improves the outcome; simple tasks need no child agents. Give each child a bounded objective, relevant context, file ownership and acceptance criteria. Keep useful work for yourself while children run. Never have multiple agents edit the same files concurrently. Shared files are not isolated worktrees.\nIn ultra use subagent/subagent_fork and send_message for delegation, not workflow or ralph. Use at most three child agents concurrently across this root session. Do not recursively proliferate agents or duplicate investigations. The parent owns integration, verifies child claims, resolves conflicts and runs appropriate checks. Seek independent review of substantial changes when useful. Parent/child messages are available; sibling direct messaging is not. Preserve the user's permission policy: ultra grants no extra authority. Track progress and costs, reuse findings, and stop delegating when coordination costs outweigh value.";
3
+ function ultraRequest(options, messages) {
4
+ if (options.reasoningEffort !== 'ultra' || options.purpose || !options.tools?.some(t => t.name === 'subagent' || t.name === 'subagent_fork')) return messages;
5
+ const copy = messages.map(m => ({ ...m }));
6
+ const system = copy.findLast(m => m.role === 'system' && typeof m.content === 'string');
7
+ if (system) system.content += '\n\n' + ULTRA_POLICY;
8
+ else copy.unshift({ role: 'system', content: ULTRA_POLICY });
9
+ return copy;
10
+ }
11
+ import z from "@deepseek-ai/schemastery";
12
+ import { CONTEXT_WINDOW_EXCEEDED_CODE, EMPTY_RESPONSE_CODE, LlmAdapter, LlmError, ProviderRequestId, QUOTA_EXCEEDED_CODE, ReasoningEffortId, RetryPolicySchema, assertUsableApiKey, attributionHeaders, contentHasImage, isContextWindowExceededError, isQuotaExceededError, offloadRequestImagesWithPolicy, offloadedImagePrefixCount, offloadedImageText, requestImageHandleText, resolveImageAttachmentAccess, resolveRetryPolicy, textOnlyImageText } from "@deepseek-ai/dsh-llm";
13
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
14
+ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
15
+ import { MAX_TIMER_DELAY_MS, deadline, idleWatchdog, timeoutOf } from "@deepseek-ai/dsh-timeout";
16
+ import { deepEqualJson } from "@deepseek-ai/dsh-util-values";
17
+ import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
18
+ import { ImageVariantId, requestImageDimensions } from "@deepseek-ai/dsh-attachment";
19
+ import { createHash } from "node:crypto";
20
+ import { mkdir, readFile } from "node:fs/promises";
21
+ import { dirname, join } from "node:path";
22
+ import { withFileLock, writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
23
+ import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
24
+ import { EventSourceParserStream } from "eventsource-parser/stream";
25
+ import { brandString } from "@deepseek-ai/dsh-brand";
26
+ //#region lib/types/serialize.js
27
+ /**
28
+ * Serialize harness messages into DeepSeek chat completions. Text-only
29
+ * requests retain string user content; the image path resolves durable
30
+ * attachments into ordered file-id or inline parts. Tool-result images follow their
31
+ * string-only tool messages in a separate user message.
32
+ * @module dsh-llm-deepseek/serialize
33
+ */
34
+ const TOOL_RESULT_IMAGE_TEXT = "Attached image(s) from tool result:";
35
+ /** Validate the adapter-owned effort before resolving its DeepSeek wire fields. */
36
+ function reasoningEffort(effort) {
37
+ if (effort === "ultra") return "max";
38
+ if (effort === "off" || effort === "low" || effort === "high" || effort === "max") return effort;
39
+ throw new LlmError(`DeepSeek does not support reasoning effort "${effort}"`, "UNSUPPORTED_REASONING_EFFORT");
40
+ }
41
+ /** Resolve one legal thinking/effort pair without exposing `off` as a wire effort. */
42
+ function resolveThinking(options, defaults) {
43
+ if (options.purpose === "session-title") return { thinking: "disabled" };
44
+ const effort = options.reasoningEffort === void 0 ? defaults.reasoningEffort : reasoningEffort(options.reasoningEffort);
45
+ if (defaults.thinking === "disabled" && effort !== void 0 && effort !== "off") throw new LlmError(`DeepSeek deployment does not support reasoning effort "${effort}"`, "UNSUPPORTED_REASONING_EFFORT");
46
+ if (effort === "off") return { thinking: "disabled" };
47
+ if (effort === "low" || effort === "high" || effort === "max") return {
48
+ thinking: "enabled",
49
+ reasoningEffort: effort
50
+ };
51
+ return defaults.thinking === void 0 ? {} : { thinking: defaults.thinking };
52
+ }
53
+ /** Join the text blocks of a message (used for user/tool-result content). */
54
+ function flattenText(blocks) {
55
+ return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
56
+ }
57
+ /** Reject core image content before any text-flattening path can silently erase it. */
58
+ function assertTextOnly(blocks) {
59
+ if (contentHasImage(blocks)) throw new LlmError("The DeepSeek chat-completions adapter does not support image content.", "UNSUPPORTED_CONTENT");
60
+ }
61
+ /** Reject roles whose DeepSeek history format cannot carry image input. */
62
+ function assertSupportedImageRoles(messages) {
63
+ for (const message of messages) if (message.role !== "user" && contentHasImage(message.content)) throw new LlmError(`The DeepSeek chat-completions adapter cannot represent image content in a ${message.role} message.`, "UNSUPPORTED_CONTENT");
64
+ }
65
+ /** Describe the exact request preview and its model-callable coordinate system. */
66
+ function imageHandle(ref, version, resolveAccess, precededByContent) {
67
+ return {
68
+ type: "text",
69
+ text: `${precededByContent ? "\n" : ""}${requestImageHandleText(ref, version, resolveAccess?.(ref))}`
70
+ };
71
+ }
72
+ /** Resolve one durable image into its descriptor and transient DeepSeek image part. */
73
+ async function imageParts(block, images, location, precededByContent) {
74
+ const version = images.requestImages.get(block.attachment.attachmentId);
75
+ if (version === void 0) throw new LlmError(`DeepSeek request image ${block.attachment.attachmentId} was not prepared.`, "INVALID_REQUEST");
76
+ const image = images.representation.kind === "file" ? {
77
+ type: "file",
78
+ file_id: await images.representation.resolveFileId(version, block, location)
79
+ } : {
80
+ type: "image_url",
81
+ image_url: { url: `data:${version.mediaType};base64,${Buffer.from(version.data).toString("base64")}` }
82
+ };
83
+ return [imageHandle(block.attachment, version, images.resolveImageAccess, precededByContent), image];
84
+ }
85
+ /** Convert user or nested tool-result blocks into ordered wire parts. */
86
+ async function contentParts(blocks, images, message, nextImage) {
87
+ const parts = [];
88
+ for (const block of blocks) switch (block.type) {
89
+ case "text":
90
+ if (block.text.length > 0) parts.push({
91
+ type: "text",
92
+ text: block.text
93
+ });
94
+ break;
95
+ case "image":
96
+ nextImage.value += 1;
97
+ parts.push(...await imageParts(block, images, {
98
+ message,
99
+ image: nextImage.value
100
+ }, parts.length > 0));
101
+ break;
102
+ case "tool-result":
103
+ parts.push(...await contentParts(block.content, images, message, nextImage));
104
+ break;
105
+ default: break;
106
+ }
107
+ return parts;
108
+ }
109
+ /** Keep text-only user messages on the compact string wire form. */
110
+ function userContent(parts) {
111
+ const text = [];
112
+ for (const part of parts) {
113
+ if (part.type !== "text") return [...parts];
114
+ text.push(part.text);
115
+ }
116
+ return text.join("");
117
+ }
118
+ /** Serialize one assistant message (text + reasoning + tool calls). */
119
+ function serializeAssistant(message) {
120
+ const text = flattenText(message.content);
121
+ const reasoning = message.content.filter((block) => block.type === "reasoning").map((block) => block.text).join("");
122
+ const toolCalls = message.content.filter((block) => block.type === "tool-call").map((block) => ({
123
+ id: block.id,
124
+ type: "function",
125
+ function: {
126
+ name: block.name,
127
+ arguments: block.arguments
128
+ }
129
+ }));
130
+ return {
131
+ role: "assistant",
132
+ content: text,
133
+ ...reasoning.length > 0 ? { reasoning_content: reasoning } : {},
134
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
135
+ };
136
+ }
137
+ /**
138
+ * Serialize the conversation. `tool-result` blocks become standalone
139
+ * `{role: 'tool'}` messages; the harness puts each tool result in its own
140
+ * user-role message, so a mixed user message contributes its text first and
141
+ * its tool results as separate wire messages after.
142
+ * @param messages - the harness conversation, in order.
143
+ * @returns the wire messages; order preserved, each tool result expanded into its own entry.
144
+ */
145
+ function serializeMessages(messages) {
146
+ const wire = [];
147
+ for (const message of messages) {
148
+ assertTextOnly(message.content);
149
+ if (message.role === "system") {
150
+ wire.push({
151
+ role: "system",
152
+ content: flattenText(message.content)
153
+ });
154
+ continue;
155
+ }
156
+ if (message.role === "assistant") {
157
+ wire.push(serializeAssistant(message));
158
+ continue;
159
+ }
160
+ const toolResults = message.content.filter((block) => block.type === "tool-result");
161
+ const text = flattenText(message.content);
162
+ if (text.length > 0 || toolResults.length === 0) wire.push({
163
+ role: "user",
164
+ content: text
165
+ });
166
+ for (const result of toolResults) wire.push({
167
+ role: "tool",
168
+ tool_call_id: result.toolCallId,
169
+ content: flattenText(result.content) || "(no output)"
170
+ });
171
+ }
172
+ return wire;
173
+ }
174
+ /**
175
+ * Serialize image-capable history after resolving durable attachments.
176
+ * Consecutive tool results keep string `tool` messages and share one following
177
+ * user message containing their images.
178
+ * @param messages - transient request history after request-size offloading.
179
+ * @param images - prepared request versions, one provider representation, and its budget.
180
+ * @returns ordered DeepSeek wire messages.
181
+ */
182
+ async function serializeMessagesWithImages(messages, images) {
183
+ assertSupportedImageRoles(messages);
184
+ const wire = [];
185
+ let pendingToolImages = [];
186
+ const flushToolImages = () => {
187
+ if (pendingToolImages.length === 0) return;
188
+ wire.push({
189
+ role: "user",
190
+ content: [{
191
+ type: "text",
192
+ text: TOOL_RESULT_IMAGE_TEXT
193
+ }, ...pendingToolImages]
194
+ });
195
+ pendingToolImages = [];
196
+ };
197
+ for (const [messageIndex, message] of messages.entries()) {
198
+ const nextImage = { value: 0 };
199
+ if (message.role === "system") {
200
+ flushToolImages();
201
+ wire.push({
202
+ role: "system",
203
+ content: flattenText(message.content)
204
+ });
205
+ continue;
206
+ }
207
+ if (message.role === "assistant") {
208
+ flushToolImages();
209
+ wire.push(serializeAssistant(message));
210
+ continue;
211
+ }
212
+ const regular = message.content.filter((block) => block.type !== "tool-result");
213
+ const toolResults = message.content.filter((block) => block.type === "tool-result");
214
+ const content = userContent(await contentParts(regular, images, messageIndex + 1, nextImage));
215
+ if (content.length > 0 || toolResults.length === 0) {
216
+ flushToolImages();
217
+ wire.push({
218
+ role: "user",
219
+ content
220
+ });
221
+ }
222
+ for (const result of toolResults) {
223
+ const parts = await contentParts(result.content, images, messageIndex + 1, nextImage);
224
+ const imageParts = parts.filter((part) => part.type !== "text");
225
+ const text = parts.filter((part) => part.type === "text").map((part) => part.text).join("");
226
+ wire.push({
227
+ role: "tool",
228
+ tool_call_id: result.toolCallId,
229
+ content: text || "(no output)"
230
+ });
231
+ pendingToolImages.push(...imageParts);
232
+ }
233
+ }
234
+ flushToolImages();
235
+ return wire;
236
+ }
237
+ /** Assemble request fields shared by text-only and image-capable conversion. */
238
+ function requestWithMessages(options, messages, defaults) {
239
+ messages = ultraRequest(options, messages);
240
+ const tools = options.tools?.map((tool) => ({
241
+ type: "function",
242
+ function: {
243
+ name: tool.name,
244
+ description: tool.description,
245
+ parameters: tool.parameters
246
+ }
247
+ }));
248
+ const resolvedThinking = resolveThinking(options, defaults);
249
+ return {
250
+ model: options.model,
251
+ messages,
252
+ stream: true,
253
+ stream_options: { include_usage: true },
254
+ ...resolvedThinking.thinking !== void 0 ? { thinking: { type: resolvedThinking.thinking } } : {},
255
+ ...resolvedThinking.reasoningEffort !== void 0 ? { reasoning_effort: resolvedThinking.reasoningEffort } : {},
256
+ ...tools !== void 0 && tools.length > 0 ? { tools } : {},
257
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
258
+ ...options.maxTokens === void 0 ? {} : { max_tokens: options.maxTokens },
259
+ ...options.stop !== void 0 ? { stop: options.stop } : {}
260
+ };
261
+ }
262
+ /**
263
+ * Build the full wire request. Always streaming (`stream: true`, usage
264
+ * reporting on); optional fields are omitted rather than sent as null, so
265
+ * provider defaults apply.
266
+ * @param options - the harness request (model, history, system, tools, sampling).
267
+ * @param defaults - adapter-level thinking defaults; undefined fields put nothing on the wire.
268
+ * @returns the chat-completions request body.
269
+ */
270
+ function serializeRequest(options, defaults = {}) {
271
+ const messages = [];
272
+ if (options.system !== void 0) messages.push({
273
+ role: "system",
274
+ content: options.system
275
+ });
276
+ messages.push(...serializeMessages(options.messages));
277
+ return requestWithMessages(options, messages, defaults);
278
+ }
279
+ /**
280
+ * Build one image-capable request while keeping durable bytes out of session
281
+ * messages. Oversized oldest images become per-image text after their
282
+ * exact request-version byte lengths are known and before provider serialization.
283
+ * @param options - harness request containing image-capable user content.
284
+ * @param images - request versions, optional current access resolver, and request bounds.
285
+ * @param defaults - adapter-level thinking defaults.
286
+ * @returns the fully materialized DeepSeek request body.
287
+ */
288
+ async function serializeRequestWithImages(options, images, defaults = {}) {
289
+ assertSupportedImageRoles(options.messages);
290
+ const requestMessages = offloadRequestImagesWithPolicy(options.messages, {
291
+ representation: images.representation.kind === "file" ? "raw" : "base64",
292
+ byteLength: (ref) => {
293
+ const version = images.requestImages.get(ref.attachmentId);
294
+ if (version === void 0) throw new LlmError(`DeepSeek request image ${ref.attachmentId} was not prepared.`, "INVALID_REQUEST");
295
+ return version.bytes;
296
+ },
297
+ maxBytes: images.maxRequestImageBytes,
298
+ ...images.maxImagesPerRequest === void 0 ? {} : { maxImages: images.maxImagesPerRequest },
299
+ ...images.byteQuantum === void 0 ? {} : { byteQuantum: images.byteQuantum },
300
+ ...images.countQuantum === void 0 ? {} : { countQuantum: images.countQuantum },
301
+ placeholder: (ref) => offloadedImageText(ref, images.resolveImageAccess?.(ref))
302
+ });
303
+ const messages = [];
304
+ if (options.system !== void 0) messages.push({
305
+ role: "system",
306
+ content: options.system
307
+ });
308
+ messages.push(...await serializeMessagesWithImages(requestMessages, images));
309
+ return requestWithMessages(options, messages, defaults);
310
+ }
311
+ //#endregion
312
+ //#region lib/types/image-tokens.js
313
+ /**
314
+ * DeepSeek v4 vision-token accounting: the provider's published image-token
315
+ * calculator (api-docs.deepseek.com, Token & Token Usage) ported verbatim.
316
+ * The provider resizes every request image onto a 14px-patch grid, downsamples
317
+ * 3:1 per axis, and caps one image at 384 tokens; the port prices the
318
+ * pad-to-4 alignment at its 3-token upper bound because request pricing has
319
+ * no preceding-token position. Actual usage remains authoritative.
320
+ *
321
+ * @module dsh-llm-deepseek/image-tokens
322
+ */
323
+ /** Vision patch edge in pixels. */
324
+ const PATCH_SIZE = 14;
325
+ /** Per-axis patch-to-token downsampling ratio. */
326
+ const DOWNSAMPLE_RATIO = 3;
327
+ /** Provider cap on tokens for one request image. */
328
+ const MAX_IMAGE_TOKENS = 384;
329
+ /** Token-alignment quantum; pricing charges its worst-case `QUANTUM - 1` pad. */
330
+ const COMPRESS_PAD_TO = 4;
331
+ /** Width is clamped to this multiple of height before grid projection. */
332
+ const MAX_WIDTH_HEIGHT_RATIO = 8;
333
+ /** Total-pixel floor; smaller images are scaled up before grid projection. */
334
+ const MIN_PIXELS = 384 * 384;
335
+ const intDiv = (value, divisor) => Math.floor(value / divisor);
336
+ const ceilDiv = (value, divisor) => Math.floor((value + divisor - 1) / divisor);
337
+ /** Token count of one grid, including row separators and framing. */
338
+ function gridTokens(gridHeight, gridWidth) {
339
+ let tokens = gridHeight * (gridWidth + 1) + 2;
340
+ if (gridHeight % 2 === 1) tokens += gridWidth + 1;
341
+ tokens += ceilDiv(gridHeight, 2) * (gridWidth + 1) % 2 * 2;
342
+ return tokens;
343
+ }
344
+ /** Solve the largest grid within `budget` tokens preserving the aspect ratio. */
345
+ function solveResizeRatio(height, width, budget) {
346
+ const aspect = height / width;
347
+ const idealGridWidth = Math.sqrt((budget - 2) / aspect + .25) - .5;
348
+ const idealGridHeight = idealGridWidth * aspect;
349
+ let bestHeight;
350
+ let bestWidth;
351
+ if (idealGridWidth < 1) {
352
+ const solvedGridWidth = 1;
353
+ let solvedGridHeight = intDiv(budget - 2, 2);
354
+ // v8 ignore: at the provider budget the one-column solve always lands on
355
+ /* v8 ignore next */
356
+ if (solvedGridHeight % 2 === 1) solvedGridHeight -= 1;
357
+ bestWidth = solvedGridWidth * PATCH_SIZE * DOWNSAMPLE_RATIO;
358
+ bestHeight = solvedGridHeight * PATCH_SIZE * DOWNSAMPLE_RATIO;
359
+ } else if (idealGridHeight < 2) {
360
+ const solvedGridHeight = 2;
361
+ const solvedGridWidth = intDiv(budget - 2, solvedGridHeight) - 1;
362
+ if (!(solvedGridWidth > 1)) throw new Error("deepseek image tokens: no grid fits the token budget");
363
+ bestWidth = solvedGridWidth * PATCH_SIZE * DOWNSAMPLE_RATIO;
364
+ bestHeight = solvedGridHeight * PATCH_SIZE * DOWNSAMPLE_RATIO;
365
+ } else {
366
+ const solvedGridWidth = Math.trunc(idealGridWidth);
367
+ let solvedGridHeight = Math.trunc(idealGridHeight);
368
+ if (solvedGridHeight % 2 === 1) solvedGridHeight -= 1;
369
+ const widthScale = solvedGridWidth * PATCH_SIZE * DOWNSAMPLE_RATIO / width;
370
+ const heightScale = solvedGridHeight * PATCH_SIZE * DOWNSAMPLE_RATIO / height;
371
+ const scale = Math.min(widthScale, heightScale);
372
+ bestWidth = Math.trunc(width * scale / PATCH_SIZE) * PATCH_SIZE;
373
+ bestHeight = Math.trunc(height * scale / PATCH_SIZE) * PATCH_SIZE;
374
+ }
375
+ const gridHeight = ceilDiv(intDiv(bestHeight, PATCH_SIZE), DOWNSAMPLE_RATIO);
376
+ const gridWidth = ceilDiv(intDiv(bestWidth, PATCH_SIZE), DOWNSAMPLE_RATIO);
377
+ return {
378
+ gridHeight,
379
+ gridWidth,
380
+ bestHeight,
381
+ bestWidth,
382
+ numTokens: gridTokens(gridHeight, gridWidth)
383
+ };
384
+ }
385
+ /** Project padded pixel dimensions onto the largest in-budget token grid. */
386
+ function safeResize(height, width, paddedHeight, paddedWidth) {
387
+ const gridHeight = ceilDiv(intDiv(paddedHeight, PATCH_SIZE), DOWNSAMPLE_RATIO);
388
+ const gridWidth = ceilDiv(intDiv(paddedWidth, PATCH_SIZE), DOWNSAMPLE_RATIO);
389
+ const pad = COMPRESS_PAD_TO - 1;
390
+ const budget = MAX_IMAGE_TOKENS - pad;
391
+ let result = {
392
+ gridHeight,
393
+ gridWidth,
394
+ bestHeight: paddedHeight,
395
+ bestWidth: paddedWidth,
396
+ numTokens: gridTokens(gridHeight, gridWidth)
397
+ };
398
+ if (result.numTokens > budget) {
399
+ result = solveResizeRatio(height, width, budget);
400
+ /* v8 ignore next 4 -- the published solver's safety net; the closed-form
401
+ solve stays within budget for every geometry the clamps admit. */
402
+ for (let reduced = budget; result.numTokens > budget; reduced -= 1) result = solveResizeRatio(height, width, reduced);
403
+ }
404
+ return {
405
+ ...result,
406
+ numTokens: result.numTokens + pad
407
+ };
408
+ }
409
+ /** One clamp-scale-pad-project pass; the caller iterates it to a fixpoint. */
410
+ function resizeOnce(width, height) {
411
+ let clampedWidth = width;
412
+ let clampedHeight = height;
413
+ if (clampedWidth > clampedHeight * MAX_WIDTH_HEIGHT_RATIO) clampedWidth = clampedHeight * MAX_WIDTH_HEIGHT_RATIO;
414
+ const pixels = clampedWidth * clampedHeight;
415
+ if (pixels < MIN_PIXELS && pixels > 0) {
416
+ const scale = Math.sqrt(MIN_PIXELS / pixels);
417
+ clampedWidth = Math.trunc(clampedWidth * scale);
418
+ clampedHeight = Math.trunc(clampedHeight * scale);
419
+ }
420
+ const paddedWidth = ceilDiv(clampedWidth, PATCH_SIZE) * PATCH_SIZE;
421
+ const paddedHeight = ceilDiv(clampedHeight, PATCH_SIZE) * PATCH_SIZE;
422
+ return safeResize(clampedHeight, clampedWidth, paddedHeight, paddedWidth);
423
+ }
424
+ function sameResize(a, b) {
425
+ return a.gridHeight === b.gridHeight && a.gridWidth === b.gridWidth && a.bestHeight === b.bestHeight && a.bestWidth === b.bestWidth && a.numTokens === b.numTokens;
426
+ }
427
+ /**
428
+ * Vision tokens DeepSeek v4 charges for one request image of the given
429
+ * dimensions, at the worst-case alignment pad.
430
+ * @param width - positive integer request-image width in pixels.
431
+ * @param height - positive integer request-image height in pixels.
432
+ * @returns the provider vision-token price, at most 384.
433
+ */
434
+ function deepSeekImageTokens(width, height) {
435
+ let result = resizeOnce(width, height);
436
+ for (let iteration = 1; iteration < 10; iteration += 1) {
437
+ const next = resizeOnce(result.bestWidth, result.bestHeight);
438
+ if (sameResize(next, result)) return result.numTokens;
439
+ result = next;
440
+ }
441
+ /* v8 ignore next 2 -- the published solver's non-convergence guard; every
442
+ pass is a projection, so a second identical pass is a fixpoint. */
443
+ throw new Error(`deepseek image tokens: resize did not converge for ${width}x${height}`);
444
+ }
445
+ //#endregion
446
+ //#region lib/types/request-pricing.js
447
+ /**
448
+ * Provider-side request-image pricing for DeepSeek routes: reproduces the
449
+ * adapter's deterministic request projection (per-model pixel budget,
450
+ * oldest-first offload under the raw-byte and count budgets) and prices every
451
+ * retained image with the published v4 vision-token accounting. Consumed
452
+ * synchronously by the token meter through `LlmAdapter.imageRequestPricing`;
453
+ * provider usage remains the authoritative anchor for completed requests.
454
+ *
455
+ * @module dsh-llm-deepseek/request-pricing
456
+ */
457
+ /** Default bound on accumulated file-referenced image bytes per request. */
458
+ const DEFAULT_MAX_REQUEST_FILES_BYTES = 128 * 1024 * 1024;
459
+ /** Provider request image-count limit. */
460
+ const DEFAULT_MAX_IMAGES_PER_REQUEST = 600;
461
+ /** Total-pixel budget matching DeepSeek's normal vision projection. */
462
+ const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 64e4;
463
+ /** Total-pixel budget matching provider low-detail image input. */
464
+ const DEFAULT_LOW_DETAIL_IMAGE_PIXEL_BUDGET = 512 * 512;
465
+ /** Encoded-byte target for one deterministic model-request image; the smallest quality-ladder output is used when no quality fits. */
466
+ const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1024 * 1024;
467
+ /**
468
+ * Resolve the request-image budgets owned by one DeepSeek model route.
469
+ * @param model - Advertised model route and its optional image overrides.
470
+ * @returns Complete pixel and encoded-byte budgets.
471
+ * @internal
472
+ */
473
+ function resolveRequestImagePolicy(model) {
474
+ return {
475
+ maxPixels: model.imagePixelBudget === "low" ? DEFAULT_LOW_DETAIL_IMAGE_PIXEL_BUDGET : model.imagePixelBudget ?? 64e4,
476
+ maxBytes: model.imageMaxBytes === void 0 ? DEFAULT_REQUEST_IMAGE_MAX_BYTES : model.imageMaxBytes
477
+ };
478
+ }
479
+ /**
480
+ * Price one occurrence a text-only route substitutes with deterministic text,
481
+ * reproducing the `projectImagesForTextModel` substitution `LlmRuntime`
482
+ * applies before dispatching to a route without the `image` modality.
483
+ */
484
+ function textOnlyPrice(ref) {
485
+ return {
486
+ visualTokens: 0,
487
+ text: textOnlyImageText(ref)
488
+ };
489
+ }
490
+ /**
491
+ * Build the request-image pricing for one DeepSeek route from a validated
492
+ * connection snapshot. Uncatalogued and text-only models price every
493
+ * occurrence as its deterministic text substitution; image-capable models
494
+ * reproduce the adapter's first-stage oldest-first offload from durable byte
495
+ * lengths and price retained images by their projected request dimensions,
496
+ * with each occurrence's handle or placeholder text built through the same
497
+ * access resolution the serializer uses. The base64 fallback's tighter inline
498
+ * budget is not reproduced, so a fallback request can only cost less than
499
+ * this estimate; access paths resolve at pricing time, so a path that changes
500
+ * before the request only shifts the text price by its own length.
501
+ * @param connection - validated connection facts of the pricing resolution.
502
+ * @param model - exact model id named by the request header.
503
+ * @param resolveAccess - current execution-world access resolution shared with request serialization.
504
+ * @returns synchronous per-occurrence pricing for the route.
505
+ */
506
+ function deepSeekImageRequestPricing(connection, model, resolveAccess) {
507
+ const catalogModel = connection.models.find((entry) => entry.id === model);
508
+ if (catalogModel?.inputModalities?.includes("image") !== true) return { priceImages: (images) => images.map(textOnlyPrice) };
509
+ const policy = resolveRequestImagePolicy(catalogModel);
510
+ return { priceImages: (images) => {
511
+ const offloaded = offloadedImagePrefixCount(images.map((ref) => Math.min(ref.bytes, policy.maxBytes)), {
512
+ maxBytes: connection.maxRequestFilesBytes,
513
+ maxImages: connection.maxImagesPerRequest,
514
+ byteQuantum: connection.imageOffloadByteQuantum,
515
+ countQuantum: connection.imageOffloadCountQuantum
516
+ });
517
+ return images.map((ref, index) => {
518
+ if (index < offloaded) return {
519
+ visualTokens: 0,
520
+ text: offloadedImageText(ref, resolveAccess?.(ref))
521
+ };
522
+ const dimensions = requestImageDimensions(ref.width, ref.height, policy.maxPixels);
523
+ return {
524
+ visualTokens: deepSeekImageTokens(dimensions.width, dimensions.height),
525
+ text: requestImageHandleText(ref, dimensions, resolveAccess?.(ref))
526
+ };
527
+ });
528
+ } };
529
+ }
530
+ //#endregion
531
+ //#region lib/types/file-id.js
532
+ /** DeepSeek Files API identifiers. @module dsh-llm-deepseek/file-id */
533
+ /**
534
+ * Brand a provider-returned file identifier after wire validation.
535
+ * @param id - non-empty Files API identifier.
536
+ * @returns the same string with its provider identity attached at type level.
537
+ */
538
+ function DeepSeekFileId(id) {
539
+ return id;
540
+ }
541
+ /**
542
+ * Brand a locally derived namespace digest.
543
+ * @param scope - SHA-256 digest of endpoint and API key.
544
+ * @returns the same string with namespace identity attached at type level.
545
+ */
546
+ function DeepSeekFileScope(scope) {
547
+ return scope;
548
+ }
549
+ //#endregion
550
+ //#region lib/types/files-api.js
551
+ /** OpenAI-compatible DeepSeek Files API transport. @module dsh-llm-deepseek/files-api */
552
+ /** Minimum provider-supported file lifetime. */
553
+ const MIN_FILE_EXPIRY_SECONDS = 3600;
554
+ /** Maximum provider-supported file lifetime. */
555
+ const MAX_FILE_EXPIRY_SECONDS = 2592e3;
556
+ /** Maximum Files API upload size. */
557
+ const MAX_FILE_UPLOAD_BYTES = 128 * 1024 * 1024;
558
+ /** Current per-key file-count quota. */
559
+ const MAX_STORED_FILE_COUNT = 1e4;
560
+ /** Current per-key storage quota. */
561
+ const MAX_STORED_FILE_BYTES = 25 * 1024 * 1024 * 1024;
562
+ /** Files API operation failure with its HTTP status retained for recovery policy. */
563
+ var DeepSeekFilesError = class extends LlmError {
564
+ /** Parsed provider detail used only for error classification. */
565
+ detail;
566
+ /**
567
+ * @param message - user-readable provider failure.
568
+ * @param status - HTTP status returned by the Files API.
569
+ * @param detail - provider error fields joined for classification.
570
+ */
571
+ constructor(message, status, detail) {
572
+ super(message, status === 401 || status === 403 ? "AUTH" : status === 429 ? "RATE_LIMIT" : status >= 500 ? "SERVER" : "FILES_API", { status });
573
+ this.name = "DeepSeekFilesError";
574
+ this.detail = detail;
575
+ }
576
+ };
577
+ /**
578
+ * Whether an upload failure reports a provider storage or file-count quota.
579
+ * @param error - Files API operation failure.
580
+ * @returns whether one bounded remote cleanup and upload retry may recover.
581
+ */
582
+ function isFilesQuotaError(error) {
583
+ return error instanceof DeepSeekFilesError && /(?:quota|storage|stored files|file count|too many files)/iu.test(error.detail);
584
+ }
585
+ function invalidResponse(operation) {
586
+ return new LlmError(`DeepSeek Files API returned an invalid ${operation} response.`, "INVALID_RESPONSE");
587
+ }
588
+ function parseFileObject(value, operation) {
589
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidResponse(operation);
590
+ const wire = value;
591
+ if (typeof wire.id !== "string" || wire.id.length === 0 || wire.object !== "file" || !Number.isSafeInteger(wire.bytes) || wire.bytes < 0 || !Number.isSafeInteger(wire.created_at) || wire.created_at < 0 || typeof wire.filename !== "string" || wire.filename.length === 0 || wire.purpose !== "user_data" || wire.expires_at !== void 0 && (!Number.isSafeInteger(wire.expires_at) || wire.expires_at < 0)) throw invalidResponse(operation);
592
+ return {
593
+ id: DeepSeekFileId(wire.id),
594
+ bytes: wire.bytes,
595
+ createdAt: wire.created_at,
596
+ filename: wire.filename,
597
+ purpose: "user_data",
598
+ ...wire.expires_at === void 0 ? {} : { expiresAt: wire.expires_at }
599
+ };
600
+ }
601
+ function providerErrorDetail(value) {
602
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return { detail: "" };
603
+ const error = value.error;
604
+ if (error === null || typeof error !== "object" || Array.isArray(error)) return { detail: "" };
605
+ const fields = error;
606
+ const message = typeof fields.message === "string" ? fields.message : void 0;
607
+ return {
608
+ ...message === void 0 ? {} : { message },
609
+ detail: [
610
+ fields.code,
611
+ fields.type,
612
+ fields.message
613
+ ].filter((field) => typeof field === "string").join(" ")
614
+ };
615
+ }
616
+ /** Direct client for the OpenAI-compatible `/files` endpoints. */
617
+ var DeepSeekFilesClient = class {
618
+ baseURL;
619
+ apiKey;
620
+ fetchImpl;
621
+ /**
622
+ * @param options - endpoint, API-key snapshot, and optional test transport.
623
+ */
624
+ constructor(options) {
625
+ this.baseURL = options.baseURL.replace(/\/+$/u, "");
626
+ this.apiKey = options.apiKey;
627
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
628
+ }
629
+ async request(path, init, signal) {
630
+ let response;
631
+ try {
632
+ const headers = new Headers(attributionHeaders());
633
+ headers.set("authorization", `Bearer ${this.apiKey}`);
634
+ response = await this.fetchImpl(`${this.baseURL}${path}`, {
635
+ ...init,
636
+ headers,
637
+ ...signal === void 0 ? {} : { signal }
638
+ });
639
+ } catch (error) {
640
+ if (signal?.aborted) throw error;
641
+ throw new LlmError(`DeepSeek Files API request to ${this.baseURL} failed`, "TRANSPORT", { cause: error });
642
+ }
643
+ if (response.ok) return response;
644
+ let parsed;
645
+ try {
646
+ parsed = await response.json();
647
+ } catch {}
648
+ const { message, detail } = providerErrorDetail(parsed);
649
+ throw new DeepSeekFilesError(message ?? `DeepSeek Files API error (HTTP ${response.status})`, response.status, detail);
650
+ }
651
+ /**
652
+ * Upload one image with an explicit expiry.
653
+ * @param input - deterministic request-version bytes, media type, filename, lifetime, and cancellation.
654
+ * @returns the validated provider file object, including `expires_at`.
655
+ */
656
+ async upload(input) {
657
+ if (input.data.byteLength > 134217728) throw new LlmError("DeepSeek Files API upload exceeds 128 MiB.", "INVALID_REQUEST");
658
+ if (!Number.isSafeInteger(input.expiresAfterSeconds) || input.expiresAfterSeconds < 3600 || input.expiresAfterSeconds > 2592e3) throw new LlmError("DeepSeek file expiry must be between 3600 and 2592000 seconds.", "INVALID_REQUEST");
659
+ const form = new FormData();
660
+ form.set("purpose", "user_data");
661
+ form.set("expires_after[anchor]", "created_at");
662
+ form.set("expires_after[seconds]", String(input.expiresAfterSeconds));
663
+ form.set("file", new Blob([Uint8Array.from(input.data).buffer], { type: input.mediaType }), input.filename);
664
+ const file = parseFileObject(await (await this.request("/files", {
665
+ method: "POST",
666
+ body: form
667
+ }, input.signal)).json(), "upload");
668
+ if (file.expiresAt === void 0) throw invalidResponse("upload");
669
+ return {
670
+ ...file,
671
+ expiresAt: file.expiresAt
672
+ };
673
+ }
674
+ /**
675
+ * List one ascending or descending page of user-data files.
676
+ * @param options - pagination, ordering, and cancellation.
677
+ * @returns the validated page.
678
+ */
679
+ async list(options = {}) {
680
+ const query = new URLSearchParams({ purpose: "user_data" });
681
+ if (options.after !== void 0) query.set("after", options.after);
682
+ if (options.limit !== void 0) query.set("limit", String(options.limit));
683
+ if (options.order !== void 0) query.set("order", options.order);
684
+ const value = await (await this.request(`/files?${query.toString()}`, { method: "GET" }, options.signal)).json();
685
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("list");
686
+ const wire = value;
687
+ if (wire.object !== "list" || !Array.isArray(wire.data) || typeof wire.has_more !== "boolean" || wire.first_id !== void 0 && typeof wire.first_id !== "string" || wire.last_id !== void 0 && typeof wire.last_id !== "string") throw invalidResponse("list");
688
+ return {
689
+ data: wire.data.map((item) => parseFileObject(item, "list")),
690
+ ...typeof wire.first_id === "string" ? { firstId: DeepSeekFileId(wire.first_id) } : {},
691
+ ...typeof wire.last_id === "string" ? { lastId: DeepSeekFileId(wire.last_id) } : {},
692
+ hasMore: wire.has_more
693
+ };
694
+ }
695
+ /**
696
+ * Retrieve one file object.
697
+ * @param fileId - provider file identifier.
698
+ * @param signal - request cancellation.
699
+ * @returns the validated file object.
700
+ */
701
+ async retrieve(fileId, signal) {
702
+ return parseFileObject(await (await this.request(`/files/${encodeURIComponent(fileId)}`, { method: "GET" }, signal)).json(), "retrieve");
703
+ }
704
+ /**
705
+ * Delete one provider file.
706
+ * @param fileId - provider file identifier.
707
+ * @param signal - request cancellation.
708
+ */
709
+ async delete(fileId, signal) {
710
+ const value = await (await this.request(`/files/${encodeURIComponent(fileId)}`, { method: "DELETE" }, signal)).json();
711
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("delete");
712
+ const wire = value;
713
+ if (wire.id !== fileId || wire.object !== "file" || wire.deleted !== true) throw invalidResponse("delete");
714
+ }
715
+ };
716
+ //#endregion
717
+ //#region lib/types/upload-index.js
718
+ /** Durable DeepSeek attachment-to-file-id index. @module dsh-llm-deepseek/upload-index */
719
+ var InvalidUploadIndexError = class extends Error {};
720
+ /**
721
+ * Derive a non-secret stable index namespace without persisting or logging the API key.
722
+ * @param baseURL - normalized provider endpoint namespace.
723
+ * @param apiKey - resolved credential used only as hash input.
724
+ * @returns branded SHA-256 namespace digest.
725
+ */
726
+ function deepSeekFileScope(baseURL, apiKey) {
727
+ return DeepSeekFileScope(createHash("sha256").update(baseURL.replace(/\/+$/u, "")).update("\0").update(apiKey).digest("hex"));
728
+ }
729
+ function absent(error) {
730
+ return error?.code === "ENOENT";
731
+ }
732
+ function parseRecord(value) {
733
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new InvalidUploadIndexError("llm-deepseek: upload index contains a non-object record");
734
+ const record = value;
735
+ if (typeof record.scope !== "string" || !/^[0-9a-f]{64}$/u.test(record.scope) || typeof record.attachmentId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(record.attachmentId) || typeof record.variantId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(record.variantId) || typeof record.fileId !== "string" || record.fileId.length === 0 || !Number.isSafeInteger(record.bytes) || record.bytes < 0 || !Number.isSafeInteger(record.createdAt) || record.createdAt < 0 || !Number.isSafeInteger(record.expiresAt) || record.expiresAt < 0) throw new InvalidUploadIndexError("llm-deepseek: upload index contains an invalid record");
736
+ return {
737
+ scope: DeepSeekFileScope(record.scope),
738
+ attachmentId: record.attachmentId,
739
+ variantId: ImageVariantId(record.variantId),
740
+ fileId: DeepSeekFileId(record.fileId),
741
+ bytes: record.bytes,
742
+ createdAt: record.createdAt,
743
+ expiresAt: record.expiresAt
744
+ };
745
+ }
746
+ function parseIndex(text) {
747
+ let value;
748
+ try {
749
+ value = JSON.parse(text);
750
+ } catch (error) {
751
+ throw new InvalidUploadIndexError("llm-deepseek: upload index is not valid JSON", { cause: error });
752
+ }
753
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new InvalidUploadIndexError("llm-deepseek: upload index is not an object");
754
+ const index = value;
755
+ if (index.formatVersion !== 3 || !Array.isArray(index.records)) throw new InvalidUploadIndexError("llm-deepseek: unsupported upload index format");
756
+ const records = index.records.map(parseRecord);
757
+ const keys = /* @__PURE__ */ new Set();
758
+ for (const record of records) {
759
+ const key = `${record.scope}\0${record.variantId}`;
760
+ if (keys.has(key)) throw new InvalidUploadIndexError("llm-deepseek: upload index contains duplicate mappings");
761
+ keys.add(key);
762
+ }
763
+ return {
764
+ formatVersion: 3,
765
+ records
766
+ };
767
+ }
768
+ function reusable(record, now, refreshMarginMs) {
769
+ return record.expiresAt - now > refreshMarginMs;
770
+ }
771
+ /** Atomic local index shared by every DeepSeek session in this DSH home. */
772
+ var DeepSeekUploadIndex = class {
773
+ /** Absolute owner-private JSON index path. */
774
+ path;
775
+ /**
776
+ * @param path - explicit test path; omission uses `DSH_HOME/llm-deepseek/files-v3.json`.
777
+ */
778
+ constructor(path = join(resolveDshHome(), "llm-deepseek", "files-v3.json")) {
779
+ this.path = path;
780
+ }
781
+ async load() {
782
+ try {
783
+ return parseIndex(await readFile(this.path, "utf8"));
784
+ } catch (error) {
785
+ if (absent(error) || error instanceof InvalidUploadIndexError) return {
786
+ formatVersion: 3,
787
+ records: []
788
+ };
789
+ throw error;
790
+ }
791
+ }
792
+ async save(index) {
793
+ await writeFileAtomic(this.path, `${JSON.stringify(index, void 0, 2)}\n`, {
794
+ mode: 384,
795
+ dirMode: 448
796
+ });
797
+ }
798
+ /**
799
+ * Read one reusable mapping.
800
+ * @param scope - endpoint/API-key namespace.
801
+ * @param variantId - complete request-image transformation identity.
802
+ * @param now - current Unix time in milliseconds.
803
+ * @param refreshMarginMs - remaining lifetime below which a mapping is not reused.
804
+ * @returns the mapping when it has enough lifetime remaining.
805
+ */
806
+ async get(scope, variantId, now, refreshMarginMs) {
807
+ const record = (await this.load()).records.find((candidate) => candidate.scope === scope && candidate.variantId === variantId);
808
+ return record !== void 0 && reusable(record, now, refreshMarginMs) ? record : void 0;
809
+ }
810
+ /**
811
+ * Publish a completed upload unless another process already published a reusable mapping.
812
+ * @param candidate - completed remote upload.
813
+ * @param now - current Unix time in milliseconds.
814
+ * @param refreshMarginMs - minimum reusable remaining lifetime.
815
+ * @returns the winning record and whether the candidate entered the index.
816
+ */
817
+ async commit(candidate, now, refreshMarginMs) {
818
+ await mkdir(dirname(this.path), {
819
+ recursive: true,
820
+ mode: 448
821
+ });
822
+ return withFileLock(this.path, async () => {
823
+ const index = await this.load();
824
+ const existing = index.records.find((record) => record.scope === candidate.scope && record.variantId === candidate.variantId && reusable(record, now, refreshMarginMs));
825
+ if (existing !== void 0) return {
826
+ record: existing,
827
+ accepted: false
828
+ };
829
+ const records = index.records.filter((record) => reusable(record, now, refreshMarginMs) && !(record.scope === candidate.scope && record.variantId === candidate.variantId));
830
+ records.push(candidate);
831
+ await this.save({
832
+ formatVersion: 3,
833
+ records
834
+ });
835
+ return {
836
+ record: candidate,
837
+ accepted: true
838
+ };
839
+ });
840
+ }
841
+ /**
842
+ * Remove one exact mapping without deleting a concurrently installed successor.
843
+ * @param scope - endpoint/API-key namespace.
844
+ * @param variantId - complete request-image transformation identity.
845
+ * @param fileId - exact remote generation being invalidated.
846
+ */
847
+ async remove(scope, variantId, fileId) {
848
+ await mkdir(dirname(this.path), {
849
+ recursive: true,
850
+ mode: 448
851
+ });
852
+ await withFileLock(this.path, async () => {
853
+ const index = await this.load();
854
+ const records = index.records.filter((record) => !(record.scope === scope && record.variantId === variantId && record.fileId === fileId));
855
+ if (records.length !== index.records.length) await this.save({
856
+ formatVersion: 3,
857
+ records
858
+ });
859
+ });
860
+ }
861
+ /**
862
+ * Remove every local mapping for one remote namespace.
863
+ * @param scope - endpoint/API-key namespace.
864
+ */
865
+ async clear(scope) {
866
+ await mkdir(dirname(this.path), {
867
+ recursive: true,
868
+ mode: 448
869
+ });
870
+ await withFileLock(this.path, async () => {
871
+ const index = await this.load();
872
+ const records = index.records.filter((record) => record.scope !== scope);
873
+ if (records.length !== index.records.length) await this.save({
874
+ formatVersion: 3,
875
+ records
876
+ });
877
+ });
878
+ }
879
+ };
880
+ //#endregion
881
+ //#region lib/types/file-store.js
882
+ /** DeepSeek Files API upload reuse, invalidation, and quota recovery. @module dsh-llm-deepseek/file-store */
883
+ /** DeepSeek chat accepts at most 32 MiB per image even when it is referenced by file id. */
884
+ const MAX_CHAT_IMAGE_BYTES = 32 * 1024 * 1024;
885
+ const OWNED_FILE_PREFIX = "dsh-";
886
+ function abortReason(signal) {
887
+ const reason = signal.reason;
888
+ return reason instanceof Error ? reason : new Error("DeepSeek file upload cancelled with a non-Error reason.", { cause: reason });
889
+ }
890
+ function uploadFailure(error) {
891
+ return error instanceof Error ? error : new Error("DeepSeek file upload failed with a non-Error reason.", { cause: error });
892
+ }
893
+ function waitForUpload(operation, signal) {
894
+ signal?.throwIfAborted();
895
+ operation.waiters += 1;
896
+ let released = false;
897
+ const release = (cancelledReason) => {
898
+ if (released) return;
899
+ released = true;
900
+ operation.waiters -= 1;
901
+ if (cancelledReason !== void 0 && operation.waiters === 0 && !operation.settled) operation.controller.abort(cancelledReason);
902
+ };
903
+ if (signal === void 0) return operation.promise.finally(() => {
904
+ release();
905
+ });
906
+ return new Promise((resolve, reject) => {
907
+ const abort = () => {
908
+ const reason = abortReason(signal);
909
+ release(reason);
910
+ reject(reason);
911
+ };
912
+ signal.addEventListener("abort", abort, { once: true });
913
+ operation.promise.then((value) => {
914
+ signal.removeEventListener("abort", abort);
915
+ release();
916
+ resolve(value);
917
+ }, (error) => {
918
+ signal.removeEventListener("abort", abort);
919
+ release();
920
+ reject(uploadFailure(error));
921
+ });
922
+ });
923
+ }
924
+ function extension(mediaType) {
925
+ switch (mediaType) {
926
+ case "image/png": return "png";
927
+ case "image/jpeg": return "jpeg";
928
+ case "image/webp": return "webp";
929
+ case "image/gif": return "gif";
930
+ }
931
+ }
932
+ function filename(version) {
933
+ return `${OWNED_FILE_PREFIX}${String(version.attachment.attachmentId).slice(7, 23)}-${String(version.variantId).slice(7, 15)}.${extension(version.mediaType)}`;
934
+ }
935
+ /** User-scoped durable file-id reuse for the DeepSeek route. */
936
+ var DeepSeekFileStore = class {
937
+ index;
938
+ now;
939
+ fetchImpl;
940
+ inflight = /* @__PURE__ */ new Map();
941
+ /**
942
+ * @param options - testable index, clock, and transport boundaries.
943
+ */
944
+ constructor(options = {}) {
945
+ this.index = options.index ?? new DeepSeekUploadIndex();
946
+ this.now = options.now ?? Date.now;
947
+ this.fetchImpl = options.fetch;
948
+ }
949
+ client(connection) {
950
+ return new DeepSeekFilesClient({
951
+ baseURL: connection.baseURL,
952
+ apiKey: connection.apiKey,
953
+ ...this.fetchImpl === void 0 ? {} : { fetch: this.fetchImpl }
954
+ });
955
+ }
956
+ /**
957
+ * Resolve or upload one deterministic request image. Concurrent calls share one upload while retaining independent waits.
958
+ * @param version - deterministic model-request bytes and complete transformation identity.
959
+ * @param connection - endpoint and API-key snapshot.
960
+ * @param policy - expiry and quota-recovery policy.
961
+ * @param signal - cancellation of this wait; shared transport stops when no waiter remains.
962
+ * @returns a reusable file id and whether this call published a new upload.
963
+ */
964
+ ensureUploaded(version, connection, policy, signal) {
965
+ signal?.throwIfAborted();
966
+ const key = `${deepSeekFileScope(connection.baseURL, connection.apiKey)}\0${version.variantId}`;
967
+ let active = this.inflight.get(key);
968
+ if (active?.controller.signal.aborted) {
969
+ this.inflight.delete(key);
970
+ active = void 0;
971
+ }
972
+ if (active !== void 0) return waitForUpload(active, signal);
973
+ const controller = new AbortController();
974
+ const shared = {
975
+ controller,
976
+ settled: false,
977
+ waiters: 0,
978
+ promise: void 0
979
+ };
980
+ shared.promise = this.ensureUploadedOnce(version, connection, policy, controller.signal).then((value) => {
981
+ shared.settled = true;
982
+ return value;
983
+ }, (error) => {
984
+ shared.settled = true;
985
+ throw uploadFailure(error);
986
+ });
987
+ this.inflight.set(key, shared);
988
+ shared.promise.finally(() => {
989
+ if (this.inflight.get(key) === shared) this.inflight.delete(key);
990
+ }).catch(() => {});
991
+ return waitForUpload(shared, signal);
992
+ }
993
+ async ensureUploadedOnce(version, connection, policy, signal) {
994
+ if (version.bytes > 33554432) throw new LlmError("DeepSeek chat image exceeds the 32 MiB per-image limit.", "INVALID_REQUEST");
995
+ const scope = deepSeekFileScope(connection.baseURL, connection.apiKey);
996
+ const now = this.now();
997
+ const marginMs = policy.refreshMarginSeconds * 1e3;
998
+ const cached = await this.index.get(scope, version.variantId, now, marginMs);
999
+ if (cached !== void 0) return {
1000
+ record: cached,
1001
+ uploaded: false
1002
+ };
1003
+ const client = this.client(connection);
1004
+ const upload = async () => {
1005
+ const remote = await client.upload({
1006
+ data: version.data,
1007
+ mediaType: version.mediaType,
1008
+ filename: filename(version),
1009
+ expiresAfterSeconds: policy.expiresAfterSeconds,
1010
+ signal
1011
+ });
1012
+ if (remote.bytes !== version.data.byteLength) throw new LlmError("DeepSeek Files API upload response does not match the submitted image.", "INVALID_RESPONSE");
1013
+ return {
1014
+ scope,
1015
+ attachmentId: version.attachment.attachmentId,
1016
+ variantId: version.variantId,
1017
+ fileId: remote.id,
1018
+ bytes: remote.bytes,
1019
+ createdAt: remote.createdAt * 1e3,
1020
+ expiresAt: remote.expiresAt * 1e3
1021
+ };
1022
+ };
1023
+ let candidate;
1024
+ try {
1025
+ candidate = await upload();
1026
+ } catch (error) {
1027
+ if (!isFilesQuotaError(error)) throw error;
1028
+ if (await this.reclaimOldestOwned(connection, policy.quotaCleanupBatch, signal) === 0) throw error;
1029
+ candidate = await upload();
1030
+ }
1031
+ const committed = await this.index.commit(candidate, this.now(), marginMs);
1032
+ if (!committed.accepted) try {
1033
+ await client.delete(candidate.fileId, signal);
1034
+ } catch {}
1035
+ return {
1036
+ record: committed.record,
1037
+ uploaded: committed.accepted
1038
+ };
1039
+ }
1040
+ /**
1041
+ * Invalidate one exact local mapping after the chat endpoint rejects its remote id.
1042
+ * @param version - request-image version whose remote generation failed.
1043
+ * @param fileId - exact rejected file id.
1044
+ * @param connection - endpoint and API-key snapshot.
1045
+ */
1046
+ async invalidate(version, fileId, connection) {
1047
+ await this.index.remove(deepSeekFileScope(connection.baseURL, connection.apiKey), version.variantId, fileId);
1048
+ }
1049
+ /**
1050
+ * Delete the indexed remote file for one attachment and remove its local mapping.
1051
+ * @param version - exact request-image version to release.
1052
+ * @param connection - endpoint and API-key snapshot.
1053
+ * @param policy - expiry policy used to locate a reusable mapping.
1054
+ * @param signal - request cancellation.
1055
+ * @returns whether an indexed file existed and was deleted.
1056
+ */
1057
+ async release(version, connection, policy, signal) {
1058
+ const scope = deepSeekFileScope(connection.baseURL, connection.apiKey);
1059
+ const record = await this.index.get(scope, version.variantId, this.now(), policy.refreshMarginSeconds * 1e3);
1060
+ if (record === void 0) return false;
1061
+ await this.client(connection).delete(record.fileId, signal);
1062
+ await this.index.remove(scope, version.variantId, record.fileId);
1063
+ return true;
1064
+ }
1065
+ /**
1066
+ * Delete the oldest provider files whose names identify harness ownership.
1067
+ * @param connection - endpoint and API-key snapshot.
1068
+ * @param count - positive maximum number of files to delete.
1069
+ * @param signal - request cancellation.
1070
+ * @returns number of successfully deleted files.
1071
+ */
1072
+ async reclaimOldestOwned(connection, count, signal) {
1073
+ const client = this.client(connection);
1074
+ let after;
1075
+ const owned = [];
1076
+ while (owned.length < count) {
1077
+ const page = await client.list({
1078
+ ...after === void 0 ? {} : { after },
1079
+ limit: 1e3,
1080
+ order: "asc",
1081
+ ...signal === void 0 ? {} : { signal }
1082
+ });
1083
+ for (const file of page.data) {
1084
+ if (!file.filename.startsWith(OWNED_FILE_PREFIX)) continue;
1085
+ owned.push(file.id);
1086
+ if (owned.length === count) break;
1087
+ }
1088
+ if (!page.hasMore || page.lastId === void 0 || page.lastId === after) break;
1089
+ after = page.lastId;
1090
+ }
1091
+ for (const fileId of owned) await client.delete(fileId, signal);
1092
+ return owned.length;
1093
+ }
1094
+ /**
1095
+ * Delete every remote harness-owned file in the active API-key namespace and clear its index.
1096
+ * @param connection - endpoint and API-key snapshot.
1097
+ * @param signal - request cancellation.
1098
+ * @returns number of deleted files.
1099
+ */
1100
+ async releaseAll(connection, signal) {
1101
+ let total = 0;
1102
+ for (;;) {
1103
+ const deleted = await this.reclaimOldestOwned(connection, 1e3, signal);
1104
+ total += deleted;
1105
+ if (deleted < 1e3) break;
1106
+ }
1107
+ await this.index.clear(deepSeekFileScope(connection.baseURL, connection.apiKey));
1108
+ return total;
1109
+ }
1110
+ };
1111
+ /**
1112
+ * Parse an SSE byte stream into data payloads. Yields `[DONE]` as the final
1113
+ * value and returns; throws `LlmError('STREAM_CLOSED')` when the stream ends
1114
+ * without it (truncated response — the model call cannot be trusted).
1115
+ * @param stream - raw SSE bytes; reads may split anywhere, including mid-UTF-8 sequence.
1116
+ * @param onComment - optional transport-activity callback; comments never enter the yielded payload stream.
1117
+ * @returns each event's data payload in arrival order, the `[DONE]` sentinel last.
1118
+ */
1119
+ async function* parseSse(stream, onComment) {
1120
+ const events = stream.pipeThrough(new TextDecoderStream()).pipeThrough(new EventSourceParserStream({ onComment }));
1121
+ for await (const { data } of events) {
1122
+ yield data;
1123
+ if (data === "[DONE]") return;
1124
+ }
1125
+ throw new LlmError("SSE stream ended without [DONE]", "STREAM_CLOSED");
1126
+ }
1127
+ //#endregion
1128
+ //#region lib/types/translate.js
1129
+ /**
1130
+ * Translate DeepSeek SSE payloads with one stateful harness block per content, reasoning, or tool
1131
+ * call index. An empty initial reasoning delta does not open a block. Finish reason and the latest
1132
+ * usage are deferred until `[DONE]`, covering both finish-attached and trailing usage-only shapes
1133
+ * while ensuring no chunk follows `finish`.
1134
+ *
1135
+ * Translate DeepSeek wire chunks into the harness `StreamChunk` protocol.
1136
+ * @module dsh-llm-deepseek/translate
1137
+ */
1138
+ /**
1139
+ * Map the wire finish_reason vocabulary to the harness FinishReason.
1140
+ * @param reason - the wire `finish_reason` string.
1141
+ * @returns the mapped reason; unrecognized values (content_filter, …) become `{kind: 'error'}` with the uppercased value as `code`.
1142
+ */
1143
+ function mapFinishReason(reason) {
1144
+ switch (reason) {
1145
+ case "stop": return { kind: "stop" };
1146
+ case "tool_calls": return { kind: "tool-calls" };
1147
+ case "length": return { kind: "max-tokens" };
1148
+ default: return {
1149
+ kind: "error",
1150
+ failure: {
1151
+ message: `model stopped: ${reason}`,
1152
+ code: reason.toUpperCase()
1153
+ }
1154
+ };
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Map wire usage fields. DeepSeek's `prompt_tokens` INCLUDES cache hits
1159
+ * (`prompt_tokens = prompt_cache_hit_tokens + prompt_cache_miss_tokens`,
1160
+ * api/create-chat-completion); the harness TokenUsage convention is
1161
+ * DISJOINT counts, so cache reads are subtracted out of `inputTokens`.
1162
+ * @param usage - wire usage from the finish chunk or the trailing usage-only chunk.
1163
+ * @returns disjoint harness counts; an exact total is present only when the
1164
+ * aggregate prompt/completion counters are valid and agree with any wire total.
1165
+ */
1166
+ function mapUsage(usage) {
1167
+ const cacheRead = usage.prompt_tokens_details?.cached_tokens ?? usage.prompt_cache_hit_tokens;
1168
+ const reasoning = usage.completion_tokens_details?.reasoning_tokens;
1169
+ const combined = usage.prompt_tokens + usage.completion_tokens;
1170
+ const hasExactTotal = Number.isSafeInteger(usage.prompt_tokens) && usage.prompt_tokens >= 0 && Number.isSafeInteger(usage.completion_tokens) && usage.completion_tokens >= 0 && Number.isSafeInteger(combined) && (usage.total_tokens === void 0 || usage.total_tokens === combined);
1171
+ return {
1172
+ inputTokens: usage.prompt_tokens - (cacheRead ?? 0),
1173
+ outputTokens: usage.completion_tokens,
1174
+ ...hasExactTotal ? { totalTokens: combined } : {},
1175
+ ...cacheRead !== void 0 ? { cacheReadTokens: cacheRead } : {},
1176
+ ...reasoning !== void 0 ? { reasoningTokens: reasoning } : {}
1177
+ };
1178
+ }
1179
+ /**
1180
+ * Accept one streamed identity field for a tool call. `id` and `name` are
1181
+ * identity, not accumulation: the wire sends each once, on the call's first
1182
+ * delta. A continuation delta that re-sends the field empty — or `null`, which
1183
+ * some OpenAI-compatible gateways fill in — means "no update", never "clear".
1184
+ * @param current - the identity established by an earlier delta of this call.
1185
+ * @param incoming - the field as parsed from this delta. The wire type is a
1186
+ * claim about a remote encoder, so anything but a non-empty string leaves the
1187
+ * established value alone rather than overwriting it.
1188
+ * @returns the identity in force after this delta.
1189
+ */
1190
+ function acceptIdentity(current, incoming) {
1191
+ return typeof incoming === "string" && incoming.length > 0 ? incoming : current;
1192
+ }
1193
+ /** Assemble the final ContentBlock for one open block. */
1194
+ function closeBlock(block) {
1195
+ switch (block.kind) {
1196
+ case "text": return {
1197
+ type: "text",
1198
+ text: block.text
1199
+ };
1200
+ case "reasoning": return {
1201
+ type: "reasoning",
1202
+ text: block.text
1203
+ };
1204
+ case "tool-call": return {
1205
+ type: "tool-call",
1206
+ id: brandString(block.callId ?? ""),
1207
+ name: block.name ?? "",
1208
+ arguments: block.text
1209
+ };
1210
+ }
1211
+ }
1212
+ /**
1213
+ * Consume SSE data payloads (ending with `[DONE]`) and yield StreamChunks.
1214
+ * Malformed JSON payloads abort the stream with `MALFORMED_RESPONSE`.
1215
+ * @param payloads - SSE data payloads from {@link parseSse}, `[DONE]`-terminated.
1216
+ * @returns deltas as they arrive; `block-end`s, `usage`, and `finish` are all deferred to the `[DONE]` sentinel.
1217
+ * A `stop` (or absent) finish with no opened blocks is a degenerate provider completion and maps to an
1218
+ * `EMPTY_RESPONSE` error finish instead of a successful empty message.
1219
+ */
1220
+ async function* translate(payloads) {
1221
+ let nextIndex = 0;
1222
+ let textBlock;
1223
+ let reasoningBlock;
1224
+ const toolBlocks = /* @__PURE__ */ new Map();
1225
+ const order = [];
1226
+ let pendingFinish;
1227
+ let pendingUsage;
1228
+ function open(kind) {
1229
+ const block = {
1230
+ index: nextIndex++,
1231
+ kind,
1232
+ text: ""
1233
+ };
1234
+ order.push(block);
1235
+ return block;
1236
+ }
1237
+ for await (const payload of payloads) {
1238
+ if (payload === "[DONE]") {
1239
+ for (const block of order) yield {
1240
+ type: "block-end",
1241
+ index: block.index,
1242
+ block: closeBlock(block)
1243
+ };
1244
+ if (pendingUsage) yield {
1245
+ type: "usage",
1246
+ usage: pendingUsage
1247
+ };
1248
+ const reason = pendingFinish ?? { kind: "stop" };
1249
+ yield {
1250
+ type: "finish",
1251
+ reason: reason.kind === "stop" && order.length === 0 ? {
1252
+ kind: "error",
1253
+ failure: {
1254
+ message: "model returned a completed response with no content",
1255
+ code: EMPTY_RESPONSE_CODE
1256
+ }
1257
+ } : reason
1258
+ };
1259
+ return;
1260
+ }
1261
+ let chunk;
1262
+ try {
1263
+ chunk = JSON.parse(payload);
1264
+ } catch {
1265
+ throw new LlmError(`malformed SSE payload: ${payload.slice(0, 120)}`, "MALFORMED_RESPONSE");
1266
+ }
1267
+ for (const choice of chunk.choices ?? []) {
1268
+ const delta = choice.delta;
1269
+ const reasoning = delta?.reasoning_content;
1270
+ if (typeof reasoning === "string" && reasoning.length > 0) {
1271
+ if (!reasoningBlock) {
1272
+ reasoningBlock = open("reasoning");
1273
+ yield {
1274
+ type: "block-start",
1275
+ index: reasoningBlock.index,
1276
+ blockType: "reasoning"
1277
+ };
1278
+ }
1279
+ reasoningBlock.text += reasoning;
1280
+ yield {
1281
+ type: "reasoning-delta",
1282
+ index: reasoningBlock.index,
1283
+ text: reasoning
1284
+ };
1285
+ }
1286
+ const content = delta?.content;
1287
+ if (typeof content === "string" && content.length > 0) {
1288
+ if (!textBlock) {
1289
+ textBlock = open("text");
1290
+ yield {
1291
+ type: "block-start",
1292
+ index: textBlock.index,
1293
+ blockType: "text"
1294
+ };
1295
+ }
1296
+ textBlock.text += content;
1297
+ yield {
1298
+ type: "text-delta",
1299
+ index: textBlock.index,
1300
+ text: content
1301
+ };
1302
+ }
1303
+ for (const call of delta?.tool_calls ?? []) {
1304
+ let block = toolBlocks.get(call.index);
1305
+ if (!block) {
1306
+ block = open("tool-call");
1307
+ toolBlocks.set(call.index, block);
1308
+ yield {
1309
+ type: "block-start",
1310
+ index: block.index,
1311
+ blockType: "tool-call"
1312
+ };
1313
+ }
1314
+ block.callId = acceptIdentity(block.callId, call.id);
1315
+ block.name = acceptIdentity(block.name, call.function?.name);
1316
+ const fragment = call.function?.arguments ?? "";
1317
+ block.text += fragment;
1318
+ yield {
1319
+ type: "tool-call-delta",
1320
+ index: block.index,
1321
+ id: brandString(block.callId ?? ""),
1322
+ ...block.name !== void 0 ? { name: block.name } : {},
1323
+ argumentsDelta: fragment
1324
+ };
1325
+ }
1326
+ if (typeof choice.finish_reason === "string") pendingFinish = mapFinishReason(choice.finish_reason);
1327
+ }
1328
+ if (chunk.usage) pendingUsage = mapUsage(chunk.usage);
1329
+ }
1330
+ throw new LlmError("SSE payload stream ended without [DONE]", "STREAM_CLOSED");
1331
+ }
1332
+ //#endregion
1333
+ //#region lib/types/adapter.js
1334
+ /**
1335
+ * `DeepSeekAdapter`: fetch + SSE against a DeepSeek (OpenAI-compatible)
1336
+ * chat-completions endpoint, emitting harness StreamChunks. The adapter is
1337
+ * transport-only: connection facts arrive through a thunk resolved once per
1338
+ * operation and the bearer token through a per-request resolver, so the
1339
+ * registering plugin owns validation, layering, and credential policy.
1340
+ *
1341
+ * @module dsh-llm-deepseek/adapter
1342
+ */
1343
+ var __addDisposableResource = function(env, value, async) {
1344
+ if (value !== null && value !== void 0) {
1345
+ if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
1346
+ var dispose, inner;
1347
+ if (async) {
1348
+ if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
1349
+ dispose = value[Symbol.asyncDispose];
1350
+ }
1351
+ if (dispose === void 0) {
1352
+ if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
1353
+ dispose = value[Symbol.dispose];
1354
+ if (async) inner = dispose;
1355
+ }
1356
+ if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
1357
+ if (inner) dispose = function() {
1358
+ try {
1359
+ inner.call(this);
1360
+ } catch (e) {
1361
+ return Promise.reject(e);
1362
+ }
1363
+ };
1364
+ env.stack.push({
1365
+ value,
1366
+ dispose,
1367
+ async
1368
+ });
1369
+ } else if (async) env.stack.push({ async: true });
1370
+ return value;
1371
+ };
1372
+ var __disposeResources = (function(SuppressedError) {
1373
+ return function(env) {
1374
+ function fail(e) {
1375
+ env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
1376
+ env.hasError = true;
1377
+ }
1378
+ var r, s = 0;
1379
+ function next() {
1380
+ while (r = env.stack.pop()) try {
1381
+ if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
1382
+ if (r.dispose) {
1383
+ var result = r.dispose.call(r.value);
1384
+ if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) {
1385
+ fail(e);
1386
+ return next();
1387
+ });
1388
+ } else s |= 1;
1389
+ } catch (e) {
1390
+ fail(e);
1391
+ }
1392
+ if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
1393
+ if (env.hasError) throw env.error;
1394
+ }
1395
+ return next();
1396
+ };
1397
+ })(typeof SuppressedError === "function" ? SuppressedError : function(error, suppressed, message) {
1398
+ var e = new Error(message);
1399
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
1400
+ });
1401
+ /** Default maximum idle interval while an adapter stream read is outstanding. */
1402
+ const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 3e5;
1403
+ /** Default combined request/response context capacity. */
1404
+ const DEFAULT_CONTEXT_WINDOW = 1e6;
1405
+ /** Default per-request output-token cap. */
1406
+ const DEFAULT_MAX_TOKENS = 256e3;
1407
+ /** Default bound on accumulated base64 image payload after Files API fallback. */
1408
+ const DEFAULT_MAX_INLINE_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024;
1409
+ /** Deterministic raw-byte removal step. */
1410
+ const DEFAULT_IMAGE_OFFLOAD_BYTE_QUANTUM = 64 * 1024 * 1024;
1411
+ /** Deterministic base64-byte removal step after Files API fallback. */
1412
+ const DEFAULT_INLINE_IMAGE_OFFLOAD_BYTE_QUANTUM = 10 * 1024 * 1024;
1413
+ /** Deterministic image-count removal step. */
1414
+ const DEFAULT_IMAGE_OFFLOAD_COUNT_QUANTUM = 20;
1415
+ /** Default explicit lifetime for uploaded images. */
1416
+ const DEFAULT_FILE_EXPIRY_SECONDS = 10080 * 60;
1417
+ /** Default proactive refresh window for indexed file ids. */
1418
+ const DEFAULT_FILE_REFRESH_MARGIN_SECONDS = 3600;
1419
+ /** Default number of oldest harness-owned files removed on quota recovery. */
1420
+ const DEFAULT_FILE_QUOTA_CLEANUP_BATCH = 100;
1421
+ /** Default deadline for resolving one request image through the Files API. */
1422
+ const DEFAULT_FILES_API_TIMEOUT_MS = 6e4;
1423
+ const STREAM_IDLE_TIMEOUT_CODE = "LLM_STREAM_IDLE_TIMEOUT";
1424
+ const FILES_API_TIMEOUT_CODE = "DEEPSEEK_FILES_API_TIMEOUT";
1425
+ const OFF_REASONING_EFFORT = ReasoningEffortId("off");
1426
+ const LOW_REASONING_EFFORT = ReasoningEffortId("low");
1427
+ const HIGH_REASONING_EFFORT = ReasoningEffortId("high");
1428
+ const MAX_REASONING_EFFORT = ReasoningEffortId("max");
1429
+ const REASONING_EFFORTS = [
1430
+ { id: ReasoningEffortId("ultra"), name: "Ultra", description: "DSCODE: max reasoning plus deliberate subagent collaboration; higher total token use." },
1431
+ {
1432
+ id: OFF_REASONING_EFFORT,
1433
+ name: "Off",
1434
+ description: "Use for simple tasks that do not need reasoning."
1435
+ },
1436
+ {
1437
+ id: LOW_REASONING_EFFORT,
1438
+ name: "Low",
1439
+ description: "Prefer for routine or latency-sensitive tasks."
1440
+ },
1441
+ {
1442
+ id: HIGH_REASONING_EFFORT,
1443
+ name: "High",
1444
+ description: "The default balance for most tasks."
1445
+ },
1446
+ {
1447
+ id: MAX_REASONING_EFFORT,
1448
+ name: "Max",
1449
+ description: "Reserve for the hardest quality-first tasks."
1450
+ }
1451
+ ];
1452
+ const OFF_ONLY_REASONING_EFFORTS = [{
1453
+ id: OFF_REASONING_EFFORT,
1454
+ name: "Off",
1455
+ description: "Use for simple tasks that do not need reasoning."
1456
+ }];
1457
+ /** Marks a failed file-id resolution that may be retried as an inline request. */
1458
+ var FileResolutionFailure = class extends Error {
1459
+ constructor(cause) {
1460
+ super("DeepSeek Files API could not resolve a request image.", { cause });
1461
+ this.name = "FileResolutionFailure";
1462
+ }
1463
+ };
1464
+ function collectImageRefs(content, refs) {
1465
+ for (const block of content) if (block.type === "image") refs.set(block.attachment.attachmentId, block.attachment);
1466
+ else if (block.type === "tool-result") collectImageRefs(block.content, refs);
1467
+ }
1468
+ async function prepareRequestImages(options, attachments, model, signal) {
1469
+ const refs = /* @__PURE__ */ new Map();
1470
+ for (const message of options.messages) collectImageRefs(message.content, refs);
1471
+ const policy = resolveRequestImagePolicy(model);
1472
+ const orderedRefs = [...refs.values()];
1473
+ const projected = await Promise.all(orderedRefs.map((ref) => attachments.readImageRequest(ref, policy, signal)));
1474
+ return new Map(orderedRefs.map((ref, index) => [ref.attachmentId, projected[index]]));
1475
+ }
1476
+ function providerRejectedNormalizedImage(detail) {
1477
+ return /(?:unsupported|invalid|cannot read|failed to (?:decode|process)).{0,40}image/iu.test(detail) || /image.{0,40}(?:unsupported|invalid|cannot be decoded)/iu.test(detail);
1478
+ }
1479
+ function providerRejectedFileId(detail) {
1480
+ const file = /\bfile(?:[_ -]?(?:id|api|not[_ -]?found|deleted|expired))?/iu.test(detail);
1481
+ const missing = /(?:expired|not[_ -]?found|deleted|do(?:es)? not exist|not created under (?:this|your) account)/iu.test(detail);
1482
+ const invalidId = /(?:invalid.{0,20}file[_ -]?(?:id|api)|file[_ -]?(?:id|api).{0,20}invalid)/iu.test(detail);
1483
+ return file && (missing || invalidId);
1484
+ }
1485
+ function detailNamesFileId(detail, fileId) {
1486
+ let index = detail.indexOf(fileId);
1487
+ while (index >= 0) {
1488
+ const before = detail[index - 1];
1489
+ const after = detail[index + fileId.length];
1490
+ if ((before === void 0 || !/[\p{L}\p{N}_-]/u.test(before)) && (after === void 0 || !/[\p{L}\p{N}_-]/u.test(after))) return true;
1491
+ index = detail.indexOf(fileId, index + 1);
1492
+ }
1493
+ return false;
1494
+ }
1495
+ function staleMappings(files, detail) {
1496
+ const unique = [...new Map(files.map((file) => [`${file.version.variantId}\0${file.fileId}`, file])).values()];
1497
+ const exact = unique.filter((file) => detailNamesFileId(detail, file.fileId));
1498
+ return exact.length > 0 ? exact : unique;
1499
+ }
1500
+ function normalizedImageFacts(file) {
1501
+ const version = file.version;
1502
+ const name = version.attachment.name ?? version.attachment.attachmentId;
1503
+ const colour = version.hasAlpha ? "sRGBA" : "sRGB";
1504
+ return `"${name}" at message ${file.location.message}, image ${file.location.image} (${version.mediaType}, 8-bit ${colour}, ${version.width}x${version.height})`;
1505
+ }
1506
+ function normalizedImageDiagnostic(files, providerMessage, providerDetail) {
1507
+ const target = files.find((file) => detailNamesFileId(providerDetail, file.fileId)) ?? (files.length === 1 ? files[0] : void 0);
1508
+ if (target !== void 0) return `DeepSeek rejected normalized image ${normalizedImageFacts(target)}: ${providerMessage}. The provider rejected bytes already normalized by the harness; PNG, JPEG, WebP, and GIF remain supported input formats.`;
1509
+ return `DeepSeek rejected a normalized request image: ${providerMessage}. Candidate images: ${[...new Map(files.map((file) => [`${file.version.variantId}\0${file.location.message}\0${file.location.image}`, file])).values()].map(normalizedImageFacts).join("; ")}. The provider rejected bytes already normalized by the harness; PNG, JPEG, WebP, and GIF remain supported input formats.`;
1510
+ }
1511
+ function modelInfo(provider, model) {
1512
+ return {
1513
+ provider,
1514
+ id: model.id,
1515
+ name: model.name ?? model.id,
1516
+ ...model.description === void 0 ? {} : { description: model.description },
1517
+ inputModalities: model.inputModalities ?? ["text"]
1518
+ };
1519
+ }
1520
+ function providerRetryAfterMs(value) {
1521
+ if (value === null) return void 0;
1522
+ if (/^\d+$/.test(value)) {
1523
+ const delay = Number(value) * 1e3;
1524
+ return Number.isFinite(delay) && delay > 0 ? delay : void 0;
1525
+ }
1526
+ const delay = Date.parse(value) - Date.now();
1527
+ return Number.isFinite(delay) && delay > 0 ? delay : void 0;
1528
+ }
1529
+ function requestId(headers) {
1530
+ const value = headers.get("x-request-id") ?? headers.get("x-deepseek-request-id");
1531
+ return value === null || value.length === 0 ? void 0 : ProviderRequestId(value);
1532
+ }
1533
+ /**
1534
+ * Map an HTTP status to a stable LlmError code.
1535
+ * @param status - status of a non-2xx provider response.
1536
+ * @param error - parsed provider error body, when available.
1537
+ * @returns the normalized harness error code.
1538
+ */
1539
+ function httpErrorCode(status, error) {
1540
+ if (status === 401 || status === 403) return "AUTH";
1541
+ if (status === 413) return "INVALID_REQUEST";
1542
+ const detail = [
1543
+ error?.code,
1544
+ error?.type,
1545
+ error?.message
1546
+ ].filter(Boolean).join(" ");
1547
+ if (isQuotaExceededError(detail)) return QUOTA_EXCEEDED_CODE;
1548
+ if (status === 429) return "RATE_LIMIT";
1549
+ if (status === 400) {
1550
+ if (isContextWindowExceededError(detail)) return CONTEXT_WINDOW_EXCEEDED_CODE;
1551
+ return "INVALID_REQUEST";
1552
+ }
1553
+ if (status >= 500) return "SERVER";
1554
+ return `HTTP_${status}`;
1555
+ }
1556
+ /**
1557
+ * The first real `LlmAdapter`. One instance serves every model name it was
1558
+ * registered under (the harness model name IS the wire model name).
1559
+ *
1560
+ * One stable signal reaches both initial fetch and body reads. Caller aborts
1561
+ * map to `ABORTED`; the configured per-read idle watchdog maps to `TIMEOUT`.
1562
+ */
1563
+ var DeepSeekAdapter = class extends LlmAdapter {
1564
+ config;
1565
+ files;
1566
+ constructor(config) {
1567
+ super();
1568
+ this.config = config;
1569
+ this.files = config.resolveFiles?.() ?? new DeepSeekFileStore();
1570
+ }
1571
+ providerInfo(provider) {
1572
+ return {
1573
+ id: provider,
1574
+ name: "DeepSeek"
1575
+ };
1576
+ }
1577
+ providerRetryPolicy(_provider) {
1578
+ return this.config.options().retryPolicy;
1579
+ }
1580
+ imageRequestPricing(_provider, model) {
1581
+ const attachments = this.config.resolveAttachments?.();
1582
+ const resolveAccess = attachments === void 0 ? void 0 : (ref) => this.config.resolveImageAccess?.(attachments, ref);
1583
+ return deepSeekImageRequestPricing(this.config.options(), model, resolveAccess);
1584
+ }
1585
+ listModels(provider) {
1586
+ return Promise.resolve(this.config.options().models.map((model) => modelInfo(provider, model)));
1587
+ }
1588
+ resolveModel(provider, model, _signal) {
1589
+ return Promise.resolve(this.modelInfoFor(this.config.options(), provider, model));
1590
+ }
1591
+ modelInfoFor(connection, provider, model) {
1592
+ const configured = connection.models.find((entry) => entry.id === model);
1593
+ const contextWindow = configured?.contextWindow ?? connection.defaultContextWindow;
1594
+ return {
1595
+ ...configured === void 0 ? {
1596
+ provider,
1597
+ id: model,
1598
+ name: model,
1599
+ inputModalities: ["text"]
1600
+ } : modelInfo(provider, configured),
1601
+ context: { contextWindow },
1602
+ defaultMaxTokens: configured?.maxTokens ?? connection.maxTokens,
1603
+ ...configured?.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: configured.systemPromptUpdate },
1604
+ ...connection.defaults.thinking === "disabled" ? { reasoning: {
1605
+ efforts: OFF_ONLY_REASONING_EFFORTS,
1606
+ defaultEffort: OFF_REASONING_EFFORT
1607
+ } } : { reasoning: {
1608
+ efforts: REASONING_EFFORTS,
1609
+ defaultEffort: connection.defaults.reasoningEffort === "off" ? OFF_REASONING_EFFORT : connection.defaults.reasoningEffort === "low" ? LOW_REASONING_EFFORT : connection.defaults.reasoningEffort === "max" ? MAX_REASONING_EFFORT : HIGH_REASONING_EFFORT
1610
+ } }
1611
+ };
1612
+ }
1613
+ prepareCall(provider, model, _signal) {
1614
+ const connection = this.config.options();
1615
+ return Promise.resolve({
1616
+ model: this.modelInfoFor(connection, provider, model),
1617
+ stream: (options) => this.streamWithConnection(options, connection)
1618
+ });
1619
+ }
1620
+ stream(options) {
1621
+ return this.streamWithConnection(options, this.config.options());
1622
+ }
1623
+ async *streamWithConnection(options, connection) {
1624
+ const env_1 = {
1625
+ stack: [],
1626
+ error: void 0,
1627
+ hasError: false
1628
+ };
1629
+ try {
1630
+ const hasImages = options.messages.some((message) => contentHasImage(message.content));
1631
+ let attachments;
1632
+ if (hasImages) {
1633
+ if (connection.models.find((entry) => entry.id === options.model)?.inputModalities?.includes("image") !== true) throw new LlmError(`DeepSeek model "${options.model}" does not accept image input.`, "UNSUPPORTED_CONTENT");
1634
+ attachments = this.config.resolveAttachments?.();
1635
+ if (attachments === void 0) throw new LlmError("DeepSeek image conversion requires the durable attachment service.", "UNSUPPORTED_CONTENT");
1636
+ }
1637
+ const apiKey = await this.config.resolveApiKey(connection);
1638
+ const userId = this.config.resolveUserId();
1639
+ const consumer = new AbortController();
1640
+ const watchdog = __addDisposableResource(env_1, idleWatchdog(options.signal === void 0 ? consumer.signal : AbortSignal.any([options.signal, consumer.signal]), connection.streamIdleTimeoutMs, STREAM_IDLE_TIMEOUT_CODE), false);
1641
+ const iterator = this.request(options, watchdog.signal, connection, apiKey, userId, attachments, () => {
1642
+ watchdog.pulse();
1643
+ })[Symbol.asyncIterator]();
1644
+ let exhausted = false;
1645
+ try {
1646
+ while (true) {
1647
+ const result = await watchdog.next(iterator);
1648
+ if (result.done) {
1649
+ exhausted = true;
1650
+ return;
1651
+ }
1652
+ yield result.value;
1653
+ }
1654
+ } catch (error) {
1655
+ if (timeoutOf(watchdog.signal, STREAM_IDLE_TIMEOUT_CODE) !== void 0) throw new LlmError(`DeepSeek stream idle timeout after ${connection.streamIdleTimeoutMs}ms`, "TIMEOUT", { cause: error });
1656
+ if (options.signal?.aborted) throw new LlmError("DeepSeek request aborted by caller", "ABORTED", { cause: error });
1657
+ if (error instanceof LlmError) throw error;
1658
+ throw new LlmError(`DeepSeek API stream from ${connection.baseURL} failed`, "TRANSPORT", { cause: error });
1659
+ } finally {
1660
+ consumer.abort("DeepSeek stream consumer stopped");
1661
+ if (!exhausted && iterator.return !== void 0) try {
1662
+ await iterator.return();
1663
+ } catch (_abortedTransportTeardown) {}
1664
+ }
1665
+ } catch (e_1) {
1666
+ env_1.error = e_1;
1667
+ env_1.hasError = true;
1668
+ } finally {
1669
+ __disposeResources(env_1);
1670
+ }
1671
+ }
1672
+ async *request(options, signal, connection, apiKey, userId, attachments, onActivity) {
1673
+ const headers = {
1674
+ "authorization": `Bearer ${apiKey}`,
1675
+ "content-type": "application/json",
1676
+ "accept": "text/event-stream",
1677
+ ...attributionHeaders(),
1678
+ "x-deepseek-harness-user-id": String(userId),
1679
+ ...options.sessionId !== void 0 ? { "x-deepseek-harness-session-id": String(options.sessionId) } : {},
1680
+ ...options.purpose === "compaction" ? { "x-deepseek-harness-compact": "1" } : {}
1681
+ };
1682
+ const fileConnection = {
1683
+ baseURL: connection.baseURL,
1684
+ apiKey
1685
+ };
1686
+ const model = connection.models.find((entry) => entry.id === options.model);
1687
+ const policy = model === void 0 ? void 0 : resolveRequestImagePolicy(model);
1688
+ const resolveImageAccess = attachments === void 0 ? void 0 : (ref) => this.config.resolveImageAccess?.(attachments, ref);
1689
+ const imageAccessOptions = resolveImageAccess === void 0 ? {} : { resolveImageAccess };
1690
+ const requestMessages = policy === void 0 ? options.messages : offloadRequestImagesWithPolicy(options.messages, {
1691
+ representation: "raw",
1692
+ maxBytes: connection.maxRequestFilesBytes,
1693
+ maxImages: connection.maxImagesPerRequest,
1694
+ byteQuantum: connection.imageOffloadByteQuantum,
1695
+ countQuantum: connection.imageOffloadCountQuantum,
1696
+ byteLength: (ref) => Math.min(ref.bytes, policy.maxBytes),
1697
+ placeholder: (ref) => offloadedImageText(ref, resolveImageAccess?.(ref))
1698
+ });
1699
+ const requestOptions = requestMessages === options.messages ? options : {
1700
+ ...options,
1701
+ messages: [...requestMessages]
1702
+ };
1703
+ const requestImages = attachments === void 0 || model === void 0 ? /* @__PURE__ */ new Map() : await prepareRequestImages(requestOptions, attachments, model, signal);
1704
+ let representation = "file";
1705
+ let fileAttempt = 0;
1706
+ while (true) {
1707
+ const usedFiles = [];
1708
+ let body;
1709
+ if (attachments === void 0) body = serializeRequest(requestOptions, connection.defaults);
1710
+ else if (representation === "base64") body = await serializeRequestWithImages(requestOptions, {
1711
+ representation: { kind: "base64" },
1712
+ requestImages,
1713
+ ...imageAccessOptions,
1714
+ maxRequestImageBytes: connection.maxInlineRequestImageBytes,
1715
+ maxImagesPerRequest: connection.maxImagesPerRequest,
1716
+ byteQuantum: connection.inlineImageOffloadByteQuantum,
1717
+ countQuantum: connection.imageOffloadCountQuantum
1718
+ }, connection.defaults);
1719
+ else try {
1720
+ body = await serializeRequestWithImages(requestOptions, {
1721
+ representation: {
1722
+ kind: "file",
1723
+ resolveFileId: async (version, _block, location) => {
1724
+ const env_2 = {
1725
+ stack: [],
1726
+ error: void 0,
1727
+ hasError: false
1728
+ };
1729
+ try {
1730
+ const filesDeadline = __addDisposableResource(env_2, deadline(signal, connection.filesApiTimeoutMs, FILES_API_TIMEOUT_CODE), false);
1731
+ let resolved;
1732
+ try {
1733
+ resolved = await this.files.ensureUploaded(version, fileConnection, connection.filePolicy, filesDeadline.signal);
1734
+ } catch (error) {
1735
+ if (signal.aborted) throw error;
1736
+ throw new FileResolutionFailure(error);
1737
+ }
1738
+ onActivity();
1739
+ usedFiles.push({
1740
+ version,
1741
+ fileId: resolved.record.fileId,
1742
+ location
1743
+ });
1744
+ return resolved.record.fileId;
1745
+ } catch (e_2) {
1746
+ env_2.error = e_2;
1747
+ env_2.hasError = true;
1748
+ } finally {
1749
+ __disposeResources(env_2);
1750
+ }
1751
+ }
1752
+ },
1753
+ requestImages,
1754
+ ...imageAccessOptions,
1755
+ maxRequestImageBytes: connection.maxRequestFilesBytes,
1756
+ maxImagesPerRequest: connection.maxImagesPerRequest,
1757
+ byteQuantum: connection.imageOffloadByteQuantum,
1758
+ countQuantum: connection.imageOffloadCountQuantum
1759
+ }, connection.defaults);
1760
+ } catch (error) {
1761
+ if (!(error instanceof FileResolutionFailure)) throw error;
1762
+ representation = "base64";
1763
+ continue;
1764
+ }
1765
+ let extensions;
1766
+ try {
1767
+ extensions = await this.config.prepareExtensions({
1768
+ body,
1769
+ signal,
1770
+ ...options.sessionId === void 0 ? {} : { sessionId: String(options.sessionId) },
1771
+ ...options.purpose === void 0 ? {} : { purpose: options.purpose }
1772
+ });
1773
+ } catch (error) {
1774
+ throw new LlmError("DeepSeek request extension preparation failed", "REQUEST_EXTENSION", { cause: error });
1775
+ }
1776
+ for (const field of Object.keys(extensions.fields)) if (Object.hasOwn(body, field)) throw new LlmError(`DeepSeek request extension field ${JSON.stringify(field)} collides with the base request`, "REQUEST_EXTENSION");
1777
+ const payload = JSON.stringify({
1778
+ ...body,
1779
+ ...extensions.fields
1780
+ });
1781
+ let response;
1782
+ try {
1783
+ response = await fetch(`${connection.baseURL}/chat/completions`, {
1784
+ method: "POST",
1785
+ headers,
1786
+ body: payload,
1787
+ signal
1788
+ });
1789
+ } catch (error) {
1790
+ if (signal.aborted) throw error;
1791
+ throw new LlmError(`DeepSeek API request to ${connection.baseURL} failed`, "TRANSPORT", { cause: error });
1792
+ }
1793
+ if (!response.ok) {
1794
+ let message = `DeepSeek API error (HTTP ${response.status})`;
1795
+ let providerError;
1796
+ const rawResponse = await response.text();
1797
+ try {
1798
+ providerError = JSON.parse(rawResponse).error;
1799
+ if (providerError?.message) message = providerError.message;
1800
+ } catch {}
1801
+ const detail = [
1802
+ providerError?.code,
1803
+ providerError?.type,
1804
+ providerError?.message
1805
+ ].filter((field) => typeof field === "string").join(" ");
1806
+ if (usedFiles.length > 0 && providerRejectedFileId(detail)) {
1807
+ await Promise.all(staleMappings(usedFiles, detail).map((file) => this.files.invalidate(file.version, file.fileId, fileConnection)));
1808
+ if (fileAttempt === 0) {
1809
+ fileAttempt += 1;
1810
+ continue;
1811
+ }
1812
+ }
1813
+ if (response.status === 400 && usedFiles.length > 0 && providerRejectedNormalizedImage(detail)) message = normalizedImageDiagnostic(usedFiles, message, detail);
1814
+ const delay = providerRetryAfterMs(response.headers.get("retry-after"));
1815
+ const id = requestId(response.headers);
1816
+ throw new LlmError(message, httpErrorCode(response.status, providerError), {
1817
+ cause: new Error(rawResponse.length > 0 ? rawResponse : `DeepSeek HTTP ${response.status}`),
1818
+ status: response.status,
1819
+ ...delay === void 0 ? {} : { providerRetryAfterMs: delay },
1820
+ ...id === void 0 ? {} : { requestId: id }
1821
+ });
1822
+ }
1823
+ try {
1824
+ await extensions.accept();
1825
+ } catch (error) {
1826
+ throw new LlmError("DeepSeek request extension acceptance failed", "REQUEST_EXTENSION", { cause: error });
1827
+ }
1828
+ if (!response.body) throw new LlmError("DeepSeek API returned no response body", "EMPTY_RESPONSE");
1829
+ yield* translate(parseSse(response.body, onActivity));
1830
+ return;
1831
+ }
1832
+ }
1833
+ };
1834
+ //#endregion
1835
+ //#region lib/types/index.js
1836
+ /**
1837
+ * Register a {@link DeepSeekAdapter} for the `deepseek-official` provider route on
1838
+ * `ctx.llm`, with connection facts resolved per request instead of frozen at
1839
+ * load: the plugin layers its `cordis.yml` entry config under the optional
1840
+ * `llm-deepseek` user-settings section (`ctx.settings`) and resolves the API
1841
+ * key through the optional credential seam (`ctx.credentials`), so a changed
1842
+ * base URL, catalog, or key reaches the very next request without restarting
1843
+ * anything, while an in-flight stream keeps the facts it started with. The
1844
+ * one registration-captured fact — the retry policy — re-registers the route
1845
+ * in place when it changes.
1846
+ * @module @deepseek-ai/dsh-llm-deepseek
1847
+ */
1848
+ const name = "llm-deepseek";
1849
+ const inject = ["llm"];
1850
+ const NS = "llm-deepseek";
1851
+ const DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
1852
+ /** The single provider route this plugin owns. */
1853
+ const PROVIDER = "deepseek-official";
1854
+ const DEFAULT_MODELS = [
1855
+ {
1856
+ id: "deepseek-flash",
1857
+ name: "DeepSeek-V41-Flash",
1858
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
1859
+ inputModalities: ["text", "image"],
1860
+ imagePixelBudget: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
1861
+ imageMaxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES,
1862
+ systemPromptUpdate: "in-history"
1863
+ },
1864
+ {
1865
+ id: "deepseek-v4-flash",
1866
+ name: "DeepSeek-V4-Flash",
1867
+ description: "Fast, efficient, and economical; suited to focused, routine, or parallel tasks.",
1868
+ contextWindow: DEFAULT_CONTEXT_WINDOW
1869
+ },
1870
+ {
1871
+ id: "deepseek-v4-pro",
1872
+ name: "DeepSeek-V4-Pro",
1873
+ description: "Stronger agentic coding, knowledge, and difficult reasoning; suited to complex or quality-critical tasks at higher cost.",
1874
+ contextWindow: DEFAULT_CONTEXT_WINDOW
1875
+ },
1876
+ {
1877
+ id: "deepseek-v4-flash-vision-exp",
1878
+ name: "DeepSeek-V4-Flash-Vision-Exp",
1879
+ contextWindow: DEFAULT_CONTEXT_WINDOW,
1880
+ inputModalities: ["text", "image"],
1881
+ imagePixelBudget: DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET,
1882
+ imageMaxBytes: DEFAULT_REQUEST_IMAGE_MAX_BYTES
1883
+ }
1884
+ ];
1885
+ const MODEL_MODALITIES = ["text", "image"];
1886
+ const catalogModel = z.object({
1887
+ id: z.string().required(),
1888
+ name: z.string(),
1889
+ description: z.string(),
1890
+ contextWindow: z.number().step(1).min(1),
1891
+ maxTokens: z.number().step(1).min(1),
1892
+ inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default(["text"]),
1893
+ imagePixelBudget: z.union([z.number().step(1).min(1), "low"]),
1894
+ imageMaxBytes: z.number().step(1).min(1),
1895
+ systemPromptUpdate: z.const("in-history")
1896
+ });
1897
+ const Config = z.object({
1898
+ apiKeyEnv: z.string().role("credential-ref").default(DEFAULT_API_KEY_ENV),
1899
+ baseURL: z.string(),
1900
+ thinking: z.union(["enabled", "disabled"]),
1901
+ reasoningEffort: z.union([
1902
+ "off",
1903
+ "low",
1904
+ "high",
1905
+ "max"
1906
+ ]),
1907
+ maxTokens: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_TOKENS),
1908
+ defaultContextWindow: z.number().step(1).min(1).default(DEFAULT_CONTEXT_WINDOW),
1909
+ models: z.array(catalogModel).default(DEFAULT_MODELS),
1910
+ streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
1911
+ maxRequestFilesBytes: z.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_FILES_BYTES),
1912
+ maxInlineRequestImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_INLINE_REQUEST_IMAGE_BYTES),
1913
+ maxImagesPerRequest: z.number().step(1).min(1).default(600),
1914
+ imageOffloadByteQuantum: z.number().step(1).min(1).default(DEFAULT_IMAGE_OFFLOAD_BYTE_QUANTUM),
1915
+ inlineImageOffloadByteQuantum: z.number().step(1).min(1).default(DEFAULT_INLINE_IMAGE_OFFLOAD_BYTE_QUANTUM),
1916
+ imageOffloadCountQuantum: z.number().step(1).min(1).default(20),
1917
+ filesApiTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_FILES_API_TIMEOUT_MS),
1918
+ fileExpiresAfterSeconds: z.number().step(1).min(3600).max(2592e3).default(DEFAULT_FILE_EXPIRY_SECONDS),
1919
+ fileRefreshMarginSeconds: z.number().step(1).min(0).default(DEFAULT_FILE_REFRESH_MARGIN_SECONDS),
1920
+ fileQuotaCleanupBatch: z.number().step(1).min(1).max(1e3).default(100),
1921
+ retryPolicy: RetryPolicySchema
1922
+ });
1923
+ /** Public API default; the internal endpoint comes from $DEEPSEEK_BASE_URL. */
1924
+ const PUBLIC_BASE_URL = "https://api.deepseek.com";
1925
+ /** Environment variable naming this provider's endpoint, honored only from trusted layers. */
1926
+ const BASE_URL_ENV = "DEEPSEEK_BASE_URL";
1927
+ /** Resolve, validate, and detach the advisory model catalog. */
1928
+ function resolveModels(models) {
1929
+ const seen = /* @__PURE__ */ new Set();
1930
+ return (models ?? DEFAULT_MODELS).map((model) => {
1931
+ if (Object.hasOwn(model, "imageDetail")) throw new Error("llm-deepseek: catalog model imageDetail is no longer supported; use imagePixelBudget");
1932
+ if (model.id.length === 0) throw new Error("llm-deepseek: catalog model ids must be non-empty");
1933
+ if (model.name !== void 0 && model.name.length === 0) throw new Error(`llm-deepseek: catalog model "${model.id}" has an empty name`);
1934
+ if (model.contextWindow !== void 0 && (!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)) throw new Error(`llm-deepseek: catalog model "${model.id}" contextWindow must be a positive integer`);
1935
+ if (model.maxTokens !== void 0 && (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) throw new Error(`llm-deepseek: catalog model "${model.id}" maxTokens must be a positive integer`);
1936
+ const inputModalities = model.inputModalities ?? ["text"];
1937
+ if (inputModalities.length === 0) throw new Error(`llm-deepseek: catalog model "${model.id}" inputModalities must not be empty`);
1938
+ if (inputModalities.some((modality) => !MODEL_MODALITIES.includes(modality))) throw new Error(`llm-deepseek: catalog model "${model.id}" inputModalities must contain only "text" and "image"`);
1939
+ if (new Set(inputModalities).size !== inputModalities.length) throw new Error(`llm-deepseek: catalog model "${model.id}" inputModalities must not contain duplicates`);
1940
+ const hasImage = inputModalities.includes("image");
1941
+ if (!hasImage && (model.imagePixelBudget !== void 0 || model.imageMaxBytes !== void 0)) throw new Error(`llm-deepseek: text-only catalog model "${model.id}" cannot declare image request limits`);
1942
+ if (model.imagePixelBudget !== void 0 && model.imagePixelBudget !== "low" && (!Number.isSafeInteger(model.imagePixelBudget) || model.imagePixelBudget <= 0)) throw new Error(`llm-deepseek: catalog model "${model.id}" imagePixelBudget must be "low" or a positive safe integer`);
1943
+ if (model.imageMaxBytes !== void 0 && (!Number.isSafeInteger(model.imageMaxBytes) || model.imageMaxBytes <= 0)) throw new Error(`llm-deepseek: catalog model "${model.id}" imageMaxBytes must be a positive safe integer`);
1944
+ const systemPromptUpdate = model.systemPromptUpdate;
1945
+ if (systemPromptUpdate !== void 0 && systemPromptUpdate !== "in-history") throw new Error(`llm-deepseek: catalog model "${model.id}" systemPromptUpdate must be "in-history" when present`);
1946
+ if (seen.has(model.id)) throw new Error(`llm-deepseek: duplicate catalog model "${model.id}"`);
1947
+ seen.add(model.id);
1948
+ return {
1949
+ id: model.id,
1950
+ ...model.name === void 0 ? {} : { name: model.name },
1951
+ ...model.description === void 0 ? {} : { description: model.description },
1952
+ ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
1953
+ ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
1954
+ ...model.systemPromptUpdate === void 0 ? {} : { systemPromptUpdate: model.systemPromptUpdate },
1955
+ inputModalities: [...inputModalities],
1956
+ ...hasImage ? {
1957
+ imagePixelBudget: model.imagePixelBudget === "low" ? DEFAULT_LOW_DETAIL_IMAGE_PIXEL_BUDGET : model.imagePixelBudget ?? 64e4,
1958
+ imageMaxBytes: model.imageMaxBytes ?? 1048576
1959
+ } : {}
1960
+ };
1961
+ });
1962
+ }
1963
+ /**
1964
+ * The one explicit resolve step from raw config to validated connection
1965
+ * facts. Programmatic construction may bypass Schemastery normalization, so
1966
+ * every default and bound is re-judged here — for the composition entry at
1967
+ * load (fail loud) and for each settings snapshot at its first use.
1968
+ * @param config - raw plugin config or resolved settings snapshot.
1969
+ * @param environment - this run's environment layers, or `undefined` outside
1970
+ * the product CLI. Every layer may supply an endpoint: the product trusts the
1971
+ * project it is launched in, so a checkout can point its own agent at the
1972
+ * gateway that checkout is meant to use.
1973
+ * @returns validated connection facts plus the credential reference.
1974
+ */
1975
+ function resolveAdapterOptions(config, environment) {
1976
+ if (config.thinking === "disabled" && config.reasoningEffort !== void 0 && config.reasoningEffort !== "off") throw new Error("llm-deepseek: only reasoningEffort \"off\" can be configured when thinking is disabled");
1977
+ if (config.defaultContextWindow !== void 0 && (!Number.isInteger(config.defaultContextWindow) || config.defaultContextWindow <= 0)) throw new Error("llm-deepseek: defaultContextWindow must be a positive integer");
1978
+ if (config.maxTokens !== void 0 && (!Number.isSafeInteger(config.maxTokens) || config.maxTokens <= 0)) throw new Error("llm-deepseek: maxTokens must be a positive safe integer");
1979
+ const streamIdleTimeoutMs = config.streamIdleTimeoutMs ?? 3e5;
1980
+ if (!Number.isFinite(streamIdleTimeoutMs) || streamIdleTimeoutMs <= 0 || streamIdleTimeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`llm-deepseek: streamIdleTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
1981
+ const maxRequestFilesBytes = config.maxRequestFilesBytes ?? 134217728;
1982
+ if (!Number.isSafeInteger(maxRequestFilesBytes) || maxRequestFilesBytes <= 0) throw new Error("llm-deepseek: maxRequestFilesBytes must be a positive safe integer");
1983
+ const maxInlineRequestImageBytes = config.maxInlineRequestImageBytes ?? 20971520;
1984
+ if (!Number.isSafeInteger(maxInlineRequestImageBytes) || maxInlineRequestImageBytes <= 0) throw new Error("llm-deepseek: maxInlineRequestImageBytes must be a positive safe integer");
1985
+ const maxImagesPerRequest = config.maxImagesPerRequest ?? 600;
1986
+ if (!Number.isSafeInteger(maxImagesPerRequest) || maxImagesPerRequest <= 0) throw new Error("llm-deepseek: maxImagesPerRequest must be a positive safe integer");
1987
+ const imageOffloadByteQuantum = config.imageOffloadByteQuantum ?? 67108864;
1988
+ if (!Number.isSafeInteger(imageOffloadByteQuantum) || imageOffloadByteQuantum <= 0) throw new Error("llm-deepseek: imageOffloadByteQuantum must be a positive safe integer");
1989
+ if (imageOffloadByteQuantum > maxRequestFilesBytes) throw new Error("llm-deepseek: imageOffloadByteQuantum must not exceed maxRequestFilesBytes");
1990
+ const inlineImageOffloadByteQuantum = config.inlineImageOffloadByteQuantum ?? 10485760;
1991
+ if (!Number.isSafeInteger(inlineImageOffloadByteQuantum) || inlineImageOffloadByteQuantum <= 0) throw new Error("llm-deepseek: inlineImageOffloadByteQuantum must be a positive safe integer");
1992
+ if (inlineImageOffloadByteQuantum > maxInlineRequestImageBytes) throw new Error("llm-deepseek: inlineImageOffloadByteQuantum must not exceed maxInlineRequestImageBytes");
1993
+ const imageOffloadCountQuantum = config.imageOffloadCountQuantum ?? 20;
1994
+ if (!Number.isSafeInteger(imageOffloadCountQuantum) || imageOffloadCountQuantum <= 0) throw new Error("llm-deepseek: imageOffloadCountQuantum must be a positive safe integer");
1995
+ if (imageOffloadCountQuantum > maxImagesPerRequest) throw new Error("llm-deepseek: imageOffloadCountQuantum must not exceed maxImagesPerRequest");
1996
+ const filesApiTimeoutMs = config.filesApiTimeoutMs ?? 6e4;
1997
+ if (!Number.isFinite(filesApiTimeoutMs) || filesApiTimeoutMs <= 0 || filesApiTimeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`llm-deepseek: filesApiTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
1998
+ const fileExpiresAfterSeconds = config.fileExpiresAfterSeconds ?? 604800;
1999
+ if (!Number.isSafeInteger(fileExpiresAfterSeconds) || fileExpiresAfterSeconds < 3600 || fileExpiresAfterSeconds > 2592e3) throw new Error("llm-deepseek: fileExpiresAfterSeconds must be an integer from 3600 through 2592000");
2000
+ const fileRefreshMarginSeconds = config.fileRefreshMarginSeconds ?? 3600;
2001
+ if (!Number.isSafeInteger(fileRefreshMarginSeconds) || fileRefreshMarginSeconds < 0 || fileRefreshMarginSeconds >= fileExpiresAfterSeconds) throw new Error("llm-deepseek: fileRefreshMarginSeconds must be a non-negative integer below fileExpiresAfterSeconds");
2002
+ const fileQuotaCleanupBatch = config.fileQuotaCleanupBatch ?? 100;
2003
+ if (!Number.isSafeInteger(fileQuotaCleanupBatch) || fileQuotaCleanupBatch < 1 || fileQuotaCleanupBatch > 1e3) throw new Error("llm-deepseek: fileQuotaCleanupBatch must be an integer from 1 through 1000");
2004
+ return {
2005
+ apiKeyEnv: credentialRef(config.apiKeyEnv ?? DEFAULT_API_KEY_ENV),
2006
+ baseURL: config.baseURL ?? environment?.get(BASE_URL_ENV)?.value ?? "https://api.deepseek.com",
2007
+ defaults: {
2008
+ thinking: config.thinking,
2009
+ reasoningEffort: config.reasoningEffort
2010
+ },
2011
+ maxTokens: config.maxTokens ?? 256e3,
2012
+ defaultContextWindow: config.defaultContextWindow ?? 1e6,
2013
+ models: resolveModels(config.models),
2014
+ streamIdleTimeoutMs,
2015
+ maxRequestFilesBytes,
2016
+ maxInlineRequestImageBytes,
2017
+ maxImagesPerRequest,
2018
+ imageOffloadByteQuantum,
2019
+ inlineImageOffloadByteQuantum,
2020
+ imageOffloadCountQuantum,
2021
+ filesApiTimeoutMs,
2022
+ filePolicy: {
2023
+ expiresAfterSeconds: fileExpiresAfterSeconds,
2024
+ refreshMarginSeconds: fileRefreshMarginSeconds,
2025
+ quotaCleanupBatch: fileQuotaCleanupBatch
2026
+ },
2027
+ retryPolicy: resolveRetryPolicy(config.retryPolicy, "llm-deepseek: retryPolicy")
2028
+ };
2029
+ }
2030
+ function apply(ctx, config) {
2031
+ let current = () => config;
2032
+ let lastRaw;
2033
+ let lastGood;
2034
+ const options = () => {
2035
+ const raw = current();
2036
+ if (raw === lastRaw && lastGood !== void 0) return lastGood;
2037
+ try {
2038
+ const next = resolveAdapterOptions(raw, launchEnvironmentOf(ctx));
2039
+ lastRaw = raw;
2040
+ lastGood = next;
2041
+ return next;
2042
+ } catch (error) {
2043
+ if (lastGood === void 0) throw error;
2044
+ lastRaw = raw;
2045
+ ctx.logger.error("llm-deepseek: keeping the last good configuration after an invalid settings section");
2046
+ ctx.logger.error(error);
2047
+ return lastGood;
2048
+ }
2049
+ };
2050
+ options();
2051
+ const resolveApiKey = async (connection) => {
2052
+ const ref = connection.apiKeyEnv;
2053
+ const credentials = ctx.get("credentials");
2054
+ if (credentials !== void 0) {
2055
+ const hit = await credentials.resolve(ref);
2056
+ if (hit !== void 0) return assertUsableApiKey(hit.value, "llm-deepseek", ref);
2057
+ } else {
2058
+ const ambient = launchEnvironmentOf(ctx).get(ref);
2059
+ if (ambient !== void 0 && ambient.value.length > 0) return assertUsableApiKey(ambient.value, "llm-deepseek", ref);
2060
+ }
2061
+ throw new LlmError(`llm-deepseek: no API key for provider route "${PROVIDER}"; store ${ref} through the credentials service (the web Models page writes it), or export ${ref} in the launching environment`, "MISSING_CREDENTIAL");
2062
+ };
2063
+ let userId;
2064
+ const resolveUserId = () => userId ??= getOrCreateAnonymousUserId();
2065
+ const adapter = new DeepSeekAdapter({
2066
+ options,
2067
+ resolveApiKey,
2068
+ resolveUserId,
2069
+ resolveAttachments: () => ctx.get("attachments"),
2070
+ resolveImageAccess: (attachments, ref) => resolveImageAttachmentAccess(attachments, (hostPath) => ctx.get("fs")?.processPathFromHostPath(hostPath), ref),
2071
+ prepareExtensions: (request) => {
2072
+ return ctx.get("deepseekLlmApiExtensions")?.prepare(request) ?? Promise.resolve({
2073
+ fields: {},
2074
+ accept: () => Promise.resolve()
2075
+ });
2076
+ }
2077
+ });
2078
+ ctx.llm.registerConfigurableProviders([{
2079
+ provider: PROVIDER,
2080
+ displayName: "DeepSeek",
2081
+ settingsNs: NS,
2082
+ settingsPath: []
2083
+ }]);
2084
+ const registration = ctx.llm.registerAdapter([PROVIDER], adapter);
2085
+ let registeredPolicy = options().retryPolicy;
2086
+ const ensureRegistrationFacts = () => {
2087
+ const policy = options().retryPolicy;
2088
+ if (deepEqualJson(policy, registeredPolicy)) return;
2089
+ registration.replace([PROVIDER]);
2090
+ registeredPolicy = policy;
2091
+ };
2092
+ ctx.inject(["settings"], (settingsCtx) => {
2093
+ settingsCtx.settings.installSection(ctx, NS, Config, config, {
2094
+ setSource: (source) => {
2095
+ current = source;
2096
+ },
2097
+ onChange: ensureRegistrationFacts
2098
+ });
2099
+ });
2100
+ }
2101
+ //#endregion
2102
+ export { Config, DEFAULT_CONTEXT_WINDOW, DEFAULT_FILES_API_TIMEOUT_MS, DEFAULT_FILE_EXPIRY_SECONDS, DEFAULT_FILE_QUOTA_CLEANUP_BATCH, DEFAULT_FILE_REFRESH_MARGIN_SECONDS, DEFAULT_IMAGE_OFFLOAD_BYTE_QUANTUM, DEFAULT_IMAGE_OFFLOAD_COUNT_QUANTUM, DEFAULT_INLINE_IMAGE_OFFLOAD_BYTE_QUANTUM, DEFAULT_LOW_DETAIL_IMAGE_PIXEL_BUDGET, DEFAULT_MAX_IMAGES_PER_REQUEST, DEFAULT_MAX_INLINE_REQUEST_IMAGE_BYTES, DEFAULT_MAX_REQUEST_FILES_BYTES, DEFAULT_MAX_TOKENS, DEFAULT_REQUEST_IMAGE_MAX_BYTES, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET, DEFAULT_STREAM_IDLE_TIMEOUT_MS, DeepSeekAdapter, DeepSeekFileId, DeepSeekFileStore, DeepSeekFilesClient, DeepSeekUploadIndex, MAX_CHAT_IMAGE_BYTES, MAX_FILE_EXPIRY_SECONDS, MAX_FILE_UPLOAD_BYTES, MAX_STORED_FILE_BYTES, MAX_STORED_FILE_COUNT, MIN_FILE_EXPIRY_SECONDS, PUBLIC_BASE_URL, apply, deepSeekFileScope, deepSeekImageRequestPricing, deepSeekImageTokens, inject, name, resolveAdapterOptions, resolveRequestImagePolicy };