@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.
Files changed (59) hide show
  1. package/README.md +45 -21
  2. package/dist/harness/harness.d.ts +3 -2
  3. package/dist/harness/harness.js +15 -35
  4. package/dist/harness/judge.d.ts +42 -0
  5. package/dist/harness/judge.js +58 -0
  6. package/dist/harness/public.d.ts +4 -0
  7. package/dist/harness/public.js +3 -0
  8. package/dist/index.d.ts +21 -80
  9. package/dist/index.js +28 -60
  10. package/dist/kernel.d.ts +7 -1
  11. package/dist/memory/public.d.ts +4 -0
  12. package/dist/memory/public.js +3 -0
  13. package/dist/os/public.d.ts +18 -0
  14. package/dist/os/public.js +14 -0
  15. package/dist/planes/public.d.ts +13 -0
  16. package/dist/planes/public.js +9 -0
  17. package/dist/providers/anthropic-compatible.d.ts +23 -0
  18. package/dist/providers/anthropic-compatible.js +29 -0
  19. package/dist/providers/anthropic.d.ts +11 -2
  20. package/dist/providers/anthropic.js +14 -9
  21. package/dist/providers/catalog.js +5 -53
  22. package/dist/providers/deepseek.d.ts +28 -8
  23. package/dist/providers/deepseek.js +38 -157
  24. package/dist/providers/factories.d.ts +31 -0
  25. package/dist/providers/factories.js +33 -0
  26. package/dist/providers/glm.d.ts +5 -4
  27. package/dist/providers/glm.js +8 -23
  28. package/dist/providers/kimi.d.ts +5 -4
  29. package/dist/providers/kimi.js +8 -22
  30. package/dist/providers/minimax.d.ts +26 -12
  31. package/dist/providers/minimax.js +32 -158
  32. package/dist/providers/openai.d.ts +57 -2
  33. package/dist/providers/openai.js +139 -70
  34. package/dist/providers/public.d.ts +10 -0
  35. package/dist/providers/public.js +11 -0
  36. package/dist/providers/qwen.d.ts +19 -19
  37. package/dist/providers/qwen.js +37 -176
  38. package/dist/providers/registry.d.ts +18 -0
  39. package/dist/providers/registry.js +35 -0
  40. package/dist/providers/vendor-profiles.d.ts +54 -0
  41. package/dist/providers/vendor-profiles.js +62 -0
  42. package/dist/runtime/event-stream.d.ts +44 -0
  43. package/dist/runtime/event-stream.js +39 -0
  44. package/dist/runtime/facade.js +6 -1
  45. package/dist/runtime/reactive-session.d.ts +125 -0
  46. package/dist/runtime/reactive-session.js +127 -0
  47. package/dist/runtime/run-group.d.ts +74 -0
  48. package/dist/runtime/run-group.js +72 -0
  49. package/dist/runtime/runner.d.ts +9 -0
  50. package/dist/runtime/runner.js +79 -46
  51. package/dist/runtime/session-log.d.ts +8 -0
  52. package/dist/runtime/turn-policy.d.ts +33 -0
  53. package/dist/runtime/turn-policy.js +58 -0
  54. package/dist/signals/gateway.d.ts +7 -2
  55. package/dist/signals/gateway.js +13 -3
  56. package/dist/signals/types.d.ts +10 -1
  57. package/dist/workflow/public.d.ts +20 -0
  58. package/dist/workflow/public.js +15 -0
  59. package/package.json +54 -2
@@ -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
- constructor(apiKey, model = "gpt-4o", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1") {
36
- this.model = model;
37
- this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey, baseURL }));
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, extensions);
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
- prompt_cache_key: this.promptCacheKey(context, tools),
106
- ...this.requestExtensions(extensions),
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 toolCalls = this.chat.normalizeToolCalls(choice.tool_calls ?? []);
114
- return { role: "assistant", content: choice.content ?? "", tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
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 msgs = this.buildChatMessages(context, extensions);
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
- prompt_cache_key: this.promptCacheKey(context, tools),
134
- ...this.requestExtensions(extensions),
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
- yield { type: "thinking_delta", delta: delta.reasoning_content };
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
- for (const part of extractor.feed(delta.content)) {
166
- if (part.type === "thinking") {
167
- accumulatedReasoning += part.content;
168
- yield { type: "thinking_delta", delta: part.content };
169
- }
170
- else {
171
- accumulatedContent += part.content;
172
- yield { type: "text_delta", delta: part.content };
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
- const toolCalls = Object.values(toolCallBufs).map(tb => ({
186
- id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
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
- for (const part of extractor.flush()) {
206
- if (part.type === "thinking") {
207
- accumulatedReasoning += part.content;
208
- yield { type: "thinking_delta", delta: part.content };
209
- }
210
- else {
211
- accumulatedContent += part.content;
212
- yield { type: "text_delta", delta: part.content };
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";
@@ -1,37 +1,37 @@
1
- import OpenAI from "openai";
2
- import type { LLMProvider, Message, ProviderDescriptor, RenderedContext, StreamEvent, ToolSchema, RuntimePolicy, ProviderReplay } from "../types.js";
3
- import { AnthropicProvider } from "./anthropic.js";
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 AnthropicProvider {
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
- export declare class QwenProvider implements LLMProvider {
18
- protected readonly model: string;
19
- protected client: OpenAI;
20
- protected circuit: CircuitBreaker;
21
- protected maxRetries: number;
22
- protected baseDelay: number;
23
- protected readonly chat: OpenAIChatAdapter;
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
- private buildChatMessages;
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
  }
@@ -1,54 +1,29 @@
1
- import OpenAI from "openai";
2
- import { withServerRuntimeGuard } from "../runtime/server.js";
3
- import { AnthropicProvider } from "./anthropic.js";
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
- const QWEN_POLICIES = {
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 AnthropicProvider {
26
- constructor(apiKey, model = "qwen3.6-plus", retry, baseURL = endpointProfiles["qwen.anthropic"].baseURL) {
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
- export class QwenProvider {
40
- model;
41
- client;
42
- circuit;
43
- maxRetries;
44
- baseDelay;
45
- chat = new OpenAIChatAdapter();
46
- constructor(apiKey, model = "qwen3.6-plus", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = endpointProfiles["qwen.dashscope"].baseURL) {
47
- this.model = model;
48
- this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey, baseURL }));
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
- buildChatMessages(context) {
72
- return this.chat.buildMessages(context, { descriptor: this.descriptor() });
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>;