@deepstrike/sdk 0.2.30 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/harness/harness.d.ts +3 -2
- package/dist/harness/harness.js +15 -35
- package/dist/harness/judge.d.ts +42 -0
- package/dist/harness/judge.js +58 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- package/dist/kernel.d.ts +7 -1
- package/dist/providers/anthropic-compatible.d.ts +23 -0
- package/dist/providers/anthropic-compatible.js +29 -0
- package/dist/providers/catalog.js +5 -53
- package/dist/providers/deepseek.d.ts +28 -8
- package/dist/providers/deepseek.js +38 -157
- package/dist/providers/factories.js +10 -22
- package/dist/providers/gemini.d.ts +12 -0
- package/dist/providers/gemini.js +38 -3
- package/dist/providers/glm.d.ts +7 -4
- package/dist/providers/glm.js +27 -24
- 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 +33 -159
- package/dist/providers/openai-responses.d.ts +6 -0
- package/dist/providers/openai-responses.js +22 -2
- package/dist/providers/openai.d.ts +52 -0
- package/dist/providers/openai.js +145 -66
- package/dist/providers/profiles.d.ts +60 -0
- package/dist/providers/profiles.js +22 -0
- package/dist/providers/qwen.d.ts +20 -19
- package/dist/providers/qwen.js +49 -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 +66 -0
- package/dist/runtime/event-stream.d.ts +44 -0
- package/dist/runtime/event-stream.js +39 -0
- package/dist/runtime/reactive-session.d.ts +125 -0
- package/dist/runtime/reactive-session.js +127 -0
- package/dist/runtime/run-group.d.ts +74 -0
- package/dist/runtime/run-group.js +72 -0
- package/dist/runtime/runner.d.ts +9 -0
- package/dist/runtime/runner.js +56 -7
- package/dist/runtime/session-log.d.ts +8 -0
- package/dist/runtime/turn-policy.d.ts +33 -0
- package/dist/runtime/turn-policy.js +58 -0
- package/dist/signals/gateway.d.ts +7 -2
- package/dist/signals/gateway.js +13 -3
- package/dist/signals/types.d.ts +10 -1
- package/package.json +2 -2
|
@@ -126,10 +126,11 @@ export interface HarnessLoopOptions {
|
|
|
126
126
|
}
|
|
127
127
|
export declare class HarnessLoop {
|
|
128
128
|
private runner;
|
|
129
|
-
private evalProvider;
|
|
130
129
|
private maxAttempts;
|
|
131
130
|
private skillDir?;
|
|
132
|
-
|
|
131
|
+
/** How each attempt is judged. Hybrid (host verdictFn → LLM eval) when a verdictFn is supplied,
|
|
132
|
+
* otherwise the built-in LLM eval. The loop just calls `this.judge.judge(...)`. */
|
|
133
|
+
private judge;
|
|
133
134
|
constructor(runner: RuntimeRunner, evalProvider: import("../types.js").LLMProvider, options?: HarnessLoopOptions);
|
|
134
135
|
run(request: HarnessRequest): Promise<HarnessOutcome>;
|
|
135
136
|
stream(request: HarnessRequest): AsyncIterable<HarnessEvent>;
|
package/dist/harness/harness.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { collectText } from "../runtime/runner.js";
|
|
2
2
|
import { writeFile } from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { HybridJudge, VerdictFnJudge, LlmEvalJudge } from "./judge.js";
|
|
5
5
|
async function runOnce(runner, req) {
|
|
6
6
|
let text = "";
|
|
7
7
|
let done;
|
|
@@ -58,16 +58,19 @@ export class EvalLoopHarness {
|
|
|
58
58
|
}
|
|
59
59
|
export class HarnessLoop {
|
|
60
60
|
runner;
|
|
61
|
-
evalProvider;
|
|
62
61
|
maxAttempts;
|
|
63
62
|
skillDir;
|
|
64
|
-
verdictFn
|
|
63
|
+
/** How each attempt is judged. Hybrid (host verdictFn → LLM eval) when a verdictFn is supplied,
|
|
64
|
+
* otherwise the built-in LLM eval. The loop just calls `this.judge.judge(...)`. */
|
|
65
|
+
judge;
|
|
65
66
|
constructor(runner, evalProvider, options = {}) {
|
|
66
67
|
this.runner = runner;
|
|
67
|
-
this.evalProvider = evalProvider;
|
|
68
68
|
this.maxAttempts = options.maxAttempts ?? 3;
|
|
69
69
|
this.skillDir = options.skillDir;
|
|
70
|
-
|
|
70
|
+
const llmJudge = new LlmEvalJudge(evalProvider);
|
|
71
|
+
this.judge = options.verdictFn
|
|
72
|
+
? new HybridJudge(new VerdictFnJudge(options.verdictFn), llmJudge)
|
|
73
|
+
: llmJudge;
|
|
71
74
|
}
|
|
72
75
|
async run(request) {
|
|
73
76
|
let last;
|
|
@@ -93,7 +96,6 @@ export class HarnessLoop {
|
|
|
93
96
|
};
|
|
94
97
|
}
|
|
95
98
|
async *stream(request) {
|
|
96
|
-
const kernel = getKernel();
|
|
97
99
|
const criteria = request.criteria ?? [];
|
|
98
100
|
let currentGoal = request.goal;
|
|
99
101
|
let lastIterations = 0;
|
|
@@ -135,35 +137,13 @@ export class HarnessLoop {
|
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
yield { type: "supervising" };
|
|
138
|
-
// I3.2 (A2/A3): host
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (!verdict) {
|
|
146
|
-
// #6 (0.5.0): the eval/verdict compute is the kernel's stateless free functions (was the
|
|
147
|
-
// EvalPipeline state machine). Build the eval prompt, call the eval LLM, parse the verdict.
|
|
148
|
-
const evalMsgs = kernel.buildEvalMessages(request.goal, criteria, lastResult, attempt, true);
|
|
149
|
-
let evalText = "";
|
|
150
|
-
const evalContext = {
|
|
151
|
-
systemText: evalMsgs.filter((m) => m.role === "system").map((m) => m.content).join("\n\n"),
|
|
152
|
-
turns: evalMsgs.filter((m) => m.role !== "system"),
|
|
153
|
-
};
|
|
154
|
-
for await (const evt of this.evalProvider.stream(evalContext, [], undefined)) {
|
|
155
|
-
if (evt.type === "text_delta")
|
|
156
|
-
evalText += evt.delta;
|
|
157
|
-
}
|
|
158
|
-
const parsed = kernel.parseVerdict(evalText);
|
|
159
|
-
verdict = {
|
|
160
|
-
passed: parsed.passed,
|
|
161
|
-
overallScore: parsed.overallScore,
|
|
162
|
-
feedback: parsed.feedback,
|
|
163
|
-
details: parsed.details ?? [],
|
|
164
|
-
};
|
|
165
|
-
skillCandidate = parsed.skillCandidate;
|
|
166
|
-
}
|
|
140
|
+
// I3.2 (A2/A3): the judge Strategy encapsulates "host verdictFn short-circuit → built-in LLM
|
|
141
|
+
// eval". HarnessLoop's judge always terminates in LlmEvalJudge, so a verdict is guaranteed.
|
|
142
|
+
const judged = await this.judge.judge({ goal: request.goal, criteria, attempt, result: lastResult });
|
|
143
|
+
const verdict = judged?.verdict;
|
|
144
|
+
const skillCandidate = judged?.skillCandidate;
|
|
145
|
+
if (!verdict)
|
|
146
|
+
throw new Error("HarnessLoop: judge produced no verdict");
|
|
167
147
|
if (verdict.passed) {
|
|
168
148
|
if (skillCandidate && this.skillDir) {
|
|
169
149
|
const { name, description, whenToUse, content } = skillCandidate;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { LLMProvider } from "../types.js";
|
|
2
|
+
import { getKernel } from "../kernel.js";
|
|
3
|
+
import type { Criterion, Verdict, VerdictFn } from "./harness.js";
|
|
4
|
+
export type SkillCandidate = ReturnType<ReturnType<typeof getKernel>["parseVerdict"]>["skillCandidate"];
|
|
5
|
+
export interface JudgeContext {
|
|
6
|
+
goal: string;
|
|
7
|
+
criteria: Criterion[];
|
|
8
|
+
attempt: number;
|
|
9
|
+
result: string;
|
|
10
|
+
}
|
|
11
|
+
export interface JudgeResult {
|
|
12
|
+
verdict: Verdict;
|
|
13
|
+
/** Skill the judge proposes extracting on pass (LLM-eval path only). */
|
|
14
|
+
skillCandidate?: SkillCandidate;
|
|
15
|
+
}
|
|
16
|
+
/** Decides whether one attempt's result meets the criteria. Returning `undefined` defers to a
|
|
17
|
+
* fallback judge (enables hybrid host/LLM judgment). */
|
|
18
|
+
export interface AttemptJudge {
|
|
19
|
+
judge(ctx: JudgeContext): Promise<JudgeResult | undefined>;
|
|
20
|
+
}
|
|
21
|
+
/** Wraps a host-supplied `VerdictFn`. Returns `undefined` (defer) when the function does. */
|
|
22
|
+
export declare class VerdictFnJudge implements AttemptJudge {
|
|
23
|
+
private readonly fn;
|
|
24
|
+
constructor(fn: VerdictFn);
|
|
25
|
+
judge(ctx: JudgeContext): Promise<JudgeResult | undefined>;
|
|
26
|
+
}
|
|
27
|
+
/** The built-in LLM eval: render the kernel eval prompt, stream the eval provider, parse the
|
|
28
|
+
* verdict. Always produces a JudgeResult (never defers). */
|
|
29
|
+
export declare class LlmEvalJudge implements AttemptJudge {
|
|
30
|
+
private readonly evalProvider;
|
|
31
|
+
private readonly extractSkillOnPass;
|
|
32
|
+
constructor(evalProvider: LLMProvider, extractSkillOnPass?: boolean);
|
|
33
|
+
judge(ctx: JudgeContext): Promise<JudgeResult>;
|
|
34
|
+
}
|
|
35
|
+
/** Try `primary`; if it defers (`undefined`), use `fallback`. Models HarnessLoop's
|
|
36
|
+
* "verdictFn short-circuits, else built-in LLM eval" hybrid judgment. */
|
|
37
|
+
export declare class HybridJudge implements AttemptJudge {
|
|
38
|
+
private readonly primary;
|
|
39
|
+
private readonly fallback;
|
|
40
|
+
constructor(primary: AttemptJudge, fallback: AttemptJudge);
|
|
41
|
+
judge(ctx: JudgeContext): Promise<JudgeResult | undefined>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getKernel } from "../kernel.js";
|
|
2
|
+
/** Wraps a host-supplied `VerdictFn`. Returns `undefined` (defer) when the function does. */
|
|
3
|
+
export class VerdictFnJudge {
|
|
4
|
+
fn;
|
|
5
|
+
constructor(fn) {
|
|
6
|
+
this.fn = fn;
|
|
7
|
+
}
|
|
8
|
+
async judge(ctx) {
|
|
9
|
+
const verdict = await this.fn({ goal: ctx.goal, criteria: ctx.criteria, attempt: ctx.attempt, result: ctx.result });
|
|
10
|
+
return verdict ? { verdict } : undefined;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** The built-in LLM eval: render the kernel eval prompt, stream the eval provider, parse the
|
|
14
|
+
* verdict. Always produces a JudgeResult (never defers). */
|
|
15
|
+
export class LlmEvalJudge {
|
|
16
|
+
evalProvider;
|
|
17
|
+
extractSkillOnPass;
|
|
18
|
+
constructor(evalProvider, extractSkillOnPass = true) {
|
|
19
|
+
this.evalProvider = evalProvider;
|
|
20
|
+
this.extractSkillOnPass = extractSkillOnPass;
|
|
21
|
+
}
|
|
22
|
+
async judge(ctx) {
|
|
23
|
+
const kernel = getKernel();
|
|
24
|
+
const evalMsgs = kernel.buildEvalMessages(ctx.goal, ctx.criteria, ctx.result, ctx.attempt, this.extractSkillOnPass);
|
|
25
|
+
const evalContext = {
|
|
26
|
+
systemText: evalMsgs.filter((m) => m.role === "system").map((m) => m.content).join("\n\n"),
|
|
27
|
+
turns: evalMsgs.filter((m) => m.role !== "system"),
|
|
28
|
+
};
|
|
29
|
+
let evalText = "";
|
|
30
|
+
for await (const evt of this.evalProvider.stream(evalContext, [], undefined)) {
|
|
31
|
+
if (evt.type === "text_delta")
|
|
32
|
+
evalText += evt.delta;
|
|
33
|
+
}
|
|
34
|
+
const parsed = kernel.parseVerdict(evalText);
|
|
35
|
+
return {
|
|
36
|
+
verdict: {
|
|
37
|
+
passed: parsed.passed,
|
|
38
|
+
overallScore: parsed.overallScore,
|
|
39
|
+
feedback: parsed.feedback,
|
|
40
|
+
details: parsed.details ?? [],
|
|
41
|
+
},
|
|
42
|
+
skillCandidate: parsed.skillCandidate,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Try `primary`; if it defers (`undefined`), use `fallback`. Models HarnessLoop's
|
|
47
|
+
* "verdictFn short-circuits, else built-in LLM eval" hybrid judgment. */
|
|
48
|
+
export class HybridJudge {
|
|
49
|
+
primary;
|
|
50
|
+
fallback;
|
|
51
|
+
constructor(primary, fallback) {
|
|
52
|
+
this.primary = primary;
|
|
53
|
+
this.fallback = fallback;
|
|
54
|
+
}
|
|
55
|
+
async judge(ctx) {
|
|
56
|
+
return (await this.primary.judge(ctx)) ?? (await this.fallback.judge(ctx));
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,14 @@ export { LocalExecutionPlane } from "./runtime/execution-plane.js";
|
|
|
6
6
|
export type { ExecutionPlane, RunContext } from "./runtime/execution-plane.js";
|
|
7
7
|
export { InMemorySessionLog, FileSessionLog } from "./runtime/session-log.js";
|
|
8
8
|
export type { SessionLog, SessionEvent } from "./runtime/session-log.js";
|
|
9
|
+
export { InMemoryGroupBudgetStore, SessionLogGroupBudgetStore } from "./runtime/run-group.js";
|
|
10
|
+
export type { RunGroup, GroupBudgetStore, GroupLedger, GroupCharge, GroupMember } from "./runtime/run-group.js";
|
|
11
|
+
export { InMemoryEventStream, isVisibleTo } from "./runtime/event-stream.js";
|
|
12
|
+
export type { EventStream, BlackboardEvent, EventViewer } from "./runtime/event-stream.js";
|
|
13
|
+
export { reactByMention, directorDriven, roundRobin, firstNonEmpty, union } from "./runtime/turn-policy.js";
|
|
14
|
+
export type { TurnPolicy, PeerView } from "./runtime/turn-policy.js";
|
|
15
|
+
export { ReactiveSession, readRecentTool } from "./runtime/reactive-session.js";
|
|
16
|
+
export type { ReactiveSessionOptions, ReactivePeerSpec, EmitEvent, Reaction, ReactorTurn, ReactorContext } from "./runtime/reactive-session.js";
|
|
9
17
|
export { tool, streamingTool } from "./tools/index.js";
|
|
10
18
|
export type { RegisteredTool, ToolExecContext } from "./tools/index.js";
|
|
11
19
|
export { safeTool, ok, fail, ToolError, formatToolError } from "./tools/errors.js";
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,10 @@ export { RuntimeRunner, collectText } from "./runtime/runner.js";
|
|
|
16
16
|
// ── Execution plane + session log (the defaults) ────────────────────────────
|
|
17
17
|
export { LocalExecutionPlane } from "./runtime/execution-plane.js";
|
|
18
18
|
export { InMemorySessionLog, FileSessionLog } from "./runtime/session-log.js";
|
|
19
|
+
export { InMemoryGroupBudgetStore, SessionLogGroupBudgetStore } from "./runtime/run-group.js";
|
|
20
|
+
export { InMemoryEventStream, isVisibleTo } from "./runtime/event-stream.js";
|
|
21
|
+
export { reactByMention, directorDriven, roundRobin, firstNonEmpty, union } from "./runtime/turn-policy.js";
|
|
22
|
+
export { ReactiveSession, readRecentTool } from "./runtime/reactive-session.js";
|
|
19
23
|
// ── Tool authoring ──────────────────────────────────────────────────────────
|
|
20
24
|
export { tool, streamingTool } from "./tools/index.js";
|
|
21
25
|
export { safeTool, ok, fail, ToolError, formatToolError } from "./tools/errors.js";
|
package/dist/kernel.d.ts
CHANGED
|
@@ -17,8 +17,12 @@ export interface MemoryWriteRateLimit {
|
|
|
17
17
|
windowMs: number;
|
|
18
18
|
}
|
|
19
19
|
export interface ResourceQuota {
|
|
20
|
-
/** Max sub-agents in the `running` state at once; further spawns are denied while at cap.
|
|
20
|
+
/** Max sub-agents in the `running` state at once; further spawns are denied while at cap.
|
|
21
|
+
* Instantaneous — vehicle-scoped (cannot span stateless replicas). */
|
|
21
22
|
maxConcurrentSubagents?: number;
|
|
23
|
+
/** L1 (RunGroup): max sub-agents spawned *cumulatively* across the governance domain. With a
|
|
24
|
+
* `runGroup`, this spans N stateless top-level runs (seeded/charged via the group ledger). */
|
|
25
|
+
maxTotalSubagents?: number;
|
|
22
26
|
/** Max sub-agent nesting depth (direct children of the root loop are depth 1). */
|
|
23
27
|
maxSpawnDepth?: number;
|
|
24
28
|
/** Rolling-window memory-write rate limit: at most `maxWrites` per any `windowMs` span. */
|
|
@@ -135,6 +139,8 @@ export interface KernelRuntimeInstance {
|
|
|
135
139
|
step(inputJson: string): string;
|
|
136
140
|
isTerminal(): boolean;
|
|
137
141
|
turn(): number;
|
|
142
|
+
/** L1 (RunGroup): cumulative sub-agent spawns this run, for charging the group ledger at run end. */
|
|
143
|
+
localSubagentsSpawned(): number;
|
|
138
144
|
recoveryContentBytes(): number;
|
|
139
145
|
render(): RenderedContext;
|
|
140
146
|
drainNewMessages(): Message[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { RuntimePolicy } from "../types.js";
|
|
2
|
+
import { AnthropicProvider } from "./anthropic.js";
|
|
3
|
+
import { type AnthropicVendorProfile } from "./vendor-profiles.js";
|
|
4
|
+
/**
|
|
5
|
+
* A vendor that exposes an Anthropic-compatible Messages endpoint (DeepSeek,
|
|
6
|
+
* Kimi, Qwen, GLM, MiniMax, …). All wire behavior is inherited from
|
|
7
|
+
* `AnthropicProvider`; the only per-vendor variation is configuration, supplied
|
|
8
|
+
* as an `AnthropicVendorProfile`. This replaces the family of near-identical
|
|
9
|
+
* `<Vendor>AnthropicProvider` subclasses that existed only to carry that config.
|
|
10
|
+
*
|
|
11
|
+
* Adding a new Anthropic-compatible vendor is now "add a profile" — no new
|
|
12
|
+
* provider class is required (the named `<Vendor>AnthropicProvider` shims are
|
|
13
|
+
* kept only for backward compatibility / `instanceof` checks).
|
|
14
|
+
*/
|
|
15
|
+
export declare class AnthropicCompatibleProvider extends AnthropicProvider {
|
|
16
|
+
private readonly vendorProfile;
|
|
17
|
+
constructor(profile: AnthropicVendorProfile, apiKey: string, model?: string, retry?: {
|
|
18
|
+
maxRetries: number;
|
|
19
|
+
baseDelay: number;
|
|
20
|
+
}, baseURL?: string);
|
|
21
|
+
protected providerName(): string;
|
|
22
|
+
runtimePolicy(): RuntimePolicy;
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AnthropicProvider } from "./anthropic.js";
|
|
2
|
+
import { anthropicVendorBaseURL } from "./vendor-profiles.js";
|
|
3
|
+
/**
|
|
4
|
+
* A vendor that exposes an Anthropic-compatible Messages endpoint (DeepSeek,
|
|
5
|
+
* Kimi, Qwen, GLM, MiniMax, …). All wire behavior is inherited from
|
|
6
|
+
* `AnthropicProvider`; the only per-vendor variation is configuration, supplied
|
|
7
|
+
* as an `AnthropicVendorProfile`. This replaces the family of near-identical
|
|
8
|
+
* `<Vendor>AnthropicProvider` subclasses that existed only to carry that config.
|
|
9
|
+
*
|
|
10
|
+
* Adding a new Anthropic-compatible vendor is now "add a profile" — no new
|
|
11
|
+
* provider class is required (the named `<Vendor>AnthropicProvider` shims are
|
|
12
|
+
* kept only for backward compatibility / `instanceof` checks).
|
|
13
|
+
*/
|
|
14
|
+
export class AnthropicCompatibleProvider extends AnthropicProvider {
|
|
15
|
+
vendorProfile;
|
|
16
|
+
constructor(profile, apiKey, model, retry, baseURL) {
|
|
17
|
+
super(apiKey, model ?? profile.defaultModel, retry, {
|
|
18
|
+
baseURL: baseURL ?? anthropicVendorBaseURL(profile),
|
|
19
|
+
authMode: "api-key",
|
|
20
|
+
});
|
|
21
|
+
this.vendorProfile = profile;
|
|
22
|
+
}
|
|
23
|
+
providerName() {
|
|
24
|
+
return this.vendorProfile.providerId;
|
|
25
|
+
}
|
|
26
|
+
runtimePolicy() {
|
|
27
|
+
return this.vendorProfile.policies[this.model] ?? {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { OpenAIChatProvider } from "./openai.js";
|
|
3
|
-
import { DeepSeekProvider, DeepSeekAnthropicProvider } from "./deepseek.js";
|
|
4
|
-
import { KimiProvider, KimiAnthropicProvider } from "./kimi.js";
|
|
5
|
-
import { OpenAIResponsesProvider } from "./openai-responses.js";
|
|
6
|
-
import { MiniMaxAnthropicProvider, MiniMaxOpenAIProvider } from "./minimax.js";
|
|
7
|
-
import { QwenProvider, QwenAnthropicProvider } from "./qwen.js";
|
|
8
|
-
import { GeminiProvider } from "./gemini.js";
|
|
9
|
-
import { GLMProvider, GLMAnthropicProvider } from "./glm.js";
|
|
1
|
+
import { PROVIDER_REGISTRY, providerRegistryKey } from "./registry.js";
|
|
10
2
|
import { endpointProfiles, getModelProfile, modelProfiles } from "./profiles.js";
|
|
11
3
|
export function createProvider(options) {
|
|
12
4
|
const profile = isModelProfileId(options.model) ? getModelProfile(options.model) : undefined;
|
|
@@ -31,50 +23,10 @@ export function createProvider(options) {
|
|
|
31
23
|
}
|
|
32
24
|
const model = modelNameForProvider(options.model, providerId);
|
|
33
25
|
const baseURL = options.baseURL ?? endpoint.baseURL;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (endpoint.protocol === "openai-chat") {
|
|
39
|
-
return new OpenAIChatProvider(options.apiKey, model, options.retry, baseURL);
|
|
40
|
-
}
|
|
41
|
-
if (endpoint.protocol === "openai-responses") {
|
|
42
|
-
return new OpenAIResponsesProvider(options.apiKey, model, options.retry, baseURL);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (providerId === "minimax" && endpoint.protocol === "anthropic-messages") {
|
|
46
|
-
return new MiniMaxAnthropicProvider(options.apiKey, model, options.retry, baseURL);
|
|
47
|
-
}
|
|
48
|
-
if (providerId === "minimax" && endpoint.protocol === "openai-chat") {
|
|
49
|
-
return new MiniMaxOpenAIProvider(options.apiKey, model, options.retry, baseURL);
|
|
50
|
-
}
|
|
51
|
-
if (providerId === "deepseek" && endpoint.protocol === "anthropic-messages") {
|
|
52
|
-
return new DeepSeekAnthropicProvider(options.apiKey, model, options.retry, baseURL);
|
|
53
|
-
}
|
|
54
|
-
if (providerId === "deepseek" && endpoint.protocol === "openai-chat") {
|
|
55
|
-
return new DeepSeekProvider(options.apiKey, model, options.retry, baseURL);
|
|
56
|
-
}
|
|
57
|
-
if (providerId === "kimi" && endpoint.protocol === "anthropic-messages") {
|
|
58
|
-
return new KimiAnthropicProvider(options.apiKey, model, options.retry, baseURL);
|
|
59
|
-
}
|
|
60
|
-
if (providerId === "kimi" && endpoint.protocol === "openai-chat") {
|
|
61
|
-
return new KimiProvider(options.apiKey, model, options.retry, baseURL);
|
|
62
|
-
}
|
|
63
|
-
if (providerId === "qwen" && endpoint.protocol === "anthropic-messages") {
|
|
64
|
-
return new QwenAnthropicProvider(options.apiKey, model, options.retry, baseURL);
|
|
65
|
-
}
|
|
66
|
-
if (providerId === "qwen" && endpoint.protocol === "openai-chat") {
|
|
67
|
-
return new QwenProvider(options.apiKey, model, options.retry, baseURL);
|
|
68
|
-
}
|
|
69
|
-
if (providerId === "gemini" && endpoint.protocol === "gemini") {
|
|
70
|
-
return new GeminiProvider(options.apiKey, model, options.retry, baseURL);
|
|
71
|
-
}
|
|
72
|
-
if (providerId === "glm" && endpoint.protocol === "anthropic-messages") {
|
|
73
|
-
return new GLMAnthropicProvider(options.apiKey, model, options.retry, baseURL);
|
|
74
|
-
}
|
|
75
|
-
if (providerId === "glm" && endpoint.protocol === "openai-chat") {
|
|
76
|
-
return new GLMProvider(options.apiKey, model, options.retry, baseURL);
|
|
77
|
-
}
|
|
26
|
+
// Single data-driven dispatch: one registry keyed by (providerId, protocol).
|
|
27
|
+
const make = PROVIDER_REGISTRY[providerRegistryKey(providerId, endpoint.protocol)];
|
|
28
|
+
if (make)
|
|
29
|
+
return make(options.apiKey, model, options.retry, baseURL);
|
|
78
30
|
throw new Error(`No Node provider factory for ${options.model} on ${endpoint.id}`);
|
|
79
31
|
}
|
|
80
32
|
function isModelProfileId(model) {
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { ProviderDescriptor, RuntimePolicy } from "../types.js";
|
|
2
|
+
import { OpenAIChatProvider, type OpenAIChatTurnReasoning } from "./openai.js";
|
|
3
|
+
import { AnthropicCompatibleProvider } from "./anthropic-compatible.js";
|
|
4
4
|
/**
|
|
5
5
|
* DeepSeek over its Anthropic-compatible endpoint.
|
|
6
|
+
* @deprecated Prefer `deepseek({ protocol: "anthropic" })`. Behavior is now fully
|
|
7
|
+
* data-driven via `anthropicVendorProfiles.deepseek`; this thin shim is kept for
|
|
8
|
+
* backward compatibility and `instanceof` checks.
|
|
6
9
|
*/
|
|
7
|
-
export declare class DeepSeekAnthropicProvider extends
|
|
10
|
+
export declare class DeepSeekAnthropicProvider extends AnthropicCompatibleProvider {
|
|
8
11
|
constructor(apiKey: string, model?: string, retry?: {
|
|
9
12
|
maxRetries: number;
|
|
10
13
|
baseDelay: number;
|
|
11
14
|
}, baseURL?: string);
|
|
12
|
-
protected providerName(): string;
|
|
13
|
-
runtimePolicy(): RuntimePolicy;
|
|
14
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* DeepSeek over its OpenAI-compatible endpoint. Reasoning is carried out-of-band as
|
|
18
|
+
* `reasoning_content`; replay persists DeepSeek's schema_version-2 envelope (with the
|
|
19
|
+
* native `tool_calls` blocks). Request shaping (`reasoning_effort` + `extra_body.thinking`)
|
|
20
|
+
* and replay are supplied via the OpenAIChatProvider Template-Method hooks; the streaming /
|
|
21
|
+
* tool-call machinery is inherited from the base class.
|
|
22
|
+
*/
|
|
15
23
|
export declare class DeepSeekProvider extends OpenAIChatProvider {
|
|
16
24
|
constructor(apiKey: string, model?: string, retry?: {
|
|
17
25
|
maxRetries: number;
|
|
@@ -20,7 +28,19 @@ export declare class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
20
28
|
runtimePolicy(): RuntimePolicy;
|
|
21
29
|
descriptor(): ProviderDescriptor;
|
|
22
30
|
protected requireNonEmptyReasoningReplayForToolTurns(extensions?: Record<string, unknown>): boolean;
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
protected cacheKeyParams(): Record<string, unknown>;
|
|
32
|
+
protected usesInlineThinkingTags(): boolean;
|
|
33
|
+
protected exposeReasoningDelta(extensions?: Record<string, unknown>): boolean;
|
|
34
|
+
protected prepareExtensions(extensions?: Record<string, unknown>): Record<string, unknown>;
|
|
35
|
+
protected rememberCompleteReplay(content: string, toolCalls: Array<{
|
|
36
|
+
id: string;
|
|
37
|
+
name: string;
|
|
38
|
+
arguments: string;
|
|
39
|
+
}>, r: OpenAIChatTurnReasoning): void;
|
|
40
|
+
protected rememberStreamReplay(content: string, toolCalls: Array<{
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
arguments: string;
|
|
44
|
+
}>, r: OpenAIChatTurnReasoning): void;
|
|
25
45
|
private rememberDeepSeekReplay;
|
|
26
46
|
}
|
|
@@ -1,30 +1,26 @@
|
|
|
1
|
-
import { AnthropicProvider } from "./anthropic.js";
|
|
2
1
|
import { OpenAIChatProvider } from "./openai.js";
|
|
2
|
+
import { AnthropicCompatibleProvider } from "./anthropic-compatible.js";
|
|
3
3
|
import { endpointProfiles } from "./profiles.js";
|
|
4
|
-
import { omitExtensionKeys
|
|
5
|
-
|
|
6
|
-
"deepseek-chat": { maxTurns: 25 },
|
|
7
|
-
"deepseek-reasoner": { maxTurns: 50 },
|
|
8
|
-
"deepseek-v4-flash": { maxTurns: 20 },
|
|
9
|
-
"deepseek-v4-pro": { maxTurns: 35 },
|
|
10
|
-
};
|
|
4
|
+
import { omitExtensionKeys } from "./base.js";
|
|
5
|
+
import { DEEPSEEK_POLICIES, anthropicVendorProfiles } from "./vendor-profiles.js";
|
|
11
6
|
/**
|
|
12
7
|
* DeepSeek over its Anthropic-compatible endpoint.
|
|
8
|
+
* @deprecated Prefer `deepseek({ protocol: "anthropic" })`. Behavior is now fully
|
|
9
|
+
* data-driven via `anthropicVendorProfiles.deepseek`; this thin shim is kept for
|
|
10
|
+
* backward compatibility and `instanceof` checks.
|
|
13
11
|
*/
|
|
14
|
-
export class DeepSeekAnthropicProvider extends
|
|
15
|
-
constructor(apiKey, model
|
|
16
|
-
super(apiKey, model, retry,
|
|
17
|
-
baseURL,
|
|
18
|
-
authMode: "api-key",
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
providerName() {
|
|
22
|
-
return "deepseek";
|
|
23
|
-
}
|
|
24
|
-
runtimePolicy() {
|
|
25
|
-
return DEEPSEEK_POLICIES[this.model] ?? {};
|
|
12
|
+
export class DeepSeekAnthropicProvider extends AnthropicCompatibleProvider {
|
|
13
|
+
constructor(apiKey, model, retry, baseURL) {
|
|
14
|
+
super(anthropicVendorProfiles.deepseek, apiKey, model, retry, baseURL);
|
|
26
15
|
}
|
|
27
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* DeepSeek over its OpenAI-compatible endpoint. Reasoning is carried out-of-band as
|
|
19
|
+
* `reasoning_content`; replay persists DeepSeek's schema_version-2 envelope (with the
|
|
20
|
+
* native `tool_calls` blocks). Request shaping (`reasoning_effort` + `extra_body.thinking`)
|
|
21
|
+
* and replay are supplied via the OpenAIChatProvider Template-Method hooks; the streaming /
|
|
22
|
+
* tool-call machinery is inherited from the base class.
|
|
23
|
+
*/
|
|
28
24
|
export class DeepSeekProvider extends OpenAIChatProvider {
|
|
29
25
|
constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = endpointProfiles["deepseek.openai"].baseURL) {
|
|
30
26
|
super(apiKey, model, retry, baseURL);
|
|
@@ -53,146 +49,38 @@ export class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
53
49
|
return false;
|
|
54
50
|
return extensions?.thinking !== false;
|
|
55
51
|
}
|
|
56
|
-
|
|
52
|
+
// DeepSeek strictly validates the request body and 400s on unknown params, so never send
|
|
53
|
+
// OpenAI's `prompt_cache_key` (DeepSeek auto prefix-caches anyway).
|
|
54
|
+
// Ref: https://api-docs.deepseek.com/quick_start/error_codes
|
|
55
|
+
cacheKeyParams() {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
// Reasoning arrives out-of-band as `reasoning_content`, never as inline <thinking> tags.
|
|
59
|
+
usesInlineThinkingTags() {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
exposeReasoningDelta(extensions) {
|
|
63
|
+
return (extensions?.exposeReasoning ?? false);
|
|
64
|
+
}
|
|
65
|
+
prepareExtensions(extensions) {
|
|
57
66
|
const thinking = extensions?.thinking === false ? "disabled" : "enabled";
|
|
58
67
|
const thinkingEnabled = thinking !== "disabled";
|
|
59
68
|
const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
|
|
60
|
-
|
|
69
|
+
return {
|
|
61
70
|
...omitExtensionKeys(extensions, ["thinking", "reasoningEffort", "exposeReasoning", "extra_body", "reasoning_effort"]),
|
|
62
71
|
__deepstrikeThinkingEnabled: thinkingEnabled,
|
|
63
|
-
// Re-thread the degrade control flag (omitExtensionKeys strips internal
|
|
64
|
-
//
|
|
72
|
+
// Re-thread the degrade control flag (omitExtensionKeys strips internal keys) so
|
|
73
|
+
// buildChatMessages can honor it; the base requestExtensions omit keeps it off nothing.
|
|
65
74
|
...(extensions?.degradeMissingReasoningReplay === true ? { degradeMissingReasoningReplay: true } : {}),
|
|
66
75
|
reasoning_effort: reasoningEffort,
|
|
67
76
|
extra_body: { thinking: { type: thinking } },
|
|
68
77
|
};
|
|
69
|
-
if (this.circuit.isOpen())
|
|
70
|
-
throw new Error("Circuit breaker open");
|
|
71
|
-
const msgs = this.buildChatMessages(context, requestExtensions);
|
|
72
|
-
let lastErr;
|
|
73
|
-
for (let i = 0; i < this.maxRetries; i++) {
|
|
74
|
-
try {
|
|
75
|
-
const resp = await this.client.chat.completions.create({
|
|
76
|
-
...this.requestExtensions(requestExtensions),
|
|
77
|
-
model: this.model,
|
|
78
|
-
messages: msgs,
|
|
79
|
-
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
80
|
-
});
|
|
81
|
-
this.circuit.recordSuccess();
|
|
82
|
-
const choice = resp.choices[0].message;
|
|
83
|
-
const nativeToolCalls = choice.tool_calls ?? [];
|
|
84
|
-
const toolCalls = this.chat.normalizeToolCalls(nativeToolCalls);
|
|
85
|
-
const content = choice.content ?? "";
|
|
86
|
-
this.rememberDeepSeekReplay(content, toolCalls, choice.reasoning_content, nativeToolCalls);
|
|
87
|
-
return { role: "assistant", content, tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
|
|
88
|
-
}
|
|
89
|
-
catch (err) {
|
|
90
|
-
lastErr = err;
|
|
91
|
-
this.circuit.recordFailure();
|
|
92
|
-
if (i < this.maxRetries - 1)
|
|
93
|
-
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw lastErr;
|
|
97
78
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const toolCallBufs = {};
|
|
104
|
-
const emittedToolCallIndexes = new Set();
|
|
105
|
-
let reasoningContent = "";
|
|
106
|
-
let finalText = "";
|
|
107
|
-
const stream = await this.client.chat.completions.create({
|
|
108
|
-
...omitExtensionKeys(extensions, [
|
|
109
|
-
"model", "messages", "tools", "stream", "stream_options", "extra_body", "reasoning_effort",
|
|
110
|
-
"exposeReasoning", "thinking", "reasoningEffort", "__deepstrikeThinkingEnabled",
|
|
111
|
-
]),
|
|
112
|
-
model: this.model,
|
|
113
|
-
messages: msgs,
|
|
114
|
-
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
115
|
-
stream: true,
|
|
116
|
-
stream_options: { include_usage: true },
|
|
117
|
-
reasoning_effort: reasoningEffort,
|
|
118
|
-
extra_body: { thinking: { type: thinking } },
|
|
119
|
-
});
|
|
120
|
-
let totalTokens = 0;
|
|
121
|
-
let inputTokens = 0;
|
|
122
|
-
let outputTokens = 0;
|
|
123
|
-
let cacheReadTokens = 0;
|
|
124
|
-
for await (const chunk of stream) {
|
|
125
|
-
if (chunk.usage) {
|
|
126
|
-
totalTokens = chunk.usage.total_tokens;
|
|
127
|
-
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
128
|
-
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
129
|
-
cacheReadTokens = openAICachedPromptTokens(chunk.usage);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
const choice = chunk.choices[0];
|
|
133
|
-
if (!choice)
|
|
134
|
-
continue;
|
|
135
|
-
const delta = choice.delta;
|
|
136
|
-
if (!delta)
|
|
137
|
-
continue;
|
|
138
|
-
if (exposeReasoning && delta.reasoning_content) {
|
|
139
|
-
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
140
|
-
}
|
|
141
|
-
if (delta.reasoning_content)
|
|
142
|
-
reasoningContent += String(delta.reasoning_content);
|
|
143
|
-
if (delta.content) {
|
|
144
|
-
finalText += String(delta.content);
|
|
145
|
-
yield { type: "text_delta", delta: delta.content };
|
|
146
|
-
}
|
|
147
|
-
for (const tc of delta.tool_calls ?? []) {
|
|
148
|
-
const idx = tc.index;
|
|
149
|
-
if (!toolCallBufs[idx])
|
|
150
|
-
toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
|
|
151
|
-
if (tc.function?.name)
|
|
152
|
-
toolCallBufs[idx].name += tc.function.name;
|
|
153
|
-
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
154
|
-
}
|
|
155
|
-
if (choice.finish_reason === "tool_calls") {
|
|
156
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
157
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
158
|
-
}));
|
|
159
|
-
this.rememberDeepSeekReplay(finalText, toolCalls, reasoningContent, nativeToolCallsFromBuffers(toolCallBufs));
|
|
160
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
161
|
-
const idx = Number(index);
|
|
162
|
-
if (emittedToolCallIndexes.has(idx))
|
|
163
|
-
continue;
|
|
164
|
-
let args = {};
|
|
165
|
-
try {
|
|
166
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
args = {};
|
|
170
|
-
}
|
|
171
|
-
emittedToolCallIndexes.add(idx);
|
|
172
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
177
|
-
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
178
|
-
}));
|
|
179
|
-
this.rememberDeepSeekReplay(finalText, toolCalls, reasoningContent, nativeToolCallsFromBuffers(toolCallBufs));
|
|
180
|
-
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
181
|
-
const idx = Number(index);
|
|
182
|
-
if (emittedToolCallIndexes.has(idx))
|
|
183
|
-
continue;
|
|
184
|
-
let args = {};
|
|
185
|
-
try {
|
|
186
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
args = {};
|
|
190
|
-
}
|
|
191
|
-
emittedToolCallIndexes.add(idx);
|
|
192
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
193
|
-
}
|
|
194
|
-
if (totalTokens > 0)
|
|
195
|
-
yield { type: "usage", totalTokens, inputTokens, outputTokens, ...(cacheReadTokens > 0 ? { cacheReadInputTokens: cacheReadTokens } : {}) };
|
|
79
|
+
rememberCompleteReplay(content, toolCalls, r) {
|
|
80
|
+
this.rememberDeepSeekReplay(content, toolCalls, r.reasoningContent, r.nativeToolCalls);
|
|
81
|
+
}
|
|
82
|
+
rememberStreamReplay(content, toolCalls, r) {
|
|
83
|
+
this.rememberDeepSeekReplay(content, toolCalls, r.reasoningContent, r.nativeToolCalls);
|
|
196
84
|
}
|
|
197
85
|
rememberDeepSeekReplay(content, toolCalls, reasoningContent, nativeToolCalls) {
|
|
198
86
|
if (typeof reasoningContent !== "string" || !reasoningContent.trim())
|
|
@@ -207,10 +95,3 @@ export class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
207
95
|
});
|
|
208
96
|
}
|
|
209
97
|
}
|
|
210
|
-
function nativeToolCallsFromBuffers(toolCallBufs) {
|
|
211
|
-
return Object.values(toolCallBufs).map(tb => ({
|
|
212
|
-
id: tb.id,
|
|
213
|
-
type: "function",
|
|
214
|
-
function: { name: tb.name, arguments: tb.argsBuf || "{}" },
|
|
215
|
-
}));
|
|
216
|
-
}
|