@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.
Files changed (42) hide show
  1. package/dist/harness/harness.d.ts +3 -2
  2. package/dist/harness/harness.js +15 -35
  3. package/dist/harness/judge.d.ts +42 -0
  4. package/dist/harness/judge.js +58 -0
  5. package/dist/index.d.ts +8 -0
  6. package/dist/index.js +4 -0
  7. package/dist/kernel.d.ts +7 -1
  8. package/dist/providers/anthropic-compatible.d.ts +23 -0
  9. package/dist/providers/anthropic-compatible.js +29 -0
  10. package/dist/providers/catalog.js +5 -53
  11. package/dist/providers/deepseek.d.ts +28 -8
  12. package/dist/providers/deepseek.js +38 -157
  13. package/dist/providers/factories.js +10 -22
  14. package/dist/providers/glm.d.ts +5 -4
  15. package/dist/providers/glm.js +8 -23
  16. package/dist/providers/kimi.d.ts +5 -4
  17. package/dist/providers/kimi.js +8 -22
  18. package/dist/providers/minimax.d.ts +26 -12
  19. package/dist/providers/minimax.js +32 -158
  20. package/dist/providers/openai.d.ts +43 -0
  21. package/dist/providers/openai.js +128 -64
  22. package/dist/providers/qwen.d.ts +19 -19
  23. package/dist/providers/qwen.js +37 -176
  24. package/dist/providers/registry.d.ts +18 -0
  25. package/dist/providers/registry.js +35 -0
  26. package/dist/providers/vendor-profiles.d.ts +54 -0
  27. package/dist/providers/vendor-profiles.js +62 -0
  28. package/dist/runtime/event-stream.d.ts +44 -0
  29. package/dist/runtime/event-stream.js +39 -0
  30. package/dist/runtime/reactive-session.d.ts +125 -0
  31. package/dist/runtime/reactive-session.js +127 -0
  32. package/dist/runtime/run-group.d.ts +74 -0
  33. package/dist/runtime/run-group.js +72 -0
  34. package/dist/runtime/runner.d.ts +9 -0
  35. package/dist/runtime/runner.js +56 -7
  36. package/dist/runtime/session-log.d.ts +8 -0
  37. package/dist/runtime/turn-policy.d.ts +33 -0
  38. package/dist/runtime/turn-policy.js +58 -0
  39. package/dist/signals/gateway.d.ts +7 -2
  40. package/dist/signals/gateway.js +13 -3
  41. package/dist/signals/types.d.ts +10 -1
  42. package/package.json +2 -2
@@ -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, extensions);
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
- prompt_cache_key: this.promptCacheKey(context, tools),
111
- ...this.requestExtensions(extensions),
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 toolCalls = this.chat.normalizeToolCalls(choice.tool_calls ?? []);
119
- 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 };
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 msgs = this.buildChatMessages(context, extensions);
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
- prompt_cache_key: this.promptCacheKey(context, tools),
139
- ...this.requestExtensions(extensions),
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
- 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) };
168
256
  }
257
+ if (delta.reasoning_details !== undefined && delta.reasoning_details !== null)
258
+ accumulatedReasoningDetails = delta.reasoning_details;
169
259
  if (delta.content) {
170
- for (const part of extractor.feed(delta.content)) {
171
- if (part.type === "thinking") {
172
- accumulatedReasoning += part.content;
173
- yield { type: "thinking_delta", delta: part.content };
174
- }
175
- else {
176
- accumulatedContent += part.content;
177
- 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
+ }
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
- const toolCalls = Object.values(toolCallBufs).map(tb => ({
191
- id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
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
- for (const [index, tb] of Object.entries(toolCallBufs)) {
227
- const idx = Number(index);
228
- if (emittedToolCallIndexes.has(idx))
229
- continue;
230
- let args = {};
231
- try {
232
- args = JSON.parse(tb.argsBuf || "{}");
233
- }
234
- catch {
235
- 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
+ }
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
  }
@@ -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>;
@@ -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
+ };