@morlay/dsh-llm-openai-compatible 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/cordis.patch.yml +24 -0
- package/lib/adapter.d.mts +137 -0
- package/lib/adapter.d.mts.map +1 -0
- package/lib/adapter.mjs +295 -0
- package/lib/adapter.mjs.map +1 -0
- package/lib/index.d.mts +89 -0
- package/lib/index.d.mts.map +1 -0
- package/lib/index.mjs +293 -0
- package/lib/index.mjs.map +1 -0
- package/lib/serialize.d.mts +68 -0
- package/lib/serialize.d.mts.map +1 -0
- package/lib/serialize.mjs +276 -0
- package/lib/serialize.mjs.map +1 -0
- package/lib/translate.d.mts +19 -0
- package/lib/translate.d.mts.map +1 -0
- package/lib/translate.mjs +209 -0
- package/lib/translate.mjs.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { LlmError, contentHasImage, offloadRequestImages } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { AttachmentError } from "@deepseek-ai/dsh-attachment";
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
//#region src/serialize.ts
|
|
5
|
+
/**
|
|
6
|
+
* Serialize harness messages into the AI SDK `LanguageModelV4Prompt` and merge
|
|
7
|
+
* profile sampling defaults into call options for
|
|
8
|
+
* `@ai-sdk/openai-compatible`. Request-level `GenerateOptions` wins, profile
|
|
9
|
+
* values fill in, and anything still undefined is omitted so the provider's
|
|
10
|
+
* own default applies. `topK` and the wire `reasoning_effort` spelling travel
|
|
11
|
+
* through `providerOptions["openai-compatible"]`, which the provider
|
|
12
|
+
* transparently forwards into the request body.
|
|
13
|
+
* @module dsh-llm-openai-compatible/serialize
|
|
14
|
+
*/
|
|
15
|
+
/** Lead-in text of the user message that follows tool-result images. */
|
|
16
|
+
const TOOL_RESULT_IMAGE_TEXT = "Attached image(s) from tool result:";
|
|
17
|
+
/**
|
|
18
|
+
* Resolve one reasoning effort to its wire spelling for the exact model.
|
|
19
|
+
* `off` (and a `null` wire spelling) means *omit the field* — the provider
|
|
20
|
+
* default applies; every other declared effort sends its configured value.
|
|
21
|
+
* An effort the model does not declare fails here, before any network I/O:
|
|
22
|
+
* that is where a bad request-level effort AND a bad profile default both
|
|
23
|
+
* belong (describing a model must never throw, but executing a request must).
|
|
24
|
+
* @param model - the configured model descriptor, or `undefined` for an
|
|
25
|
+
* unlisted model id (which carries no reasoning declaration).
|
|
26
|
+
* @param effort - the resolved effort to send, or `undefined` to send none.
|
|
27
|
+
* @returns the wire `reasoning_effort` value, or `undefined` to omit the field.
|
|
28
|
+
* @throws LlmError `UNSUPPORTED_REASONING_EFFORT` when the model does not
|
|
29
|
+
* declare the effort.
|
|
30
|
+
*/
|
|
31
|
+
function resolveReasoningWire(model, effort) {
|
|
32
|
+
if (effort === void 0) return void 0;
|
|
33
|
+
const declaration = model?.reasoningEfforts;
|
|
34
|
+
if (declaration === void 0 || declaration === false) {
|
|
35
|
+
const subject = model === void 0 ? "unlisted model" : `model "${model.id}"`;
|
|
36
|
+
throw new LlmError(`OpenAI-compatible ${subject} declares no reasoning efforts, so "${effort}" cannot be selected`, "UNSUPPORTED_REASONING_EFFORT");
|
|
37
|
+
}
|
|
38
|
+
const wire = declaration[effort];
|
|
39
|
+
if (wire === void 0) throw new LlmError(`OpenAI-compatible model "${model.id}" does not support reasoning effort "${effort}"`, "UNSUPPORTED_REASONING_EFFORT");
|
|
40
|
+
if (wire === null) return void 0;
|
|
41
|
+
return wire;
|
|
42
|
+
}
|
|
43
|
+
/** Join the text blocks of a message (used for user/tool-result content). */
|
|
44
|
+
function flattenText(blocks) {
|
|
45
|
+
return blocks.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
46
|
+
}
|
|
47
|
+
/** Reject core image content before any text-flattening path can silently erase it. */
|
|
48
|
+
function assertTextOnly(blocks) {
|
|
49
|
+
if (contentHasImage(blocks)) throw new LlmError("The OpenAI-compatible chat-completions adapter does not support image content in this message.", "UNSUPPORTED_CONTENT");
|
|
50
|
+
}
|
|
51
|
+
/** Reject roles whose wire format cannot carry image input. */
|
|
52
|
+
function assertSupportedImageRoles(messages) {
|
|
53
|
+
for (const message of messages) if (message.role !== "user" && contentHasImage(message.content)) throw new LlmError(`The OpenAI-compatible chat-completions adapter cannot represent image content in a ${message.role} message.`, "UNSUPPORTED_CONTENT");
|
|
54
|
+
}
|
|
55
|
+
/** Resolve one durable image into its transient data-URL file part. */
|
|
56
|
+
async function imagePart(block, attachments, signal) {
|
|
57
|
+
try {
|
|
58
|
+
const stored = await attachments.readImage(block.attachment, signal);
|
|
59
|
+
return {
|
|
60
|
+
type: "file",
|
|
61
|
+
mediaType: stored.ref.mediaType,
|
|
62
|
+
data: {
|
|
63
|
+
type: "url",
|
|
64
|
+
url: new URL(`data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString("base64")}`)
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof AttachmentError) throw new LlmError(error.message, error.code, { cause: error });
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** Serialize one assistant message into prompt parts, recording tool names by call id. */
|
|
73
|
+
function assistantParts(message, toolNames) {
|
|
74
|
+
const parts = [];
|
|
75
|
+
for (const block of message.content) switch (block.type) {
|
|
76
|
+
case "text":
|
|
77
|
+
if (block.text.length > 0) parts.push({
|
|
78
|
+
type: "text",
|
|
79
|
+
text: block.text
|
|
80
|
+
});
|
|
81
|
+
break;
|
|
82
|
+
case "reasoning":
|
|
83
|
+
if (block.text.length > 0) parts.push({
|
|
84
|
+
type: "reasoning",
|
|
85
|
+
text: block.text
|
|
86
|
+
});
|
|
87
|
+
break;
|
|
88
|
+
case "tool-call": {
|
|
89
|
+
let input;
|
|
90
|
+
try {
|
|
91
|
+
input = JSON.parse(block.arguments);
|
|
92
|
+
} catch {
|
|
93
|
+
throw new LlmError(`assistant tool call "${block.id}" carries malformed JSON arguments`, "MALFORMED_RESPONSE");
|
|
94
|
+
}
|
|
95
|
+
parts.push({
|
|
96
|
+
type: "tool-call",
|
|
97
|
+
toolCallId: block.id,
|
|
98
|
+
toolName: block.name,
|
|
99
|
+
input
|
|
100
|
+
});
|
|
101
|
+
toolNames.set(block.id, block.name);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return parts;
|
|
106
|
+
}
|
|
107
|
+
/** Convert user blocks into prompt parts, resolving images through the resolver. */
|
|
108
|
+
async function userParts(blocks, resolveImage, signal) {
|
|
109
|
+
const parts = [];
|
|
110
|
+
for (const block of blocks) switch (block.type) {
|
|
111
|
+
case "text":
|
|
112
|
+
if (block.text.length > 0) parts.push({
|
|
113
|
+
type: "text",
|
|
114
|
+
text: block.text
|
|
115
|
+
});
|
|
116
|
+
break;
|
|
117
|
+
case "image":
|
|
118
|
+
if (resolveImage === void 0) throw new LlmError("The OpenAI-compatible chat-completions adapter does not support image content in this message.", "UNSUPPORTED_CONTENT");
|
|
119
|
+
parts.push(await resolveImage(block, signal));
|
|
120
|
+
break;
|
|
121
|
+
case "tool-result": parts.push(...await userParts(block.content, resolveImage, signal));
|
|
122
|
+
}
|
|
123
|
+
return parts;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Serialize the conversation into the AI SDK prompt. `tool-result` blocks
|
|
127
|
+
* become standalone `{role: "tool"}` messages; the harness puts each tool
|
|
128
|
+
* result in its own user-role message, so a mixed user message contributes
|
|
129
|
+
* its text first and its tool results as separate wire messages after.
|
|
130
|
+
* Tool-result images cannot ride a tool message, so they are buffered and
|
|
131
|
+
* flushed into the next user message (or a dedicated one at the end).
|
|
132
|
+
* @param messages - the harness conversation, in order.
|
|
133
|
+
* @param resolveImage - image resolver for the image-capable path, or `undefined` for text-only.
|
|
134
|
+
* @param signal - cancellation for attachment reads.
|
|
135
|
+
* @returns the AI SDK prompt; order preserved.
|
|
136
|
+
*/
|
|
137
|
+
async function serializePrompt(messages, resolveImage, signal) {
|
|
138
|
+
if (resolveImage === void 0) for (const message of messages) assertTextOnly(message.content);
|
|
139
|
+
else assertSupportedImageRoles(messages);
|
|
140
|
+
const prompt = [];
|
|
141
|
+
const toolNames = /* @__PURE__ */ new Map();
|
|
142
|
+
let pendingToolImages = [];
|
|
143
|
+
const flushToolImages = () => {
|
|
144
|
+
if (pendingToolImages.length === 0) return;
|
|
145
|
+
prompt.push({
|
|
146
|
+
role: "user",
|
|
147
|
+
content: [{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: TOOL_RESULT_IMAGE_TEXT
|
|
150
|
+
}, ...pendingToolImages]
|
|
151
|
+
});
|
|
152
|
+
pendingToolImages = [];
|
|
153
|
+
};
|
|
154
|
+
for (const message of messages) {
|
|
155
|
+
if (message.role === "system") {
|
|
156
|
+
flushToolImages();
|
|
157
|
+
prompt.push({
|
|
158
|
+
role: "system",
|
|
159
|
+
content: flattenText(message.content)
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (message.role === "assistant") {
|
|
164
|
+
flushToolImages();
|
|
165
|
+
const parts = assistantParts(message, toolNames);
|
|
166
|
+
if (parts.length > 0) prompt.push({
|
|
167
|
+
role: "assistant",
|
|
168
|
+
content: parts
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const regular = message.content.filter((block) => block.type !== "tool-result");
|
|
173
|
+
const toolResults = message.content.filter((block) => block.type === "tool-result");
|
|
174
|
+
const content = await userParts(regular, resolveImage, signal);
|
|
175
|
+
if (content.length > 0 || toolResults.length === 0) {
|
|
176
|
+
flushToolImages();
|
|
177
|
+
prompt.push({
|
|
178
|
+
role: "user",
|
|
179
|
+
content
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
for (const result of toolResults) {
|
|
183
|
+
const images = [];
|
|
184
|
+
if (resolveImage !== void 0) {
|
|
185
|
+
for (const block of result.content) if (block.type === "image") images.push(await resolveImage(block, signal));
|
|
186
|
+
}
|
|
187
|
+
prompt.push({
|
|
188
|
+
role: "tool",
|
|
189
|
+
content: [{
|
|
190
|
+
type: "tool-result",
|
|
191
|
+
toolCallId: result.toolCallId,
|
|
192
|
+
toolName: toolNames.get(result.toolCallId) ?? "",
|
|
193
|
+
output: {
|
|
194
|
+
type: "text",
|
|
195
|
+
value: flattenText(result.content) || (images.length > 0 ? "(see attached image)" : "(no output)")
|
|
196
|
+
}
|
|
197
|
+
}]
|
|
198
|
+
});
|
|
199
|
+
pendingToolImages.push(...images);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
flushToolImages();
|
|
203
|
+
return prompt;
|
|
204
|
+
}
|
|
205
|
+
/** Serialize tool schemas to AI SDK function tools. */
|
|
206
|
+
function serializeTools(options) {
|
|
207
|
+
const tools = options.tools?.map((tool) => ({
|
|
208
|
+
type: "function",
|
|
209
|
+
name: tool.name,
|
|
210
|
+
description: tool.description,
|
|
211
|
+
inputSchema: tool.parameters
|
|
212
|
+
}));
|
|
213
|
+
return tools !== void 0 && tools.length > 0 ? tools : void 0;
|
|
214
|
+
}
|
|
215
|
+
/** Merge profile sampling defaults under request-level values into call options. */
|
|
216
|
+
function callOptionsWithPrompt(options, profile, model, prompt) {
|
|
217
|
+
const tools = serializeTools(options);
|
|
218
|
+
const temperature = options.temperature ?? profile.temperature;
|
|
219
|
+
const maxOutputTokens = options.maxTokens ?? model?.maxTokens ?? profile.defaultMaxTokens;
|
|
220
|
+
const reasoningEffort = resolveReasoningWire(model, options.reasoningEffort === void 0 ? profile.reasoning : options.reasoningEffort);
|
|
221
|
+
const providerOptions = { "openai-compatible": {
|
|
222
|
+
...reasoningEffort === void 0 ? {} : { reasoningEffort },
|
|
223
|
+
...profile.topK === void 0 ? {} : { top_k: profile.topK }
|
|
224
|
+
} };
|
|
225
|
+
return {
|
|
226
|
+
prompt,
|
|
227
|
+
...temperature !== void 0 ? { temperature } : {},
|
|
228
|
+
...profile.topP !== void 0 ? { topP: profile.topP } : {},
|
|
229
|
+
...profile.presencePenalty !== void 0 ? { presencePenalty: profile.presencePenalty } : {},
|
|
230
|
+
...profile.frequencyPenalty !== void 0 ? { frequencyPenalty: profile.frequencyPenalty } : {},
|
|
231
|
+
...profile.seed !== void 0 ? { seed: profile.seed } : {},
|
|
232
|
+
...maxOutputTokens !== void 0 ? { maxOutputTokens } : {},
|
|
233
|
+
...options.stop !== void 0 ? { stopSequences: options.stop } : {},
|
|
234
|
+
...tools !== void 0 ? { tools } : {},
|
|
235
|
+
...Object.keys(providerOptions["openai-compatible"] ?? {}).length > 0 ? { providerOptions } : {}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Build the full call options for text-only content.
|
|
240
|
+
* @param options - the harness request.
|
|
241
|
+
* @param profile - resolved provider profile.
|
|
242
|
+
* @param model - configured model descriptor, or `undefined` for unlisted ids.
|
|
243
|
+
* @returns the AI SDK call options (settings + prompt + provider options).
|
|
244
|
+
*/
|
|
245
|
+
async function serializeCallOptions(options, profile, model) {
|
|
246
|
+
const system = options.system === void 0 ? [] : [{
|
|
247
|
+
role: "system",
|
|
248
|
+
content: options.system
|
|
249
|
+
}];
|
|
250
|
+
const prompt = await serializePrompt(options.messages, void 0);
|
|
251
|
+
return callOptionsWithPrompt(options, profile, model, [...system, ...prompt]);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Build one image-capable request while keeping durable bytes out of session
|
|
255
|
+
* messages. Oversized oldest images become deterministic text before any
|
|
256
|
+
* attachment read.
|
|
257
|
+
* @param options - the harness request containing image-capable user content.
|
|
258
|
+
* @param profile - resolved provider profile.
|
|
259
|
+
* @param model - configured model descriptor, or `undefined` for unlisted ids.
|
|
260
|
+
* @param images - the attachment resolver, request bound, and cancellation.
|
|
261
|
+
* @returns the fully materialized call options.
|
|
262
|
+
*/
|
|
263
|
+
async function serializeCallOptionsWithImages(options, profile, model, images) {
|
|
264
|
+
const requestMessages = offloadRequestImages(options.messages, images.maxRequestImageBytes);
|
|
265
|
+
const resolveImage = (block, signal) => imagePart(block, images.attachments, signal);
|
|
266
|
+
const system = options.system === void 0 ? [] : [{
|
|
267
|
+
role: "system",
|
|
268
|
+
content: options.system
|
|
269
|
+
}];
|
|
270
|
+
const prompt = await serializePrompt(requestMessages, resolveImage, images.signal);
|
|
271
|
+
return callOptionsWithPrompt(options, profile, model, [...system, ...prompt]);
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
export { resolveReasoningWire, serializeCallOptions, serializeCallOptionsWithImages };
|
|
275
|
+
|
|
276
|
+
//# sourceMappingURL=serialize.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize.mjs","names":[],"sources":["../src/serialize.ts"],"sourcesContent":["/**\n * Serialize harness messages into the AI SDK `LanguageModelV4Prompt` and merge\n * profile sampling defaults into call options for\n * `@ai-sdk/openai-compatible`. Request-level `GenerateOptions` wins, profile\n * values fill in, and anything still undefined is omitted so the provider's\n * own default applies. `topK` and the wire `reasoning_effort` spelling travel\n * through `providerOptions[\"openai-compatible\"]`, which the provider\n * transparently forwards into the request body.\n * @module dsh-llm-openai-compatible/serialize\n */\n\nimport { LlmError, contentHasImage, offloadRequestImages } from \"@deepseek-ai/dsh-llm\";\nimport type { ContentBlock, GenerateOptions, Message } from \"@deepseek-ai/dsh-llm\";\nimport { AttachmentError } from \"@deepseek-ai/dsh-attachment\";\nimport type { AttachmentStore } from \"@deepseek-ai/dsh-attachment\";\nimport type {\n JSONSchema7,\n LanguageModelV4FunctionTool,\n LanguageModelV4Prompt,\n SharedV4ProviderOptions,\n} from \"@ai-sdk/provider\";\nimport { Buffer } from \"node:buffer\";\nimport type { ReasoningEffort, ResolvedModelProfile, ResolvedProviderProfile } from \"./adapter.ts\";\n\n/** Lead-in text of the user message that follows tool-result images. */\nconst TOOL_RESULT_IMAGE_TEXT = \"Attached image(s) from tool result:\";\n\n/** A user-message content part the adapter understands. */\ntype UserContentPart = Extract<LanguageModelV4Prompt[number], { role: \"user\" }>[\"content\"][number];\n\n/** Provider-specific options the adapter forwards into the request body. */\nexport type OpenAICompatibleProviderOptions = SharedV4ProviderOptions & {\n \"openai-compatible\"?: {\n /** Exact wire `reasoning_effort` spelling; absence omits the field. */\n reasoningEffort?: string;\n /** Non-standard `top_k` sampling knob, sent only to gateways that accept it. */\n top_k?: number;\n };\n};\n\n/** The per-call options resolved from one harness request and provider profile. */\nexport interface OpenAICompatibleCallOptions {\n prompt: LanguageModelV4Prompt;\n maxOutputTokens?: number;\n temperature?: number;\n topP?: number;\n presencePenalty?: number;\n frequencyPenalty?: number;\n seed?: number;\n stopSequences?: string[];\n tools?: LanguageModelV4FunctionTool[];\n providerOptions?: OpenAICompatibleProviderOptions;\n}\n\n/**\n * Resolve one reasoning effort to its wire spelling for the exact model.\n * `off` (and a `null` wire spelling) means *omit the field* — the provider\n * default applies; every other declared effort sends its configured value.\n * An effort the model does not declare fails here, before any network I/O:\n * that is where a bad request-level effort AND a bad profile default both\n * belong (describing a model must never throw, but executing a request must).\n * @param model - the configured model descriptor, or `undefined` for an\n * unlisted model id (which carries no reasoning declaration).\n * @param effort - the resolved effort to send, or `undefined` to send none.\n * @returns the wire `reasoning_effort` value, or `undefined` to omit the field.\n * @throws LlmError `UNSUPPORTED_REASONING_EFFORT` when the model does not\n * declare the effort.\n */\nexport function resolveReasoningWire(\n model: ResolvedModelProfile | undefined,\n effort: ResolvedProviderProfile[\"reasoning\"] | undefined,\n): string | undefined {\n if (effort === void 0) return void 0;\n const declaration = model?.reasoningEfforts;\n if (declaration === void 0 || declaration === false) {\n const subject = model === void 0 ? \"unlisted model\" : `model \"${model.id}\"`;\n throw new LlmError(\n `OpenAI-compatible ${subject} declares no reasoning efforts, so \"${effort}\" cannot be selected`,\n \"UNSUPPORTED_REASONING_EFFORT\",\n );\n }\n const wire = declaration[effort];\n if (wire === void 0) {\n throw new LlmError(\n `OpenAI-compatible model \"${model.id}\" does not support reasoning effort \"${effort}\"`,\n \"UNSUPPORTED_REASONING_EFFORT\",\n );\n }\n if (wire === null) return void 0;\n return wire;\n}\n\n/** Join the text blocks of a message (used for user/tool-result content). */\nfunction flattenText(blocks: readonly ContentBlock[]): string {\n return blocks\n .filter((block) => block.type === \"text\")\n .map((block) => block.text)\n .join(\"\");\n}\n\n/** Reject core image content before any text-flattening path can silently erase it. */\nfunction assertTextOnly(blocks: readonly ContentBlock[]): void {\n if (contentHasImage(blocks)) {\n throw new LlmError(\n \"The OpenAI-compatible chat-completions adapter does not support image content in this message.\",\n \"UNSUPPORTED_CONTENT\",\n );\n }\n}\n\n/** Reject roles whose wire format cannot carry image input. */\nfunction assertSupportedImageRoles(messages: readonly Message[]): void {\n for (const message of messages) {\n if (message.role !== \"user\" && contentHasImage(message.content)) {\n throw new LlmError(\n `The OpenAI-compatible chat-completions adapter cannot represent image content in a ${message.role} message.`,\n \"UNSUPPORTED_CONTENT\",\n );\n }\n }\n}\n\n/** Resolve one durable image into its transient data-URL file part. */\nasync function imagePart(\n block: Extract<ContentBlock, { type: \"image\" }>,\n attachments: AttachmentStore,\n signal?: AbortSignal,\n): Promise<UserContentPart> {\n try {\n const stored = await attachments.readImage(block.attachment, signal);\n return {\n type: \"file\",\n mediaType: stored.ref.mediaType,\n data: {\n type: \"url\",\n url: new URL(\n `data:${stored.ref.mediaType};base64,${Buffer.from(stored.data).toString(\"base64\")}`,\n ),\n },\n };\n } catch (error) {\n if (error instanceof AttachmentError)\n throw new LlmError(error.message, error.code, { cause: error });\n throw error;\n }\n}\n\n/** Serialize one assistant message into prompt parts, recording tool names by call id. */\nfunction assistantParts(\n message: Message,\n toolNames: Map<string, string>,\n): Extract<LanguageModelV4Prompt[number], { role: \"assistant\" }>[\"content\"] {\n const parts: Extract<LanguageModelV4Prompt[number], { role: \"assistant\" }>[\"content\"] = [];\n for (const block of message.content) {\n switch (block.type) {\n case \"text\":\n if (block.text.length > 0) parts.push({ type: \"text\", text: block.text });\n break;\n case \"reasoning\":\n if (block.text.length > 0) parts.push({ type: \"reasoning\", text: block.text });\n break;\n case \"tool-call\": {\n let input: unknown;\n try {\n input = JSON.parse(block.arguments) as unknown;\n } catch {\n throw new LlmError(\n `assistant tool call \"${block.id}\" carries malformed JSON arguments`,\n \"MALFORMED_RESPONSE\",\n );\n }\n parts.push({ type: \"tool-call\", toolCallId: block.id, toolName: block.name, input });\n toolNames.set(block.id, block.name);\n break;\n }\n default:\n break;\n }\n }\n return parts;\n}\n\n/** Convert user blocks into prompt parts, resolving images through the resolver. */\nasync function userParts(\n blocks: readonly ContentBlock[],\n resolveImage:\n | ((\n block: Extract<ContentBlock, { type: \"image\" }>,\n signal?: AbortSignal,\n ) => Promise<UserContentPart>)\n | undefined,\n signal?: AbortSignal,\n): Promise<UserContentPart[]> {\n const parts: UserContentPart[] = [];\n for (const block of blocks) {\n switch (block.type) {\n case \"text\":\n if (block.text.length > 0) parts.push({ type: \"text\", text: block.text });\n break;\n case \"image\":\n if (resolveImage === void 0)\n throw new LlmError(\n \"The OpenAI-compatible chat-completions adapter does not support image content in this message.\",\n \"UNSUPPORTED_CONTENT\",\n );\n parts.push(await resolveImage(block, signal));\n break;\n case \"tool-result\":\n parts.push(...(await userParts(block.content, resolveImage, signal)));\n break;\n default:\n break;\n }\n }\n return parts;\n}\n\n/**\n * Serialize the conversation into the AI SDK prompt. `tool-result` blocks\n * become standalone `{role: \"tool\"}` messages; the harness puts each tool\n * result in its own user-role message, so a mixed user message contributes\n * its text first and its tool results as separate wire messages after.\n * Tool-result images cannot ride a tool message, so they are buffered and\n * flushed into the next user message (or a dedicated one at the end).\n * @param messages - the harness conversation, in order.\n * @param resolveImage - image resolver for the image-capable path, or `undefined` for text-only.\n * @param signal - cancellation for attachment reads.\n * @returns the AI SDK prompt; order preserved.\n */\nasync function serializePrompt(\n messages: readonly Message[],\n resolveImage:\n | ((\n block: Extract<ContentBlock, { type: \"image\" }>,\n signal?: AbortSignal,\n ) => Promise<UserContentPart>)\n | undefined,\n signal?: AbortSignal,\n): Promise<LanguageModelV4Prompt> {\n if (resolveImage === void 0) {\n for (const message of messages) assertTextOnly(message.content);\n } else {\n assertSupportedImageRoles(messages);\n }\n const prompt: LanguageModelV4Prompt = [];\n const toolNames = new Map<string, string>();\n let pendingToolImages: UserContentPart[] = [];\n const flushToolImages = () => {\n if (pendingToolImages.length === 0) return;\n prompt.push({\n role: \"user\",\n content: [{ type: \"text\", text: TOOL_RESULT_IMAGE_TEXT }, ...pendingToolImages],\n });\n pendingToolImages = [];\n };\n for (const message of messages) {\n if (message.role === \"system\") {\n flushToolImages();\n prompt.push({ role: \"system\", content: flattenText(message.content) });\n continue;\n }\n if (message.role === \"assistant\") {\n flushToolImages();\n const parts = assistantParts(message, toolNames);\n if (parts.length > 0) prompt.push({ role: \"assistant\", content: parts });\n continue;\n }\n const regular = message.content.filter((block) => block.type !== \"tool-result\");\n const toolResults = message.content.filter((block) => block.type === \"tool-result\");\n const content = await userParts(regular, resolveImage, signal);\n if (content.length > 0 || toolResults.length === 0) {\n flushToolImages();\n prompt.push({ role: \"user\", content });\n }\n for (const result of toolResults) {\n const images: UserContentPart[] = [];\n if (resolveImage !== void 0) {\n for (const block of result.content) {\n if (block.type === \"image\") images.push(await resolveImage(block, signal));\n }\n }\n prompt.push({\n role: \"tool\",\n content: [\n {\n type: \"tool-result\",\n toolCallId: result.toolCallId,\n toolName: toolNames.get(result.toolCallId) ?? \"\",\n output: {\n type: \"text\",\n value:\n flattenText(result.content) ||\n (images.length > 0 ? \"(see attached image)\" : \"(no output)\"),\n },\n },\n ],\n });\n pendingToolImages.push(...images);\n }\n }\n flushToolImages();\n return prompt;\n}\n\n/** Serialize tool schemas to AI SDK function tools. */\nfunction serializeTools(options: GenerateOptions): LanguageModelV4FunctionTool[] | undefined {\n const tools = options.tools?.map((tool): LanguageModelV4FunctionTool => ({\n type: \"function\",\n name: tool.name,\n description: tool.description,\n inputSchema: tool.parameters as JSONSchema7,\n }));\n return tools !== void 0 && tools.length > 0 ? tools : void 0;\n}\n\n/** Merge profile sampling defaults under request-level values into call options. */\nfunction callOptionsWithPrompt(\n options: GenerateOptions,\n profile: ResolvedProviderProfile,\n model: ResolvedModelProfile | undefined,\n prompt: LanguageModelV4Prompt,\n): OpenAICompatibleCallOptions {\n const tools = serializeTools(options);\n const temperature = options.temperature ?? profile.temperature;\n const maxOutputTokens = options.maxTokens ?? model?.maxTokens ?? profile.defaultMaxTokens;\n const requestedEffort: ReasoningEffort | undefined =\n options.reasoningEffort === void 0\n ? profile.reasoning\n : (options.reasoningEffort as unknown as ReasoningEffort);\n const reasoningEffort = resolveReasoningWire(model, requestedEffort);\n const providerOptions: OpenAICompatibleProviderOptions = {\n \"openai-compatible\": {\n ...(reasoningEffort === void 0 ? {} : { reasoningEffort }),\n ...(profile.topK === void 0 ? {} : { top_k: profile.topK }),\n },\n };\n return {\n prompt,\n ...(temperature !== void 0 ? { temperature } : {}),\n ...(profile.topP !== void 0 ? { topP: profile.topP } : {}),\n ...(profile.presencePenalty !== void 0 ? { presencePenalty: profile.presencePenalty } : {}),\n ...(profile.frequencyPenalty !== void 0 ? { frequencyPenalty: profile.frequencyPenalty } : {}),\n ...(profile.seed !== void 0 ? { seed: profile.seed } : {}),\n ...(maxOutputTokens !== void 0 ? { maxOutputTokens } : {}),\n ...(options.stop !== void 0 ? { stopSequences: options.stop } : {}),\n ...(tools !== void 0 ? { tools } : {}),\n ...(Object.keys(providerOptions[\"openai-compatible\"] ?? {}).length > 0\n ? { providerOptions }\n : {}),\n };\n}\n\n/**\n * Build the full call options for text-only content.\n * @param options - the harness request.\n * @param profile - resolved provider profile.\n * @param model - configured model descriptor, or `undefined` for unlisted ids.\n * @returns the AI SDK call options (settings + prompt + provider options).\n */\nexport async function serializeCallOptions(\n options: GenerateOptions,\n profile: ResolvedProviderProfile,\n model: ResolvedModelProfile | undefined,\n): Promise<OpenAICompatibleCallOptions> {\n const system =\n options.system === void 0 ? [] : [{ role: \"system\" as const, content: options.system }];\n const prompt = await serializePrompt(options.messages, void 0);\n return callOptionsWithPrompt(options, profile, model, [...system, ...prompt]);\n}\n\n/**\n * Build one image-capable request while keeping durable bytes out of session\n * messages. Oversized oldest images become deterministic text before any\n * attachment read.\n * @param options - the harness request containing image-capable user content.\n * @param profile - resolved provider profile.\n * @param model - configured model descriptor, or `undefined` for unlisted ids.\n * @param images - the attachment resolver, request bound, and cancellation.\n * @returns the fully materialized call options.\n */\nexport async function serializeCallOptionsWithImages(\n options: GenerateOptions,\n profile: ResolvedProviderProfile,\n model: ResolvedModelProfile | undefined,\n images: { attachments: AttachmentStore; maxRequestImageBytes: number; signal?: AbortSignal },\n): Promise<OpenAICompatibleCallOptions> {\n const requestMessages = offloadRequestImages(options.messages, images.maxRequestImageBytes);\n const resolveImage = (block: Extract<ContentBlock, { type: \"image\" }>, signal?: AbortSignal) =>\n imagePart(block, images.attachments, signal);\n const system =\n options.system === void 0 ? [] : [{ role: \"system\" as const, content: options.system }];\n const prompt = await serializePrompt(requestMessages, resolveImage, images.signal);\n return callOptionsWithPrompt(options, profile, model, [...system, ...prompt]);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FinishReason, StreamChunk, TokenUsage } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
import { LanguageModelV4FinishReason, LanguageModelV4StreamPart, LanguageModelV4Usage } from "@ai-sdk/provider";
|
|
3
|
+
//#region src/translate.d.ts
|
|
4
|
+
/** Map the AI SDK finish-reason vocabulary to the harness FinishReason. */
|
|
5
|
+
declare function mapFinishReason(reason: LanguageModelV4FinishReason): FinishReason;
|
|
6
|
+
/** Map AI SDK usage (already disjoint by the provider converter) to harness counts. */
|
|
7
|
+
declare function mapUsage(usage: LanguageModelV4Usage): TokenUsage;
|
|
8
|
+
/**
|
|
9
|
+
* Consume the AI SDK stream and yield StreamChunks. The `finish` part closes
|
|
10
|
+
* every open block, reports usage, and emits the terminal finish; an empty
|
|
11
|
+
* `stop` completion maps to `EMPTY_RESPONSE`. A stream-level `error` part
|
|
12
|
+
* aborts with `TRANSPORT`.
|
|
13
|
+
* @param stream - the `doStream` result stream.
|
|
14
|
+
* @returns harness chunks in order; the terminal `finish` is always last.
|
|
15
|
+
*/
|
|
16
|
+
declare function translate(stream: ReadableStream<LanguageModelV4StreamPart>): AsyncGenerator<StreamChunk, void>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { mapFinishReason, mapUsage, translate };
|
|
19
|
+
//# sourceMappingURL=translate.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.d.mts","names":[],"sources":["../src/translate.ts"],"mappings":";;;;iBAmBgB,gBAAgB,QAAQ,8BAA8B;;iBAoBtD,SAAS,OAAO,uBAAuB;;;;;;;;;iBA4ChC,UACrB,QAAQ,eAAe,6BACtB,eAAe"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { CallId, EMPTY_RESPONSE_CODE, LlmError } from "@deepseek-ai/dsh-llm";
|
|
2
|
+
//#region src/translate.ts
|
|
3
|
+
/**
|
|
4
|
+
* Translate AI SDK `LanguageModelV4StreamPart`s (as produced by
|
|
5
|
+
* `@ai-sdk/openai-compatible`'s `doStream`) into the harness `StreamChunk`
|
|
6
|
+
* protocol. One stateful harness block per text, reasoning, or tool-call
|
|
7
|
+
* index; `block-end`s, `usage`, and `finish` are all deferred to the stream's
|
|
8
|
+
* `finish` part so nothing follows the terminal finish.
|
|
9
|
+
* @module dsh-llm-openai-compatible/translate
|
|
10
|
+
*/
|
|
11
|
+
/** Map the AI SDK finish-reason vocabulary to the harness FinishReason. */
|
|
12
|
+
function mapFinishReason(reason) {
|
|
13
|
+
switch (reason.unified) {
|
|
14
|
+
case "stop": return { kind: "stop" };
|
|
15
|
+
case "tool-calls": return { kind: "tool-calls" };
|
|
16
|
+
case "length": return { kind: "max-tokens" };
|
|
17
|
+
default: return {
|
|
18
|
+
kind: "error",
|
|
19
|
+
failure: {
|
|
20
|
+
message: `model stopped: ${reason.raw ?? reason.unified}`,
|
|
21
|
+
code: (reason.raw ?? reason.unified).toUpperCase()
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Map AI SDK usage (already disjoint by the provider converter) to harness counts. */
|
|
27
|
+
function mapUsage(usage) {
|
|
28
|
+
const cacheRead = usage.inputTokens.cacheRead;
|
|
29
|
+
const reasoning = usage.outputTokens.reasoning;
|
|
30
|
+
return {
|
|
31
|
+
inputTokens: usage.inputTokens.noCache ?? usage.inputTokens.total ?? 0,
|
|
32
|
+
outputTokens: usage.outputTokens.total ?? 0,
|
|
33
|
+
...cacheRead !== void 0 && cacheRead > 0 ? { cacheReadTokens: cacheRead } : {},
|
|
34
|
+
...reasoning !== void 0 && reasoning > 0 ? { reasoningTokens: reasoning } : {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Assemble the final ContentBlock for one open block. */
|
|
38
|
+
function closeBlock(block) {
|
|
39
|
+
switch (block.kind) {
|
|
40
|
+
case "text": return {
|
|
41
|
+
type: "text",
|
|
42
|
+
text: block.text
|
|
43
|
+
};
|
|
44
|
+
case "reasoning": return {
|
|
45
|
+
type: "reasoning",
|
|
46
|
+
text: block.text
|
|
47
|
+
};
|
|
48
|
+
case "tool-call": return {
|
|
49
|
+
type: "tool-call",
|
|
50
|
+
id: CallId(block.callId ?? ""),
|
|
51
|
+
name: block.name ?? "",
|
|
52
|
+
arguments: block.text
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Consume the AI SDK stream and yield StreamChunks. The `finish` part closes
|
|
58
|
+
* every open block, reports usage, and emits the terminal finish; an empty
|
|
59
|
+
* `stop` completion maps to `EMPTY_RESPONSE`. A stream-level `error` part
|
|
60
|
+
* aborts with `TRANSPORT`.
|
|
61
|
+
* @param stream - the `doStream` result stream.
|
|
62
|
+
* @returns harness chunks in order; the terminal `finish` is always last.
|
|
63
|
+
*/
|
|
64
|
+
async function* translate(stream) {
|
|
65
|
+
let nextIndex = 0;
|
|
66
|
+
const textBlocks = /* @__PURE__ */ new Map();
|
|
67
|
+
const reasoningBlocks = /* @__PURE__ */ new Map();
|
|
68
|
+
const toolBlocks = /* @__PURE__ */ new Map();
|
|
69
|
+
const toolQueue = [];
|
|
70
|
+
const order = [];
|
|
71
|
+
let pendingUsage;
|
|
72
|
+
let pendingFinish;
|
|
73
|
+
const open = (kind) => {
|
|
74
|
+
const block = {
|
|
75
|
+
index: nextIndex++,
|
|
76
|
+
kind,
|
|
77
|
+
text: ""
|
|
78
|
+
};
|
|
79
|
+
order.push(block);
|
|
80
|
+
return block;
|
|
81
|
+
};
|
|
82
|
+
for await (const part of stream) switch (part.type) {
|
|
83
|
+
case "stream-start":
|
|
84
|
+
case "response-metadata":
|
|
85
|
+
case "raw": break;
|
|
86
|
+
case "text-start": {
|
|
87
|
+
const block = open("text");
|
|
88
|
+
textBlocks.set(part.id, block);
|
|
89
|
+
yield {
|
|
90
|
+
type: "block-start",
|
|
91
|
+
index: block.index,
|
|
92
|
+
blockType: "text"
|
|
93
|
+
};
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "text-delta": {
|
|
97
|
+
const block = textBlocks.get(part.id);
|
|
98
|
+
if (block === void 0) break;
|
|
99
|
+
block.text += part.delta;
|
|
100
|
+
yield {
|
|
101
|
+
type: "text-delta",
|
|
102
|
+
index: block.index,
|
|
103
|
+
text: part.delta
|
|
104
|
+
};
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "text-end": break;
|
|
108
|
+
case "reasoning-start": {
|
|
109
|
+
const block = open("reasoning");
|
|
110
|
+
reasoningBlocks.set(part.id, block);
|
|
111
|
+
yield {
|
|
112
|
+
type: "block-start",
|
|
113
|
+
index: block.index,
|
|
114
|
+
blockType: "reasoning"
|
|
115
|
+
};
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
case "reasoning-delta": {
|
|
119
|
+
const block = reasoningBlocks.get(part.id);
|
|
120
|
+
if (block === void 0) break;
|
|
121
|
+
block.text += part.delta;
|
|
122
|
+
yield {
|
|
123
|
+
type: "reasoning-delta",
|
|
124
|
+
index: block.index,
|
|
125
|
+
text: part.delta
|
|
126
|
+
};
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case "reasoning-end": break;
|
|
130
|
+
case "tool-input-start": {
|
|
131
|
+
const block = open("tool-call");
|
|
132
|
+
if (part.toolName !== void 0) block.name = part.toolName;
|
|
133
|
+
toolBlocks.set(part.id, block);
|
|
134
|
+
toolQueue.push(block);
|
|
135
|
+
yield {
|
|
136
|
+
type: "block-start",
|
|
137
|
+
index: block.index,
|
|
138
|
+
blockType: "tool-call"
|
|
139
|
+
};
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "tool-input-delta": {
|
|
143
|
+
const block = toolBlocks.get(part.id);
|
|
144
|
+
if (block === void 0) break;
|
|
145
|
+
block.text += part.delta;
|
|
146
|
+
yield {
|
|
147
|
+
type: "tool-call-delta",
|
|
148
|
+
index: block.index,
|
|
149
|
+
id: CallId(block.callId ?? part.id),
|
|
150
|
+
...block.name !== void 0 ? { name: block.name } : {},
|
|
151
|
+
argumentsDelta: part.delta
|
|
152
|
+
};
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "tool-input-end": break;
|
|
156
|
+
case "tool-call":
|
|
157
|
+
applyToolCall(part, toolQueue);
|
|
158
|
+
break;
|
|
159
|
+
case "tool-result":
|
|
160
|
+
case "tool-approval-request":
|
|
161
|
+
case "custom":
|
|
162
|
+
case "file":
|
|
163
|
+
case "reasoning-file":
|
|
164
|
+
case "source": break;
|
|
165
|
+
case "finish":
|
|
166
|
+
pendingUsage = mapUsage(part.usage);
|
|
167
|
+
pendingFinish = mapFinishReason(part.finishReason);
|
|
168
|
+
for (const block of order) yield {
|
|
169
|
+
type: "block-end",
|
|
170
|
+
index: block.index,
|
|
171
|
+
block: closeBlock(block)
|
|
172
|
+
};
|
|
173
|
+
if (pendingUsage !== void 0) yield {
|
|
174
|
+
type: "usage",
|
|
175
|
+
usage: pendingUsage
|
|
176
|
+
};
|
|
177
|
+
const reason = pendingFinish ?? { kind: "stop" };
|
|
178
|
+
yield {
|
|
179
|
+
type: "finish",
|
|
180
|
+
reason: reason.kind === "stop" && order.length === 0 ? {
|
|
181
|
+
kind: "error",
|
|
182
|
+
failure: {
|
|
183
|
+
message: "model returned a completed response with no content",
|
|
184
|
+
code: EMPTY_RESPONSE_CODE
|
|
185
|
+
}
|
|
186
|
+
} : reason
|
|
187
|
+
};
|
|
188
|
+
return;
|
|
189
|
+
case "error": {
|
|
190
|
+
const error = part.error;
|
|
191
|
+
const cause = error instanceof Error ? error : void 0;
|
|
192
|
+
const message = cause?.message ?? (typeof error === "string" ? error : "provider stream error");
|
|
193
|
+
throw new LlmError(`OpenAI-compatible stream failed: ${message}`, "TRANSPORT", { cause });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw new LlmError("AI SDK stream ended without a finish part", "STREAM_CLOSED");
|
|
197
|
+
}
|
|
198
|
+
/** Merge a complete tool-call part into the oldest buffered tool block. */
|
|
199
|
+
function applyToolCall(part, toolQueue) {
|
|
200
|
+
const block = toolQueue.shift();
|
|
201
|
+
if (block === void 0) return;
|
|
202
|
+
block.callId = part.toolCallId;
|
|
203
|
+
block.name = part.toolName;
|
|
204
|
+
block.text = part.input;
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
export { mapFinishReason, mapUsage, translate };
|
|
208
|
+
|
|
209
|
+
//# sourceMappingURL=translate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"translate.mjs","names":[],"sources":["../src/translate.ts"],"sourcesContent":["/**\n * Translate AI SDK `LanguageModelV4StreamPart`s (as produced by\n * `@ai-sdk/openai-compatible`'s `doStream`) into the harness `StreamChunk`\n * protocol. One stateful harness block per text, reasoning, or tool-call\n * index; `block-end`s, `usage`, and `finish` are all deferred to the stream's\n * `finish` part so nothing follows the terminal finish.\n * @module dsh-llm-openai-compatible/translate\n */\n\nimport { EMPTY_RESPONSE_CODE, CallId, LlmError } from \"@deepseek-ai/dsh-llm\";\nimport type { FinishReason, StreamChunk, TokenUsage } from \"@deepseek-ai/dsh-llm\";\nimport type {\n LanguageModelV4FinishReason,\n LanguageModelV4StreamPart,\n LanguageModelV4ToolCall,\n LanguageModelV4Usage,\n} from \"@ai-sdk/provider\";\n\n/** Map the AI SDK finish-reason vocabulary to the harness FinishReason. */\nexport function mapFinishReason(reason: LanguageModelV4FinishReason): FinishReason {\n switch (reason.unified) {\n case \"stop\":\n return { kind: \"stop\" };\n case \"tool-calls\":\n return { kind: \"tool-calls\" };\n case \"length\":\n return { kind: \"max-tokens\" };\n default:\n return {\n kind: \"error\",\n failure: {\n message: `model stopped: ${reason.raw ?? reason.unified}`,\n code: (reason.raw ?? reason.unified).toUpperCase(),\n },\n };\n }\n}\n\n/** Map AI SDK usage (already disjoint by the provider converter) to harness counts. */\nexport function mapUsage(usage: LanguageModelV4Usage): TokenUsage {\n const cacheRead = usage.inputTokens.cacheRead;\n const reasoning = usage.outputTokens.reasoning;\n return {\n inputTokens: usage.inputTokens.noCache ?? usage.inputTokens.total ?? 0,\n outputTokens: usage.outputTokens.total ?? 0,\n ...(cacheRead !== void 0 && cacheRead > 0 ? { cacheReadTokens: cacheRead } : {}),\n ...(reasoning !== void 0 && reasoning > 0 ? { reasoningTokens: reasoning } : {}),\n };\n}\n\ninterface OpenBlock {\n index: number;\n kind: \"text\" | \"reasoning\" | \"tool-call\";\n text: string;\n callId?: string;\n name?: string;\n}\n\n/** Assemble the final ContentBlock for one open block. */\nfunction closeBlock(block: OpenBlock): Extract<StreamChunk, { type: \"block-end\" }>[\"block\"] {\n switch (block.kind) {\n case \"text\":\n return { type: \"text\", text: block.text };\n case \"reasoning\":\n return { type: \"reasoning\", text: block.text };\n case \"tool-call\":\n return {\n type: \"tool-call\",\n id: CallId(block.callId ?? \"\"),\n name: block.name ?? \"\",\n arguments: block.text,\n };\n }\n}\n\n/**\n * Consume the AI SDK stream and yield StreamChunks. The `finish` part closes\n * every open block, reports usage, and emits the terminal finish; an empty\n * `stop` completion maps to `EMPTY_RESPONSE`. A stream-level `error` part\n * aborts with `TRANSPORT`.\n * @param stream - the `doStream` result stream.\n * @returns harness chunks in order; the terminal `finish` is always last.\n */\nexport async function* translate(\n stream: ReadableStream<LanguageModelV4StreamPart>,\n): AsyncGenerator<StreamChunk, void> {\n let nextIndex = 0;\n const textBlocks = new Map<string, OpenBlock>();\n const reasoningBlocks = new Map<string, OpenBlock>();\n const toolBlocks = new Map<string, OpenBlock>();\n const toolQueue: OpenBlock[] = [];\n const order: OpenBlock[] = [];\n let pendingUsage: TokenUsage | undefined;\n let pendingFinish: FinishReason | undefined;\n\n const open = (kind: OpenBlock[\"kind\"]): OpenBlock => {\n const block: OpenBlock = { index: nextIndex++, kind, text: \"\" };\n order.push(block);\n return block;\n };\n\n for await (const part of stream) {\n switch (part.type) {\n case \"stream-start\":\n case \"response-metadata\":\n case \"raw\":\n break;\n case \"text-start\": {\n const block = open(\"text\");\n textBlocks.set(part.id, block);\n yield { type: \"block-start\", index: block.index, blockType: \"text\" };\n break;\n }\n case \"text-delta\": {\n const block = textBlocks.get(part.id);\n if (block === void 0) break;\n block.text += part.delta;\n yield { type: \"text-delta\", index: block.index, text: part.delta };\n break;\n }\n case \"text-end\":\n break;\n case \"reasoning-start\": {\n const block = open(\"reasoning\");\n reasoningBlocks.set(part.id, block);\n yield { type: \"block-start\", index: block.index, blockType: \"reasoning\" };\n break;\n }\n case \"reasoning-delta\": {\n const block = reasoningBlocks.get(part.id);\n if (block === void 0) break;\n block.text += part.delta;\n yield { type: \"reasoning-delta\", index: block.index, text: part.delta };\n break;\n }\n case \"reasoning-end\":\n break;\n case \"tool-input-start\": {\n const block = open(\"tool-call\");\n if (part.toolName !== void 0) block.name = part.toolName;\n toolBlocks.set(part.id, block);\n toolQueue.push(block);\n yield { type: \"block-start\", index: block.index, blockType: \"tool-call\" };\n break;\n }\n case \"tool-input-delta\": {\n const block = toolBlocks.get(part.id);\n if (block === void 0) break;\n block.text += part.delta;\n yield {\n type: \"tool-call-delta\",\n index: block.index,\n id: CallId(block.callId ?? part.id),\n ...(block.name !== void 0 ? { name: block.name } : {}),\n argumentsDelta: part.delta,\n };\n break;\n }\n case \"tool-input-end\":\n break;\n case \"tool-call\": {\n applyToolCall(part, toolQueue);\n break;\n }\n case \"tool-result\":\n case \"tool-approval-request\":\n case \"custom\":\n case \"file\":\n case \"reasoning-file\":\n case \"source\":\n // Provider-executed tools and generated files are not part of this\n // adapter's client-executed tool loop; nothing to emit.\n break;\n case \"finish\":\n pendingUsage = mapUsage(part.usage);\n pendingFinish = mapFinishReason(part.finishReason);\n for (const block of order)\n yield { type: \"block-end\", index: block.index, block: closeBlock(block) };\n if (pendingUsage !== void 0) yield { type: \"usage\", usage: pendingUsage };\n const reason = pendingFinish ?? { kind: \"stop\" as const };\n yield {\n type: \"finish\",\n reason:\n reason.kind === \"stop\" && order.length === 0\n ? {\n kind: \"error\",\n failure: {\n message: \"model returned a completed response with no content\",\n code: EMPTY_RESPONSE_CODE,\n },\n }\n : reason,\n };\n return;\n case \"error\": {\n const error = part.error;\n const cause = error instanceof Error ? error : void 0;\n const message =\n cause?.message ?? (typeof error === \"string\" ? error : \"provider stream error\");\n throw new LlmError(`OpenAI-compatible stream failed: ${message}`, \"TRANSPORT\", { cause });\n }\n }\n }\n throw new LlmError(\"AI SDK stream ended without a finish part\", \"STREAM_CLOSED\");\n}\n\n/** Merge a complete tool-call part into the oldest buffered tool block. */\nfunction applyToolCall(part: LanguageModelV4ToolCall, toolQueue: OpenBlock[]): void {\n const block = toolQueue.shift();\n if (block === void 0) return;\n block.callId = part.toolCallId;\n block.name = part.toolName;\n // The provider emits the complete arguments on this part; the buffered\n // deltas were a partial view.\n block.text = part.input;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@morlay/dsh-llm-openai-compatible",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "OpenAI-compatible LLM adapter plugin for DeepSeek Harness with configurable default sampling parameters (temperature / topP / topK / penalties / seed) over a providers dict.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"llm",
|
|
9
|
+
"ollama",
|
|
10
|
+
"openai",
|
|
11
|
+
"openai-compatible"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/morlay/dsh-llm-openai-compatible.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"lib",
|
|
20
|
+
"cordis.patch.yml"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./lib/index.d.mts",
|
|
26
|
+
"default": "./lib/index.mjs"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json",
|
|
29
|
+
"./cordis.patch.yml": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@ai-sdk/openai-compatible": "^3.0.32",
|
|
33
|
+
"@ai-sdk/provider": "^4.0.7",
|
|
34
|
+
"zod": "^4.4.3"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
38
|
+
"@deepseek-ai/dsh-anonymous-user-id": "^0.1.0-rc.8",
|
|
39
|
+
"@deepseek-ai/dsh-attachment": "^0.1.0-rc.8",
|
|
40
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.8",
|
|
41
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.0-rc.8",
|
|
42
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.8",
|
|
43
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|
|
44
|
+
"@deepseek-ai/dsh-timeout": "^0.1.0-rc.8",
|
|
45
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
46
|
+
},
|
|
47
|
+
"dsh": {
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"scripts": {}
|
|
53
|
+
}
|