@deepstrike/sdk 0.2.30 → 0.2.31
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/dist/harness/harness.d.ts +3 -2
- package/dist/harness/harness.js +15 -35
- package/dist/harness/judge.d.ts +42 -0
- package/dist/harness/judge.js +58 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/kernel.d.ts +7 -1
- package/dist/providers/anthropic-compatible.d.ts +23 -0
- package/dist/providers/anthropic-compatible.js +29 -0
- package/dist/providers/catalog.js +5 -53
- package/dist/providers/deepseek.d.ts +28 -8
- package/dist/providers/deepseek.js +38 -157
- package/dist/providers/factories.js +10 -22
- package/dist/providers/glm.d.ts +5 -4
- package/dist/providers/glm.js +8 -23
- package/dist/providers/kimi.d.ts +5 -4
- package/dist/providers/kimi.js +8 -22
- package/dist/providers/minimax.d.ts +26 -12
- package/dist/providers/minimax.js +32 -158
- package/dist/providers/openai.d.ts +43 -0
- package/dist/providers/openai.js +128 -64
- package/dist/providers/qwen.d.ts +19 -19
- package/dist/providers/qwen.js +37 -176
- package/dist/providers/registry.d.ts +18 -0
- package/dist/providers/registry.js +35 -0
- package/dist/providers/vendor-profiles.d.ts +54 -0
- package/dist/providers/vendor-profiles.js +62 -0
- package/dist/runtime/event-stream.d.ts +44 -0
- package/dist/runtime/event-stream.js +39 -0
- package/dist/runtime/reactive-session.d.ts +125 -0
- package/dist/runtime/reactive-session.js +127 -0
- package/dist/runtime/run-group.d.ts +74 -0
- package/dist/runtime/run-group.js +72 -0
- package/dist/runtime/runner.d.ts +9 -0
- package/dist/runtime/runner.js +56 -7
- package/dist/runtime/session-log.d.ts +8 -0
- package/dist/runtime/turn-policy.d.ts +33 -0
- package/dist/runtime/turn-policy.js +58 -0
- package/dist/signals/gateway.d.ts +7 -2
- package/dist/signals/gateway.js +13 -3
- package/dist/signals/types.d.ts +10 -1
- package/package.json +2 -2
package/dist/providers/openai.js
CHANGED
|
@@ -25,6 +25,15 @@ const OPENAI_POLICIES = {
|
|
|
25
25
|
"o3-mini": { maxTurns: 25 },
|
|
26
26
|
"o4-mini": { maxTurns: 25 },
|
|
27
27
|
};
|
|
28
|
+
/** Rebuild OpenAI-native `tool_calls` blocks from the streamed buffers — needed by reasoning
|
|
29
|
+
* vendors (DeepSeek/MiniMax) that persist the native blocks in their replay envelope. */
|
|
30
|
+
export function nativeToolCallsFromBuffers(toolCallBufs) {
|
|
31
|
+
return Object.values(toolCallBufs).map(tb => ({
|
|
32
|
+
id: tb.id,
|
|
33
|
+
type: "function",
|
|
34
|
+
function: { name: tb.name, arguments: tb.argsBuf || "{}" },
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
28
37
|
export class OpenAIChatProvider {
|
|
29
38
|
client;
|
|
30
39
|
circuit;
|
|
@@ -75,6 +84,46 @@ export class OpenAIChatProvider {
|
|
|
75
84
|
degradeMissingReasoning: this.degradeMissingReasoningReplay(extensions),
|
|
76
85
|
});
|
|
77
86
|
}
|
|
87
|
+
// ── Template-Method hooks ───────────────────────────────────────────────────
|
|
88
|
+
// Defaults reproduce the plain OpenAI-chat behavior; reasoning vendors
|
|
89
|
+
// (DeepSeek/MiniMax) override these instead of duplicating complete()/stream().
|
|
90
|
+
/** Pre-process caller extensions before they reach buildChatMessages + the wire request
|
|
91
|
+
* (e.g. set `__deepstrikeThinkingEnabled`). Default: pass through unchanged. */
|
|
92
|
+
prepareExtensions(extensions) {
|
|
93
|
+
return extensions;
|
|
94
|
+
}
|
|
95
|
+
/** Extra top-level request-body fields merged into the chat.completions call (vendor thinking
|
|
96
|
+
* knobs like `reasoning_effort`, `extra_body`, `reasoning_split`). Default: none. */
|
|
97
|
+
requestBodyExtras(_extensions) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
/** Request-body params controlling prompt caching. Default sends OpenAI's `prompt_cache_key`;
|
|
101
|
+
* vendors whose endpoints reject unknown params (e.g. DeepSeek 400s) override to `{}`. */
|
|
102
|
+
cacheKeyParams(context, tools) {
|
|
103
|
+
return { prompt_cache_key: this.promptCacheKey(context, tools) };
|
|
104
|
+
}
|
|
105
|
+
/** Whether streamed `content` may carry inline `<thinking>…</thinking>` tags to split out.
|
|
106
|
+
* Default true (OpenAI). Reasoning vendors emit reasoning out-of-band, so they return false. */
|
|
107
|
+
usesInlineThinkingTags() {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
/** Whether to surface streamed `reasoning_content` as thinking_delta events. Default true;
|
|
111
|
+
* vendors gate this behind an `exposeReasoning` extension. */
|
|
112
|
+
exposeReasoningDelta(_extensions) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/** Persist replay after a non-streaming turn. Default: nothing (plain OpenAI has no reasoning
|
|
116
|
+
* to replay). Reasoning vendors override to store their envelope. */
|
|
117
|
+
rememberCompleteReplay(_content, _toolCalls, _reasoning) {
|
|
118
|
+
/* no-op */
|
|
119
|
+
}
|
|
120
|
+
/** Persist replay after a streamed turn. Default: store `{ reasoning_content }` when there is a
|
|
121
|
+
* tool-call turn or captured reasoning (the prior base behavior). Vendors override. */
|
|
122
|
+
rememberStreamReplay(content, toolCalls, reasoning) {
|
|
123
|
+
if (toolCalls.length || reasoning.reasoningContent) {
|
|
124
|
+
this.chat.rememberReplayFields({ content, toolCalls }, { reasoning_content: reasoning.reasoningContent });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
78
127
|
/**
|
|
79
128
|
* Pre-flight query: would this history validate against this provider with the
|
|
80
129
|
* given extensions, without sending the request? Lets an embedder route around
|
|
@@ -100,23 +149,32 @@ export class OpenAIChatProvider {
|
|
|
100
149
|
}
|
|
101
150
|
}
|
|
102
151
|
async complete(context, tools, extensions) {
|
|
152
|
+
const prepared = this.prepareExtensions(extensions);
|
|
103
153
|
if (this.circuit.isOpen())
|
|
104
154
|
throw new Error("Circuit breaker open");
|
|
105
|
-
const msgs = this.buildChatMessages(context,
|
|
155
|
+
const msgs = this.buildChatMessages(context, prepared);
|
|
106
156
|
let lastErr;
|
|
107
157
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
108
158
|
try {
|
|
109
159
|
const resp = await this.client.chat.completions.create({
|
|
110
|
-
|
|
111
|
-
...this.requestExtensions(
|
|
160
|
+
...this.cacheKeyParams(context, tools),
|
|
161
|
+
...this.requestExtensions(prepared),
|
|
162
|
+
...this.requestBodyExtras(extensions),
|
|
112
163
|
model: this.model,
|
|
113
164
|
messages: msgs,
|
|
114
165
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
115
166
|
});
|
|
116
167
|
this.circuit.recordSuccess();
|
|
117
168
|
const choice = resp.choices[0].message;
|
|
118
|
-
const
|
|
119
|
-
|
|
169
|
+
const nativeToolCalls = choice.tool_calls ?? [];
|
|
170
|
+
const toolCalls = this.chat.normalizeToolCalls(nativeToolCalls);
|
|
171
|
+
const content = choice.content ?? "";
|
|
172
|
+
this.rememberCompleteReplay(content, toolCalls, {
|
|
173
|
+
reasoningContent: typeof choice.reasoning_content === "string" ? choice.reasoning_content : "",
|
|
174
|
+
reasoningDetails: choice.reasoning_details,
|
|
175
|
+
nativeToolCalls: nativeToolCalls,
|
|
176
|
+
});
|
|
177
|
+
return { role: "assistant", content, tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
|
|
120
178
|
}
|
|
121
179
|
catch (err) {
|
|
122
180
|
lastErr = err;
|
|
@@ -128,15 +186,20 @@ export class OpenAIChatProvider {
|
|
|
128
186
|
throw lastErr;
|
|
129
187
|
}
|
|
130
188
|
async *stream(context, tools, extensions, _state, signal) {
|
|
131
|
-
const
|
|
189
|
+
const prepared = this.prepareExtensions(extensions);
|
|
190
|
+
const msgs = this.buildChatMessages(context, prepared);
|
|
132
191
|
const toolCallBufs = {};
|
|
133
192
|
const emittedToolCallIndexes = new Set();
|
|
193
|
+
const useTags = this.usesInlineThinkingTags();
|
|
194
|
+
const exposeReasoning = this.exposeReasoningDelta(extensions);
|
|
134
195
|
const extractor = new ThinkingTagStreamExtractor();
|
|
135
196
|
let accumulatedReasoning = "";
|
|
197
|
+
let accumulatedReasoningDetails;
|
|
136
198
|
let accumulatedContent = "";
|
|
137
199
|
const stream = await this.client.chat.completions.create({
|
|
138
|
-
|
|
139
|
-
...this.requestExtensions(
|
|
200
|
+
...this.cacheKeyParams(context, tools),
|
|
201
|
+
...this.requestExtensions(prepared),
|
|
202
|
+
...this.requestBodyExtras(extensions),
|
|
140
203
|
model: this.model,
|
|
141
204
|
messages: msgs,
|
|
142
205
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
@@ -144,6 +207,30 @@ export class OpenAIChatProvider {
|
|
|
144
207
|
stream_options: { include_usage: true },
|
|
145
208
|
// #2-B-ii: forward the abort signal so a preempt cancels the in-flight HTTP request.
|
|
146
209
|
}, signal ? { signal } : undefined);
|
|
210
|
+
const rememberStream = () => {
|
|
211
|
+
const toolCalls = Object.values(toolCallBufs).map(tb => ({ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}" }));
|
|
212
|
+
this.rememberStreamReplay(accumulatedContent, toolCalls, {
|
|
213
|
+
reasoningContent: accumulatedReasoning,
|
|
214
|
+
reasoningDetails: accumulatedReasoningDetails,
|
|
215
|
+
nativeToolCalls: nativeToolCallsFromBuffers(toolCallBufs),
|
|
216
|
+
});
|
|
217
|
+
};
|
|
218
|
+
const emitPendingToolCalls = function* () {
|
|
219
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
220
|
+
const idx = Number(index);
|
|
221
|
+
if (emittedToolCallIndexes.has(idx))
|
|
222
|
+
continue;
|
|
223
|
+
let args = {};
|
|
224
|
+
try {
|
|
225
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
args = {};
|
|
229
|
+
}
|
|
230
|
+
emittedToolCallIndexes.add(idx);
|
|
231
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
232
|
+
}
|
|
233
|
+
};
|
|
147
234
|
let totalTokens = 0;
|
|
148
235
|
let inputTokens = 0;
|
|
149
236
|
let outputTokens = 0;
|
|
@@ -163,20 +250,29 @@ export class OpenAIChatProvider {
|
|
|
163
250
|
if (!delta)
|
|
164
251
|
continue;
|
|
165
252
|
if (delta.reasoning_content) {
|
|
166
|
-
accumulatedReasoning += delta.reasoning_content;
|
|
167
|
-
|
|
253
|
+
accumulatedReasoning += String(delta.reasoning_content);
|
|
254
|
+
if (exposeReasoning)
|
|
255
|
+
yield { type: "thinking_delta", delta: String(delta.reasoning_content) };
|
|
168
256
|
}
|
|
257
|
+
if (delta.reasoning_details !== undefined && delta.reasoning_details !== null)
|
|
258
|
+
accumulatedReasoningDetails = delta.reasoning_details;
|
|
169
259
|
if (delta.content) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
260
|
+
if (useTags) {
|
|
261
|
+
for (const part of extractor.feed(String(delta.content))) {
|
|
262
|
+
if (part.type === "thinking") {
|
|
263
|
+
accumulatedReasoning += part.content;
|
|
264
|
+
yield { type: "thinking_delta", delta: part.content };
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
accumulatedContent += part.content;
|
|
268
|
+
yield { type: "text_delta", delta: part.content };
|
|
269
|
+
}
|
|
178
270
|
}
|
|
179
271
|
}
|
|
272
|
+
else {
|
|
273
|
+
accumulatedContent += String(delta.content);
|
|
274
|
+
yield { type: "text_delta", delta: delta.content };
|
|
275
|
+
}
|
|
180
276
|
}
|
|
181
277
|
for (const tc of delta.tool_calls ?? []) {
|
|
182
278
|
const idx = tc.index;
|
|
@@ -187,56 +283,24 @@ export class OpenAIChatProvider {
|
|
|
187
283
|
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
188
284
|
}
|
|
189
285
|
if (choice.finish_reason === "tool_calls") {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
}));
|
|
193
|
-
this.chat.rememberReplayFields({ content: accumulatedContent, toolCalls }, { reasoning_content: accumulatedReasoning });
|
|
194
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
195
|
-
const idx = Number(index);
|
|
196
|
-
if (emittedToolCallIndexes.has(idx))
|
|
197
|
-
continue;
|
|
198
|
-
let args = {};
|
|
199
|
-
try {
|
|
200
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
args = {};
|
|
204
|
-
}
|
|
205
|
-
emittedToolCallIndexes.add(idx);
|
|
206
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
for (const part of extractor.flush()) {
|
|
211
|
-
if (part.type === "thinking") {
|
|
212
|
-
accumulatedReasoning += part.content;
|
|
213
|
-
yield { type: "thinking_delta", delta: part.content };
|
|
286
|
+
rememberStream();
|
|
287
|
+
yield* emitPendingToolCalls();
|
|
214
288
|
}
|
|
215
|
-
else {
|
|
216
|
-
accumulatedContent += part.content;
|
|
217
|
-
yield { type: "text_delta", delta: part.content };
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
221
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
222
|
-
}));
|
|
223
|
-
if (toolCalls.length || accumulatedReasoning) {
|
|
224
|
-
this.chat.rememberReplayFields({ content: accumulatedContent, toolCalls }, { reasoning_content: accumulatedReasoning });
|
|
225
289
|
}
|
|
226
|
-
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
290
|
+
if (useTags) {
|
|
291
|
+
for (const part of extractor.flush()) {
|
|
292
|
+
if (part.type === "thinking") {
|
|
293
|
+
accumulatedReasoning += part.content;
|
|
294
|
+
yield { type: "thinking_delta", delta: part.content };
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
accumulatedContent += part.content;
|
|
298
|
+
yield { type: "text_delta", delta: part.content };
|
|
299
|
+
}
|
|
236
300
|
}
|
|
237
|
-
emittedToolCallIndexes.add(idx);
|
|
238
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
239
301
|
}
|
|
302
|
+
rememberStream();
|
|
303
|
+
yield* emitPendingToolCalls();
|
|
240
304
|
if (totalTokens > 0)
|
|
241
305
|
yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}) };
|
|
242
306
|
}
|
package/dist/providers/qwen.d.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { CircuitBreaker } from "./base.js";
|
|
5
|
-
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
1
|
+
import type { Message, ProviderDescriptor, ProviderReplay, RuntimePolicy } from "../types.js";
|
|
2
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
3
|
+
import { AnthropicCompatibleProvider } from "./anthropic-compatible.js";
|
|
6
4
|
/**
|
|
7
5
|
* Qwen over its Anthropic-compatible endpoint.
|
|
6
|
+
* @deprecated Prefer `qwen({ protocol: "anthropic" })`. Behavior is now fully
|
|
7
|
+
* data-driven via `anthropicVendorProfiles.qwen`; this thin shim is kept for
|
|
8
|
+
* backward compatibility and `instanceof` checks.
|
|
8
9
|
*/
|
|
9
|
-
export declare class QwenAnthropicProvider extends
|
|
10
|
+
export declare class QwenAnthropicProvider extends AnthropicCompatibleProvider {
|
|
10
11
|
constructor(apiKey: string, model?: string, retry?: {
|
|
11
12
|
maxRetries: number;
|
|
12
13
|
baseDelay: number;
|
|
13
14
|
}, baseURL?: string);
|
|
14
|
-
protected providerName(): string;
|
|
15
|
-
runtimePolicy(): RuntimePolicy;
|
|
16
15
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Qwen / DashScope over its OpenAI-compatible (DashScope) endpoint. Reasoning is carried
|
|
18
|
+
* out-of-band as `reasoning_content`; thinking is opted into via `enable_thinking` /
|
|
19
|
+
* `thinking_budget` (sent under `extra_body`). The streaming / tool-call machinery and the
|
|
20
|
+
* default `{ reasoning_content }` replay are inherited from OpenAIChatProvider; only request
|
|
21
|
+
* shaping and the (string-coerced) replay peek differ, supplied via the Template-Method hooks.
|
|
22
|
+
*/
|
|
23
|
+
export declare class QwenProvider extends OpenAIChatProvider {
|
|
24
24
|
constructor(apiKey: string, model?: string, retry?: {
|
|
25
25
|
maxRetries: number;
|
|
26
26
|
baseDelay: number;
|
|
27
27
|
}, baseURL?: string);
|
|
28
28
|
runtimePolicy(): RuntimePolicy;
|
|
29
29
|
descriptor(): ProviderDescriptor;
|
|
30
|
-
|
|
30
|
+
protected cacheKeyParams(): Record<string, unknown>;
|
|
31
|
+
protected usesInlineThinkingTags(): boolean;
|
|
32
|
+
protected requestBodyExtras(extensions?: Record<string, unknown>): Record<string, unknown>;
|
|
33
|
+
protected requestExtensions(extensions?: Record<string, unknown>): Record<string, unknown>;
|
|
31
34
|
peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
32
35
|
seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
33
|
-
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
34
|
-
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
35
36
|
private thinkingExtraBody;
|
|
36
|
-
private requestExtensions;
|
|
37
37
|
}
|
package/dist/providers/qwen.js
CHANGED
|
@@ -1,54 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { CircuitBreaker, omitExtensionKeys, openAICachedPromptTokens } from "./base.js";
|
|
5
|
-
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
1
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
2
|
+
import { AnthropicCompatibleProvider } from "./anthropic-compatible.js";
|
|
3
|
+
import { omitExtensionKeys } from "./base.js";
|
|
6
4
|
import { endpointProfiles } from "./profiles.js";
|
|
7
|
-
|
|
8
|
-
"qwen3.7-max-preview": { maxTurns: 45 },
|
|
9
|
-
"qwen3.7-plus-preview": { maxTurns: 40 },
|
|
10
|
-
"qwen3.6-max-preview": { maxTurns: 40 },
|
|
11
|
-
"qwen3.6-plus": { maxTurns: 35 },
|
|
12
|
-
"qwen3.6-flash": { maxTurns: 20 },
|
|
13
|
-
"qwen3.6-35b-a3b": { maxTurns: 25 },
|
|
14
|
-
"qwen3.6-27b": { maxTurns: 25 },
|
|
15
|
-
"qwen3.5-plus": { maxTurns: 35 },
|
|
16
|
-
"qwen3.5-flash": { maxTurns: 20 },
|
|
17
|
-
"qwen3.5-397b-a17b": { maxTurns: 35 },
|
|
18
|
-
"qwen3.5-122b-a10b": { maxTurns: 25 },
|
|
19
|
-
"qwen3.5-35b-a3b": { maxTurns: 20 },
|
|
20
|
-
"qwen3.5-27b": { maxTurns: 20 },
|
|
21
|
-
};
|
|
5
|
+
import { QWEN_POLICIES, anthropicVendorProfiles } from "./vendor-profiles.js";
|
|
22
6
|
/**
|
|
23
7
|
* Qwen over its Anthropic-compatible endpoint.
|
|
8
|
+
* @deprecated Prefer `qwen({ protocol: "anthropic" })`. Behavior is now fully
|
|
9
|
+
* data-driven via `anthropicVendorProfiles.qwen`; this thin shim is kept for
|
|
10
|
+
* backward compatibility and `instanceof` checks.
|
|
24
11
|
*/
|
|
25
|
-
export class QwenAnthropicProvider extends
|
|
26
|
-
constructor(apiKey, model
|
|
27
|
-
super(apiKey, model, retry,
|
|
28
|
-
baseURL,
|
|
29
|
-
authMode: "api-key",
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
providerName() {
|
|
33
|
-
return "qwen";
|
|
34
|
-
}
|
|
35
|
-
runtimePolicy() {
|
|
36
|
-
return QWEN_POLICIES[this.model] ?? {};
|
|
12
|
+
export class QwenAnthropicProvider extends AnthropicCompatibleProvider {
|
|
13
|
+
constructor(apiKey, model, retry, baseURL) {
|
|
14
|
+
super(anthropicVendorProfiles.qwen, apiKey, model, retry, baseURL);
|
|
37
15
|
}
|
|
38
16
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
this.circuit = new CircuitBreaker();
|
|
50
|
-
this.maxRetries = retry.maxRetries;
|
|
51
|
-
this.baseDelay = retry.baseDelay;
|
|
17
|
+
/**
|
|
18
|
+
* Qwen / DashScope over its OpenAI-compatible (DashScope) endpoint. Reasoning is carried
|
|
19
|
+
* out-of-band as `reasoning_content`; thinking is opted into via `enable_thinking` /
|
|
20
|
+
* `thinking_budget` (sent under `extra_body`). The streaming / tool-call machinery and the
|
|
21
|
+
* default `{ reasoning_content }` replay are inherited from OpenAIChatProvider; only request
|
|
22
|
+
* shaping and the (string-coerced) replay peek differ, supplied via the Template-Method hooks.
|
|
23
|
+
*/
|
|
24
|
+
export class QwenProvider extends OpenAIChatProvider {
|
|
25
|
+
constructor(apiKey, model = "qwen3.6-plus", retry, baseURL = endpointProfiles["qwen.dashscope"].baseURL) {
|
|
26
|
+
super(apiKey, model, retry, baseURL);
|
|
52
27
|
}
|
|
53
28
|
runtimePolicy() {
|
|
54
29
|
return QWEN_POLICIES[this.model] ?? {};
|
|
@@ -68,8 +43,23 @@ export class QwenProvider {
|
|
|
68
43
|
},
|
|
69
44
|
};
|
|
70
45
|
}
|
|
71
|
-
|
|
72
|
-
|
|
46
|
+
// DashScope auto prefix-caches and does not accept OpenAI's `prompt_cache_key`; omit it.
|
|
47
|
+
cacheKeyParams() {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
// Reasoning arrives out-of-band as `reasoning_content`, never as inline <thinking> tags.
|
|
51
|
+
usesInlineThinkingTags() {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
requestBodyExtras(extensions) {
|
|
55
|
+
const extraBody = this.thinkingExtraBody(extensions);
|
|
56
|
+
return extraBody ? { extra_body: extraBody } : {};
|
|
57
|
+
}
|
|
58
|
+
requestExtensions(extensions) {
|
|
59
|
+
return omitExtensionKeys(extensions, [
|
|
60
|
+
"model", "messages", "tools", "stream", "stream_options", "extra_body",
|
|
61
|
+
"enableThinking", "enable_thinking", "thinkingBudget", "thinking_budget",
|
|
62
|
+
]);
|
|
73
63
|
}
|
|
74
64
|
peekProviderReplay(message) {
|
|
75
65
|
const fields = this.chat.peekReplayFields(message);
|
|
@@ -82,129 +72,6 @@ export class QwenProvider {
|
|
|
82
72
|
this.chat.rememberReplayFields(message, { reasoning_content: replay.reasoning_content });
|
|
83
73
|
}
|
|
84
74
|
}
|
|
85
|
-
async complete(context, tools, extensions) {
|
|
86
|
-
if (this.circuit.isOpen())
|
|
87
|
-
throw new Error("Circuit breaker open");
|
|
88
|
-
const msgs = this.buildChatMessages(context);
|
|
89
|
-
const extraBody = this.thinkingExtraBody(extensions);
|
|
90
|
-
let lastErr;
|
|
91
|
-
for (let i = 0; i < this.maxRetries; i++) {
|
|
92
|
-
try {
|
|
93
|
-
const resp = await this.client.chat.completions.create({
|
|
94
|
-
...this.requestExtensions(extensions),
|
|
95
|
-
model: this.model,
|
|
96
|
-
messages: msgs,
|
|
97
|
-
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
98
|
-
...(extraBody ? { extra_body: extraBody } : {}),
|
|
99
|
-
});
|
|
100
|
-
this.circuit.recordSuccess();
|
|
101
|
-
const choice = resp.choices[0].message;
|
|
102
|
-
const toolCalls = this.chat.normalizeToolCalls(choice.tool_calls ?? []);
|
|
103
|
-
return { role: "assistant", content: choice.content ?? "", tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
lastErr = err;
|
|
107
|
-
this.circuit.recordFailure();
|
|
108
|
-
if (i < this.maxRetries - 1)
|
|
109
|
-
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
throw lastErr;
|
|
113
|
-
}
|
|
114
|
-
async *stream(context, tools, extensions) {
|
|
115
|
-
const msgs = this.buildChatMessages(context);
|
|
116
|
-
const toolCallBufs = {};
|
|
117
|
-
const emittedToolCallIndexes = new Set();
|
|
118
|
-
let reasoningContent = "";
|
|
119
|
-
let finalText = "";
|
|
120
|
-
const extraBody = this.thinkingExtraBody(extensions);
|
|
121
|
-
const stream = await this.client.chat.completions.create({
|
|
122
|
-
...this.requestExtensions(extensions),
|
|
123
|
-
model: this.model,
|
|
124
|
-
messages: msgs,
|
|
125
|
-
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
126
|
-
stream: true,
|
|
127
|
-
stream_options: { include_usage: true },
|
|
128
|
-
...(extraBody ? { extra_body: extraBody } : {}),
|
|
129
|
-
});
|
|
130
|
-
let totalTokens = 0;
|
|
131
|
-
let inputTokens = 0;
|
|
132
|
-
let outputTokens = 0;
|
|
133
|
-
let cacheReadTokens = 0;
|
|
134
|
-
for await (const chunk of stream) {
|
|
135
|
-
if (chunk.usage) {
|
|
136
|
-
totalTokens = chunk.usage.total_tokens;
|
|
137
|
-
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
138
|
-
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
139
|
-
cacheReadTokens = openAICachedPromptTokens(chunk.usage);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
const choice = chunk.choices[0];
|
|
143
|
-
if (!choice)
|
|
144
|
-
continue;
|
|
145
|
-
const delta = choice.delta;
|
|
146
|
-
if (!delta)
|
|
147
|
-
continue;
|
|
148
|
-
if (delta.reasoning_content) {
|
|
149
|
-
reasoningContent += String(delta.reasoning_content);
|
|
150
|
-
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
151
|
-
}
|
|
152
|
-
if (delta.content) {
|
|
153
|
-
finalText += String(delta.content);
|
|
154
|
-
yield { type: "text_delta", delta: delta.content };
|
|
155
|
-
}
|
|
156
|
-
for (const tc of delta.tool_calls ?? []) {
|
|
157
|
-
const idx = tc.index;
|
|
158
|
-
if (!toolCallBufs[idx])
|
|
159
|
-
toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
|
|
160
|
-
if (tc.function?.name)
|
|
161
|
-
toolCallBufs[idx].name += tc.function.name;
|
|
162
|
-
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
163
|
-
}
|
|
164
|
-
if (choice.finish_reason === "tool_calls") {
|
|
165
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
166
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
167
|
-
}));
|
|
168
|
-
this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
|
|
169
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
170
|
-
const idx = Number(index);
|
|
171
|
-
if (emittedToolCallIndexes.has(idx))
|
|
172
|
-
continue;
|
|
173
|
-
let args = {};
|
|
174
|
-
try {
|
|
175
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
args = {};
|
|
179
|
-
}
|
|
180
|
-
emittedToolCallIndexes.add(idx);
|
|
181
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
186
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
187
|
-
}));
|
|
188
|
-
if (toolCalls.length || reasoningContent) {
|
|
189
|
-
this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
|
|
190
|
-
}
|
|
191
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
192
|
-
const idx = Number(index);
|
|
193
|
-
if (emittedToolCallIndexes.has(idx))
|
|
194
|
-
continue;
|
|
195
|
-
let args = {};
|
|
196
|
-
try {
|
|
197
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
args = {};
|
|
201
|
-
}
|
|
202
|
-
emittedToolCallIndexes.add(idx);
|
|
203
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
204
|
-
}
|
|
205
|
-
if (totalTokens > 0)
|
|
206
|
-
yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}) };
|
|
207
|
-
}
|
|
208
75
|
thinkingExtraBody(extensions) {
|
|
209
76
|
const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
|
|
210
77
|
const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
|
|
@@ -215,10 +82,4 @@ export class QwenProvider {
|
|
|
215
82
|
...(typeof thinkingBudget === "number" ? { thinking_budget: thinkingBudget } : {}),
|
|
216
83
|
};
|
|
217
84
|
}
|
|
218
|
-
requestExtensions(extensions) {
|
|
219
|
-
return omitExtensionKeys(extensions, [
|
|
220
|
-
"model", "messages", "tools", "stream", "stream_options", "extra_body",
|
|
221
|
-
"enableThinking", "enable_thinking", "thinkingBudget", "thinking_budget",
|
|
222
|
-
]);
|
|
223
|
-
}
|
|
224
85
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LLMProvider } from "../types.js";
|
|
2
|
+
export type ProviderRetry = {
|
|
3
|
+
maxRetries: number;
|
|
4
|
+
baseDelay: number;
|
|
5
|
+
};
|
|
6
|
+
/** Constructs a provider for one `(providerId, endpointProtocol)` pair. The lambda absorbs the
|
|
7
|
+
* per-class constructor-shape differences (e.g. AnthropicProvider takes a `{ baseURL }` options
|
|
8
|
+
* object, the rest take a positional `baseURL` string). */
|
|
9
|
+
export type ProviderMaker = (apiKey: string, model: string | undefined, retry: ProviderRetry | undefined, baseURL: string | undefined) => LLMProvider;
|
|
10
|
+
/** Build the registry key for a `(providerId, endpointProtocol)` pair. */
|
|
11
|
+
export declare function providerRegistryKey(providerId: string, protocol: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Single source of truth for which provider class backs each `(vendor, wire)` pair. Consumed by
|
|
14
|
+
* both `createProvider` (catalog) and the per-backend factory functions, so the two can no longer
|
|
15
|
+
* drift. Adding a vendor/wire = add a row here (+ its `vendor-profiles` / `endpointProfiles` data) —
|
|
16
|
+
* no dispatch branch to edit. Values are the same named classes as before, so `instanceof` holds.
|
|
17
|
+
*/
|
|
18
|
+
export declare const PROVIDER_REGISTRY: Record<string, ProviderMaker>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { AnthropicProvider } from "./anthropic.js";
|
|
2
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
3
|
+
import { OpenAIResponsesProvider } from "./openai-responses.js";
|
|
4
|
+
import { DeepSeekProvider, DeepSeekAnthropicProvider } from "./deepseek.js";
|
|
5
|
+
import { KimiProvider, KimiAnthropicProvider } from "./kimi.js";
|
|
6
|
+
import { QwenProvider, QwenAnthropicProvider } from "./qwen.js";
|
|
7
|
+
import { GLMProvider, GLMAnthropicProvider } from "./glm.js";
|
|
8
|
+
import { MiniMaxOpenAIProvider, MiniMaxAnthropicProvider } from "./minimax.js";
|
|
9
|
+
import { GeminiProvider } from "./gemini.js";
|
|
10
|
+
/** Build the registry key for a `(providerId, endpointProtocol)` pair. */
|
|
11
|
+
export function providerRegistryKey(providerId, protocol) {
|
|
12
|
+
return `${providerId}:${protocol}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Single source of truth for which provider class backs each `(vendor, wire)` pair. Consumed by
|
|
16
|
+
* both `createProvider` (catalog) and the per-backend factory functions, so the two can no longer
|
|
17
|
+
* drift. Adding a vendor/wire = add a row here (+ its `vendor-profiles` / `endpointProfiles` data) —
|
|
18
|
+
* no dispatch branch to edit. Values are the same named classes as before, so `instanceof` holds.
|
|
19
|
+
*/
|
|
20
|
+
export const PROVIDER_REGISTRY = {
|
|
21
|
+
"anthropic:anthropic-messages": (k, m, r, b) => new AnthropicProvider(k, m, r, { baseURL: b }),
|
|
22
|
+
"openai:openai-chat": (k, m, r, b) => new OpenAIChatProvider(k, m, r, b),
|
|
23
|
+
"openai:openai-responses": (k, m, r, b) => new OpenAIResponsesProvider(k, m, r, b),
|
|
24
|
+
"deepseek:openai-chat": (k, m, r, b) => new DeepSeekProvider(k, m, r, b),
|
|
25
|
+
"deepseek:anthropic-messages": (k, m, r, b) => new DeepSeekAnthropicProvider(k, m, r, b),
|
|
26
|
+
"kimi:openai-chat": (k, m, r, b) => new KimiProvider(k, m, r, b),
|
|
27
|
+
"kimi:anthropic-messages": (k, m, r, b) => new KimiAnthropicProvider(k, m, r, b),
|
|
28
|
+
"qwen:openai-chat": (k, m, r, b) => new QwenProvider(k, m, r, b),
|
|
29
|
+
"qwen:anthropic-messages": (k, m, r, b) => new QwenAnthropicProvider(k, m, r, b),
|
|
30
|
+
"glm:openai-chat": (k, m, r, b) => new GLMProvider(k, m, r, b),
|
|
31
|
+
"glm:anthropic-messages": (k, m, r, b) => new GLMAnthropicProvider(k, m, r, b),
|
|
32
|
+
"minimax:openai-chat": (k, m, r, b) => new MiniMaxOpenAIProvider(k, m, r, b),
|
|
33
|
+
"minimax:anthropic-messages": (k, m, r, b) => new MiniMaxAnthropicProvider(k, m, r, b),
|
|
34
|
+
"gemini:gemini": (k, m, r, b) => new GeminiProvider(k, m, r, b),
|
|
35
|
+
};
|