@deepstrike/sdk 0.2.28 → 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/README.md +45 -21
- 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/harness/public.d.ts +4 -0
- package/dist/harness/public.js +3 -0
- package/dist/index.d.ts +21 -80
- package/dist/index.js +28 -60
- package/dist/kernel.d.ts +7 -1
- package/dist/memory/public.d.ts +4 -0
- package/dist/memory/public.js +3 -0
- package/dist/os/public.d.ts +18 -0
- package/dist/os/public.js +14 -0
- package/dist/planes/public.d.ts +13 -0
- package/dist/planes/public.js +9 -0
- package/dist/providers/anthropic-compatible.d.ts +23 -0
- package/dist/providers/anthropic-compatible.js +29 -0
- package/dist/providers/anthropic.d.ts +11 -2
- package/dist/providers/anthropic.js +14 -9
- 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.d.ts +31 -0
- package/dist/providers/factories.js +33 -0
- 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 +57 -2
- package/dist/providers/openai.js +139 -70
- package/dist/providers/public.d.ts +10 -0
- package/dist/providers/public.js +11 -0
- 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/facade.js +6 -1
- 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 +79 -46
- 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/dist/workflow/public.d.ts +20 -0
- package/dist/workflow/public.js +15 -0
- package/package.json +54 -2
package/dist/providers/openai.js
CHANGED
|
@@ -25,19 +25,33 @@ 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
|
-
model;
|
|
30
38
|
client;
|
|
31
39
|
circuit;
|
|
32
40
|
maxRetries;
|
|
33
41
|
baseDelay;
|
|
42
|
+
model;
|
|
34
43
|
chat = new OpenAIChatAdapter();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
// Accepts either the options object (`new OpenAIProvider({ apiKey, model, baseURL })`) or the legacy
|
|
45
|
+
// positional form (still used by the backend subclasses' `super(...)` calls).
|
|
46
|
+
constructor(apiKeyOrOptions, model = "gpt-4o", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1") {
|
|
47
|
+
const o = typeof apiKeyOrOptions === "string"
|
|
48
|
+
? { apiKey: apiKeyOrOptions, model, retry, baseURL }
|
|
49
|
+
: { model: "gpt-4o", retry: { maxRetries: 3, baseDelay: 1000 }, baseURL: "https://api.openai.com/v1", ...apiKeyOrOptions };
|
|
50
|
+
this.model = o.model;
|
|
51
|
+
this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey: o.apiKey, baseURL: o.baseURL }));
|
|
38
52
|
this.circuit = new CircuitBreaker();
|
|
39
|
-
this.maxRetries = retry.maxRetries;
|
|
40
|
-
this.baseDelay = retry.baseDelay;
|
|
53
|
+
this.maxRetries = o.retry.maxRetries;
|
|
54
|
+
this.baseDelay = o.retry.baseDelay;
|
|
41
55
|
}
|
|
42
56
|
runtimePolicy() {
|
|
43
57
|
return OPENAI_POLICIES[this.model] ?? {};
|
|
@@ -70,6 +84,46 @@ export class OpenAIChatProvider {
|
|
|
70
84
|
degradeMissingReasoning: this.degradeMissingReasoningReplay(extensions),
|
|
71
85
|
});
|
|
72
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
|
+
}
|
|
73
127
|
/**
|
|
74
128
|
* Pre-flight query: would this history validate against this provider with the
|
|
75
129
|
* given extensions, without sending the request? Lets an embedder route around
|
|
@@ -95,23 +149,32 @@ export class OpenAIChatProvider {
|
|
|
95
149
|
}
|
|
96
150
|
}
|
|
97
151
|
async complete(context, tools, extensions) {
|
|
152
|
+
const prepared = this.prepareExtensions(extensions);
|
|
98
153
|
if (this.circuit.isOpen())
|
|
99
154
|
throw new Error("Circuit breaker open");
|
|
100
|
-
const msgs = this.buildChatMessages(context,
|
|
155
|
+
const msgs = this.buildChatMessages(context, prepared);
|
|
101
156
|
let lastErr;
|
|
102
157
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
103
158
|
try {
|
|
104
159
|
const resp = await this.client.chat.completions.create({
|
|
105
|
-
|
|
106
|
-
...this.requestExtensions(
|
|
160
|
+
...this.cacheKeyParams(context, tools),
|
|
161
|
+
...this.requestExtensions(prepared),
|
|
162
|
+
...this.requestBodyExtras(extensions),
|
|
107
163
|
model: this.model,
|
|
108
164
|
messages: msgs,
|
|
109
165
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
110
166
|
});
|
|
111
167
|
this.circuit.recordSuccess();
|
|
112
168
|
const choice = resp.choices[0].message;
|
|
113
|
-
const
|
|
114
|
-
|
|
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 };
|
|
115
178
|
}
|
|
116
179
|
catch (err) {
|
|
117
180
|
lastErr = err;
|
|
@@ -123,15 +186,20 @@ export class OpenAIChatProvider {
|
|
|
123
186
|
throw lastErr;
|
|
124
187
|
}
|
|
125
188
|
async *stream(context, tools, extensions, _state, signal) {
|
|
126
|
-
const
|
|
189
|
+
const prepared = this.prepareExtensions(extensions);
|
|
190
|
+
const msgs = this.buildChatMessages(context, prepared);
|
|
127
191
|
const toolCallBufs = {};
|
|
128
192
|
const emittedToolCallIndexes = new Set();
|
|
193
|
+
const useTags = this.usesInlineThinkingTags();
|
|
194
|
+
const exposeReasoning = this.exposeReasoningDelta(extensions);
|
|
129
195
|
const extractor = new ThinkingTagStreamExtractor();
|
|
130
196
|
let accumulatedReasoning = "";
|
|
197
|
+
let accumulatedReasoningDetails;
|
|
131
198
|
let accumulatedContent = "";
|
|
132
199
|
const stream = await this.client.chat.completions.create({
|
|
133
|
-
|
|
134
|
-
...this.requestExtensions(
|
|
200
|
+
...this.cacheKeyParams(context, tools),
|
|
201
|
+
...this.requestExtensions(prepared),
|
|
202
|
+
...this.requestBodyExtras(extensions),
|
|
135
203
|
model: this.model,
|
|
136
204
|
messages: msgs,
|
|
137
205
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
@@ -139,6 +207,30 @@ export class OpenAIChatProvider {
|
|
|
139
207
|
stream_options: { include_usage: true },
|
|
140
208
|
// #2-B-ii: forward the abort signal so a preempt cancels the in-flight HTTP request.
|
|
141
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
|
+
};
|
|
142
234
|
let totalTokens = 0;
|
|
143
235
|
let inputTokens = 0;
|
|
144
236
|
let outputTokens = 0;
|
|
@@ -158,20 +250,29 @@ export class OpenAIChatProvider {
|
|
|
158
250
|
if (!delta)
|
|
159
251
|
continue;
|
|
160
252
|
if (delta.reasoning_content) {
|
|
161
|
-
accumulatedReasoning += delta.reasoning_content;
|
|
162
|
-
|
|
253
|
+
accumulatedReasoning += String(delta.reasoning_content);
|
|
254
|
+
if (exposeReasoning)
|
|
255
|
+
yield { type: "thinking_delta", delta: String(delta.reasoning_content) };
|
|
163
256
|
}
|
|
257
|
+
if (delta.reasoning_details !== undefined && delta.reasoning_details !== null)
|
|
258
|
+
accumulatedReasoningDetails = delta.reasoning_details;
|
|
164
259
|
if (delta.content) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
+
}
|
|
173
270
|
}
|
|
174
271
|
}
|
|
272
|
+
else {
|
|
273
|
+
accumulatedContent += String(delta.content);
|
|
274
|
+
yield { type: "text_delta", delta: delta.content };
|
|
275
|
+
}
|
|
175
276
|
}
|
|
176
277
|
for (const tc of delta.tool_calls ?? []) {
|
|
177
278
|
const idx = tc.index;
|
|
@@ -182,56 +283,24 @@ export class OpenAIChatProvider {
|
|
|
182
283
|
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
183
284
|
}
|
|
184
285
|
if (choice.finish_reason === "tool_calls") {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}));
|
|
188
|
-
this.chat.rememberReplayFields({ content: accumulatedContent, toolCalls }, { reasoning_content: accumulatedReasoning });
|
|
189
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
190
|
-
const idx = Number(index);
|
|
191
|
-
if (emittedToolCallIndexes.has(idx))
|
|
192
|
-
continue;
|
|
193
|
-
let args = {};
|
|
194
|
-
try {
|
|
195
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
args = {};
|
|
199
|
-
}
|
|
200
|
-
emittedToolCallIndexes.add(idx);
|
|
201
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
202
|
-
}
|
|
286
|
+
rememberStream();
|
|
287
|
+
yield* emitPendingToolCalls();
|
|
203
288
|
}
|
|
204
289
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
216
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
217
|
-
}));
|
|
218
|
-
if (toolCalls.length || accumulatedReasoning) {
|
|
219
|
-
this.chat.rememberReplayFields({ content: accumulatedContent, toolCalls }, { reasoning_content: accumulatedReasoning });
|
|
220
|
-
}
|
|
221
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
222
|
-
const idx = Number(index);
|
|
223
|
-
if (emittedToolCallIndexes.has(idx))
|
|
224
|
-
continue;
|
|
225
|
-
let args = {};
|
|
226
|
-
try {
|
|
227
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
args = {};
|
|
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
|
+
}
|
|
231
300
|
}
|
|
232
|
-
emittedToolCallIndexes.add(idx);
|
|
233
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
234
301
|
}
|
|
302
|
+
rememberStream();
|
|
303
|
+
yield* emitPendingToolCalls();
|
|
235
304
|
if (totalTokens > 0)
|
|
236
305
|
yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}) };
|
|
237
306
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { deepseek, kimi, qwen, glm, minimax, gemini, ollama } from "./factories.js";
|
|
2
|
+
export type { BackendProviderOptions } from "./factories.js";
|
|
3
|
+
export { OpenAIChatProvider } from "./openai.js";
|
|
4
|
+
export { CircuitBreaker } from "./base.js";
|
|
5
|
+
export { OpenAIResponsesAdapter } from "./openai-responses.js";
|
|
6
|
+
export type { OpenAIResponsesRunState } from "./openai-responses.js";
|
|
7
|
+
export { OpenAIChatAdapter } from "./openai-chat.js";
|
|
8
|
+
export { endpointProfiles, modelProfiles, getModelProfile } from "./profiles.js";
|
|
9
|
+
export type { ModelProfileId, ProviderId } from "./profiles.js";
|
|
10
|
+
export type { ProviderRunState, ProviderToolSpec, ProviderReplay, RenderedContext, CacheBreakpointStrategy } from "../types.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// `@deepstrike/sdk/providers` — backend provider factories, profiles, and provider-authoring types.
|
|
2
|
+
// The root package exports `createProvider` + the 3 base providers (Anthropic / OpenAI / OpenAIResponses);
|
|
3
|
+
// every other backend is a factory here. One function per backend (with a `protocol` option where a
|
|
4
|
+
// backend speaks both wires) replaces the old dual `<Backend>Provider`/`<Backend>AnthropicProvider` classes.
|
|
5
|
+
export { deepseek, kimi, qwen, glm, minimax, gemini, ollama } from "./factories.js";
|
|
6
|
+
// `OpenAIChatProvider` is the base OpenAI-compatible class advanced users compose/extend directly.
|
|
7
|
+
export { OpenAIChatProvider } from "./openai.js";
|
|
8
|
+
export { CircuitBreaker } from "./base.js";
|
|
9
|
+
export { OpenAIResponsesAdapter } from "./openai-responses.js";
|
|
10
|
+
export { OpenAIChatAdapter } from "./openai-chat.js";
|
|
11
|
+
export { endpointProfiles, modelProfiles, getModelProfile } from "./profiles.js";
|
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>;
|