@deepstrike/sdk 0.2.6 → 0.2.8
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/index.d.ts +8 -4
- package/dist/index.js +5 -2
- package/dist/kernel.d.ts +54 -0
- package/dist/kernel.js +8 -0
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +51 -0
- package/dist/providers/base.d.ts +6 -0
- package/dist/providers/base.js +10 -1
- package/dist/providers/catalog.js +5 -2
- package/dist/providers/deepseek.d.ts +4 -1
- package/dist/providers/deepseek.js +79 -8
- package/dist/providers/glm.d.ts +2 -1
- package/dist/providers/glm.js +7 -0
- package/dist/providers/kimi.d.ts +2 -1
- package/dist/providers/kimi.js +7 -0
- package/dist/providers/minimax.d.ts +28 -2
- package/dist/providers/minimax.js +200 -1
- package/dist/providers/openai-chat.d.ts +18 -2
- package/dist/providers/openai-chat.js +37 -3
- package/dist/providers/openai.d.ts +14 -1
- package/dist/providers/openai.js +48 -7
- package/dist/providers/profiles.d.ts +6 -0
- package/dist/providers/profiles.js +6 -0
- package/dist/providers/qwen.d.ts +3 -1
- package/dist/providers/qwen.js +20 -2
- package/dist/providers/replay-validator.d.ts +32 -0
- package/dist/providers/replay-validator.js +90 -0
- package/dist/runtime/kernel-event-log.js +22 -0
- package/dist/runtime/kernel-step.d.ts +13 -0
- package/dist/runtime/provider-replay.d.ts +16 -1
- package/dist/runtime/provider-replay.js +47 -4
- package/dist/runtime/runner.d.ts +22 -1
- package/dist/runtime/runner.js +68 -2
- package/dist/runtime/session-log.d.ts +22 -0
- package/dist/runtime/session-repair.d.ts +26 -3
- package/dist/runtime/session-repair.js +33 -32
- package/dist/types/agent.d.ts +50 -0
- package/dist/types/agent.js +110 -0
- package/dist/types.d.ts +38 -0
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { AnthropicProvider } from "./anthropic.js";
|
|
2
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
2
3
|
import { endpointProfiles } from "./profiles.js";
|
|
4
|
+
import { omitExtensionKeys } from "./base.js";
|
|
3
5
|
const MINIMAX_POLICIES = {
|
|
4
6
|
"MiniMax-M2.7": { maxTurns: 35 },
|
|
5
7
|
"MiniMax-M2.7-highspeed": { maxTurns: 35 },
|
|
@@ -10,14 +12,211 @@ const MINIMAX_POLICIES = {
|
|
|
10
12
|
"MiniMax-M2": { maxTurns: 20 },
|
|
11
13
|
"MiniMax-Text-01": { maxTurns: 20 },
|
|
12
14
|
};
|
|
13
|
-
|
|
15
|
+
/**
|
|
16
|
+
* MiniMax over its Anthropic-compatible endpoint. Replay is carried as Anthropic
|
|
17
|
+
* `native_blocks` (thinking/text/tool_use), identical to the first-party
|
|
18
|
+
* Anthropic provider.
|
|
19
|
+
*/
|
|
20
|
+
export class MiniMaxAnthropicProvider extends AnthropicProvider {
|
|
14
21
|
constructor(apiKey, model = "MiniMax-M2.7", retry, baseURL = endpointProfiles["minimax.anthropic"].baseURL) {
|
|
15
22
|
super(apiKey, model, retry, {
|
|
16
23
|
baseURL,
|
|
17
24
|
authMode: "api-key",
|
|
18
25
|
});
|
|
19
26
|
}
|
|
27
|
+
providerName() {
|
|
28
|
+
return "minimax";
|
|
29
|
+
}
|
|
30
|
+
runtimePolicy() {
|
|
31
|
+
return MINIMAX_POLICIES[this.model] ?? {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* MiniMax over its OpenAI-compatible endpoint. Replay is carried as
|
|
36
|
+
* `reasoning_content` / `reasoning_details` (split reasoning), and requests
|
|
37
|
+
* default to `reasoning_split: true` so reasoning is returned out-of-band rather
|
|
38
|
+
* than embedded in the message content.
|
|
39
|
+
*/
|
|
40
|
+
export class MiniMaxOpenAIProvider extends OpenAIChatProvider {
|
|
41
|
+
constructor(apiKey, model = "MiniMax-M2.7", retry, baseURL = endpointProfiles["minimax.openai"].baseURL) {
|
|
42
|
+
super(apiKey, model, retry, baseURL);
|
|
43
|
+
}
|
|
20
44
|
runtimePolicy() {
|
|
21
45
|
return MINIMAX_POLICIES[this.model] ?? {};
|
|
22
46
|
}
|
|
47
|
+
descriptor() {
|
|
48
|
+
return {
|
|
49
|
+
provider: "minimax",
|
|
50
|
+
protocol: "openai-chat",
|
|
51
|
+
model: this.model,
|
|
52
|
+
reasoning: {
|
|
53
|
+
supported: true,
|
|
54
|
+
preserveAcrossToolTurns: true,
|
|
55
|
+
requiresReplayForToolTurns: true,
|
|
56
|
+
},
|
|
57
|
+
toolCalls: {
|
|
58
|
+
supported: true,
|
|
59
|
+
requiresStrictPairing: true,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
requireNonEmptyReasoningReplayForToolTurns(extensions) {
|
|
64
|
+
if (extensions?.__deepstrikeThinkingEnabled === false)
|
|
65
|
+
return false;
|
|
66
|
+
return extensions?.reasoning_split !== false;
|
|
67
|
+
}
|
|
68
|
+
buildRequestExtensions(extensions) {
|
|
69
|
+
const reasoningSplit = extensions?.reasoning_split !== false;
|
|
70
|
+
return {
|
|
71
|
+
...omitExtensionKeys(extensions, ["reasoning_split", "exposeReasoning"]),
|
|
72
|
+
__deepstrikeThinkingEnabled: reasoningSplit,
|
|
73
|
+
// Re-thread the degrade control flag (omitExtensionKeys strips internal
|
|
74
|
+
// keys) so buildChatMessages can honor it; the wire-request omit drops it.
|
|
75
|
+
...(extensions?.degradeMissingReasoningReplay === true ? { degradeMissingReasoningReplay: true } : {}),
|
|
76
|
+
reasoning_split: reasoningSplit,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async complete(context, tools, extensions) {
|
|
80
|
+
const requestExtensions = this.buildRequestExtensions(extensions);
|
|
81
|
+
if (this.circuit.isOpen())
|
|
82
|
+
throw new Error("Circuit breaker open");
|
|
83
|
+
const msgs = this.buildChatMessages(context, requestExtensions);
|
|
84
|
+
let lastErr;
|
|
85
|
+
for (let i = 0; i < this.maxRetries; i++) {
|
|
86
|
+
try {
|
|
87
|
+
const resp = await this.client.chat.completions.create({
|
|
88
|
+
...this.requestExtensions(requestExtensions),
|
|
89
|
+
model: this.model,
|
|
90
|
+
messages: msgs,
|
|
91
|
+
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
92
|
+
});
|
|
93
|
+
this.circuit.recordSuccess();
|
|
94
|
+
const choice = resp.choices[0].message;
|
|
95
|
+
const nativeToolCalls = choice.tool_calls ?? [];
|
|
96
|
+
const toolCalls = this.chat.normalizeToolCalls(nativeToolCalls);
|
|
97
|
+
const content = choice.content ?? "";
|
|
98
|
+
this.rememberMiniMaxReplay(content, toolCalls, choice.reasoning_content, choice.reasoning_details, nativeToolCalls);
|
|
99
|
+
return { role: "assistant", content, tokenCount: resp.usage?.completion_tokens ?? resp.usage?.total_tokens, toolCalls };
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
lastErr = err;
|
|
103
|
+
this.circuit.recordFailure();
|
|
104
|
+
if (i < this.maxRetries - 1)
|
|
105
|
+
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw lastErr;
|
|
109
|
+
}
|
|
110
|
+
async *stream(context, tools, extensions) {
|
|
111
|
+
const exposeReasoning = extensions?.exposeReasoning ?? false;
|
|
112
|
+
const requestExtensions = this.buildRequestExtensions(extensions);
|
|
113
|
+
const msgs = this.buildChatMessages(context, requestExtensions);
|
|
114
|
+
const toolCallBufs = {};
|
|
115
|
+
const emittedToolCallIndexes = new Set();
|
|
116
|
+
let reasoningContent = "";
|
|
117
|
+
let reasoningDetails;
|
|
118
|
+
let finalText = "";
|
|
119
|
+
const stream = await this.client.chat.completions.create({
|
|
120
|
+
...this.requestExtensions(requestExtensions),
|
|
121
|
+
model: this.model,
|
|
122
|
+
messages: msgs,
|
|
123
|
+
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
124
|
+
stream: true,
|
|
125
|
+
stream_options: { include_usage: true },
|
|
126
|
+
});
|
|
127
|
+
let totalTokens = 0;
|
|
128
|
+
let inputTokens = 0;
|
|
129
|
+
let outputTokens = 0;
|
|
130
|
+
for await (const chunk of stream) {
|
|
131
|
+
if (chunk.usage) {
|
|
132
|
+
totalTokens = chunk.usage.total_tokens;
|
|
133
|
+
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
134
|
+
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const choice = chunk.choices[0];
|
|
138
|
+
if (!choice)
|
|
139
|
+
continue;
|
|
140
|
+
const delta = choice.delta;
|
|
141
|
+
if (!delta)
|
|
142
|
+
continue;
|
|
143
|
+
if (exposeReasoning && delta.reasoning_content) {
|
|
144
|
+
yield { type: "thinking_delta", delta: String(delta.reasoning_content) };
|
|
145
|
+
}
|
|
146
|
+
if (delta.reasoning_content)
|
|
147
|
+
reasoningContent += String(delta.reasoning_content);
|
|
148
|
+
if (delta.reasoning_details !== undefined && delta.reasoning_details !== null)
|
|
149
|
+
reasoningDetails = delta.reasoning_details;
|
|
150
|
+
if (delta.content) {
|
|
151
|
+
finalText += String(delta.content);
|
|
152
|
+
yield { type: "text_delta", delta: delta.content };
|
|
153
|
+
}
|
|
154
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
155
|
+
const idx = tc.index;
|
|
156
|
+
if (!toolCallBufs[idx])
|
|
157
|
+
toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
|
|
158
|
+
if (tc.function?.name)
|
|
159
|
+
toolCallBufs[idx].name += tc.function.name;
|
|
160
|
+
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
161
|
+
}
|
|
162
|
+
if (choice.finish_reason === "tool_calls") {
|
|
163
|
+
const toolCalls = Object.values(toolCallBufs).map(tb => ({ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}" }));
|
|
164
|
+
this.rememberMiniMaxReplay(finalText, toolCalls, reasoningContent, reasoningDetails, nativeToolCallsFromBuffers(toolCallBufs));
|
|
165
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
166
|
+
const idx = Number(index);
|
|
167
|
+
if (emittedToolCallIndexes.has(idx))
|
|
168
|
+
continue;
|
|
169
|
+
let args = {};
|
|
170
|
+
try {
|
|
171
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
args = {};
|
|
175
|
+
}
|
|
176
|
+
emittedToolCallIndexes.add(idx);
|
|
177
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const toolCalls = Object.values(toolCallBufs).map(tb => ({ id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}" }));
|
|
182
|
+
this.rememberMiniMaxReplay(finalText, toolCalls, reasoningContent, reasoningDetails, nativeToolCallsFromBuffers(toolCallBufs));
|
|
183
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
184
|
+
const idx = Number(index);
|
|
185
|
+
if (emittedToolCallIndexes.has(idx))
|
|
186
|
+
continue;
|
|
187
|
+
let args = {};
|
|
188
|
+
try {
|
|
189
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
args = {};
|
|
193
|
+
}
|
|
194
|
+
emittedToolCallIndexes.add(idx);
|
|
195
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
196
|
+
}
|
|
197
|
+
if (totalTokens > 0)
|
|
198
|
+
yield { type: "usage", totalTokens, inputTokens, outputTokens };
|
|
199
|
+
}
|
|
200
|
+
rememberMiniMaxReplay(content, toolCalls, reasoningContent, reasoningDetails, nativeToolCalls) {
|
|
201
|
+
const hasReasoning = typeof reasoningContent === "string" && reasoningContent.trim().length > 0;
|
|
202
|
+
const hasDetails = reasoningDetails !== undefined && reasoningDetails !== null;
|
|
203
|
+
if (!hasReasoning && !hasDetails)
|
|
204
|
+
return;
|
|
205
|
+
this.chat.rememberReplayFields({ content, toolCalls }, {
|
|
206
|
+
schema_version: 2,
|
|
207
|
+
provider: "minimax",
|
|
208
|
+
protocol: "openai-chat",
|
|
209
|
+
model: this.model,
|
|
210
|
+
...(hasReasoning ? { reasoning_content: reasoningContent } : {}),
|
|
211
|
+
...(hasDetails ? { reasoning_details: reasoningDetails } : {}),
|
|
212
|
+
...(nativeToolCalls.length ? { tool_calls: nativeToolCalls } : {}),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function nativeToolCallsFromBuffers(toolCallBufs) {
|
|
217
|
+
return Object.values(toolCallBufs).map(tb => ({
|
|
218
|
+
id: tb.id,
|
|
219
|
+
type: "function",
|
|
220
|
+
function: { name: tb.name, arguments: tb.argsBuf || "{}" },
|
|
221
|
+
}));
|
|
23
222
|
}
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type OpenAI from "openai";
|
|
2
|
-
import type { Message, RenderedContext, ToolSchema } from "../types.js";
|
|
2
|
+
import type { Message, ProviderDescriptor, RenderedContext, ToolSchema } from "../types.js";
|
|
3
|
+
import { type ReplayabilityAssessment } from "./replay-validator.js";
|
|
4
|
+
export interface OpenAIChatBuildMessageOptions {
|
|
5
|
+
descriptor?: ProviderDescriptor;
|
|
6
|
+
requireNonEmptyReasoningForToolCalls?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Degrade (rather than throw) when a reasoning-requiring tool-call turn has
|
|
9
|
+
* no stored reasoning replay: a placeholder reasoning is injected so the
|
|
10
|
+
* request still goes out in degraded form.
|
|
11
|
+
*/
|
|
12
|
+
degradeMissingReasoning?: boolean;
|
|
13
|
+
}
|
|
3
14
|
export declare class OpenAIChatAdapter {
|
|
4
15
|
private replayFields;
|
|
5
16
|
buildTools(tools: ToolSchema[]): {
|
|
@@ -10,7 +21,12 @@ export declare class OpenAIChatAdapter {
|
|
|
10
21
|
parameters: any;
|
|
11
22
|
};
|
|
12
23
|
}[];
|
|
13
|
-
buildMessages(context: RenderedContext): OpenAI.ChatCompletionMessageParam[];
|
|
24
|
+
buildMessages(context: RenderedContext, options?: OpenAIChatBuildMessageOptions): OpenAI.ChatCompletionMessageParam[];
|
|
25
|
+
/**
|
|
26
|
+
* Throw-free pre-flight check: which assistant tool-call turns in `context`
|
|
27
|
+
* lack the non-empty reasoning replay a reasoning-requiring provider needs.
|
|
28
|
+
*/
|
|
29
|
+
assessReasoning(context: RenderedContext): ReplayabilityAssessment;
|
|
14
30
|
normalizeToolCalls(toolCalls?: OpenAI.ChatCompletionMessageToolCall[]): Array<{
|
|
15
31
|
id: string;
|
|
16
32
|
name: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assistantReplayKey } from "../runtime/provider-replay.js";
|
|
2
2
|
import { normalizeToolCall, toOpenAIMessageParams } from "./base.js";
|
|
3
|
+
import { DEGRADED_REASONING_PLACEHOLDER, assessReasoningReplay, validateOpenAIChatReplay, } from "./replay-validator.js";
|
|
3
4
|
export class OpenAIChatAdapter {
|
|
4
5
|
replayFields = new Map();
|
|
5
6
|
buildTools(tools) {
|
|
@@ -8,7 +9,14 @@ export class OpenAIChatAdapter {
|
|
|
8
9
|
function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
|
|
9
10
|
}));
|
|
10
11
|
}
|
|
11
|
-
buildMessages(context) {
|
|
12
|
+
buildMessages(context, options = {}) {
|
|
13
|
+
validateOpenAIChatReplay(context, {
|
|
14
|
+
descriptor: options.descriptor,
|
|
15
|
+
requireNonEmptyReasoningForToolCalls: options.requireNonEmptyReasoningForToolCalls,
|
|
16
|
+
degradeMissingReasoning: options.degradeMissingReasoning,
|
|
17
|
+
replayForAssistant: message => this.replayFields.get(assistantReplayKey(message)),
|
|
18
|
+
});
|
|
19
|
+
const degradeReasoning = Boolean(options.requireNonEmptyReasoningForToolCalls && options.degradeMissingReasoning);
|
|
12
20
|
// toOpenAIMessageParams prepends systemText as messages[0], then turns.
|
|
13
21
|
const serialized = toOpenAIMessageParams(context);
|
|
14
22
|
// Cursor starts at 1 to skip the system message injected by toOpenAIMessageParams.
|
|
@@ -20,13 +28,29 @@ export class OpenAIChatAdapter {
|
|
|
20
28
|
}
|
|
21
29
|
if (source.role === "assistant") {
|
|
22
30
|
const replay = this.replayFields.get(assistantReplayKey(source));
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
let wireReplay = openAIChatWireReplayFields(replay);
|
|
32
|
+
if (!wireReplay && degradeReasoning && source.toolCalls?.length) {
|
|
33
|
+
// Reasoning-requiring provider, no stored reasoning for this tool-call
|
|
34
|
+
// turn, caller opted into degradation: inject a placeholder so the
|
|
35
|
+
// wire message stays well-formed instead of failing the whole request.
|
|
36
|
+
wireReplay = { reasoning_content: DEGRADED_REASONING_PLACEHOLDER };
|
|
37
|
+
}
|
|
38
|
+
if (wireReplay)
|
|
39
|
+
serialized[cursor] = { ...serialized[cursor], ...wireReplay };
|
|
25
40
|
}
|
|
26
41
|
cursor += 1;
|
|
27
42
|
}
|
|
28
43
|
return serialized;
|
|
29
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Throw-free pre-flight check: which assistant tool-call turns in `context`
|
|
47
|
+
* lack the non-empty reasoning replay a reasoning-requiring provider needs.
|
|
48
|
+
*/
|
|
49
|
+
assessReasoning(context) {
|
|
50
|
+
return assessReasoningReplay(context.turns, {
|
|
51
|
+
replayForAssistant: message => this.replayFields.get(assistantReplayKey(message)),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
30
54
|
normalizeToolCalls(toolCalls = []) {
|
|
31
55
|
return toolCalls
|
|
32
56
|
.filter((tc) => tc.type === "function")
|
|
@@ -40,3 +64,13 @@ export class OpenAIChatAdapter {
|
|
|
40
64
|
return this.replayFields.get(assistantReplayKey(message));
|
|
41
65
|
}
|
|
42
66
|
}
|
|
67
|
+
function openAIChatWireReplayFields(replay) {
|
|
68
|
+
if (!replay)
|
|
69
|
+
return undefined;
|
|
70
|
+
const fields = {};
|
|
71
|
+
if (typeof replay.reasoning_content === "string")
|
|
72
|
+
fields.reasoning_content = replay.reasoning_content;
|
|
73
|
+
if (replay.reasoning_details !== undefined)
|
|
74
|
+
fields.reasoning_details = replay.reasoning_details;
|
|
75
|
+
return Object.keys(fields).length ? fields : undefined;
|
|
76
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { Message, ProviderReplay, RenderedContext, ToolSchema, StreamEvent, LLMProvider, RuntimePolicy } from "../types.js";
|
|
2
|
+
import type { Message, ProviderDescriptor, ProviderReplay, RenderedContext, ToolSchema, StreamEvent, LLMProvider, RuntimePolicy } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
|
+
import type { ReplayabilityAssessment } from "./replay-validator.js";
|
|
5
6
|
export declare class OpenAIChatProvider implements LLMProvider {
|
|
6
7
|
protected readonly model: string;
|
|
7
8
|
protected client: OpenAI;
|
|
@@ -14,6 +15,18 @@ export declare class OpenAIChatProvider implements LLMProvider {
|
|
|
14
15
|
baseDelay: number;
|
|
15
16
|
}, baseURL?: string);
|
|
16
17
|
runtimePolicy(): RuntimePolicy;
|
|
18
|
+
descriptor(): ProviderDescriptor;
|
|
19
|
+
protected requireNonEmptyReasoningReplayForToolTurns(_extensions?: Record<string, unknown>): boolean;
|
|
20
|
+
protected degradeMissingReasoningReplay(extensions?: Record<string, unknown>): boolean;
|
|
21
|
+
protected buildChatMessages(context: RenderedContext, extensions?: Record<string, unknown>): OpenAI.Chat.Completions.ChatCompletionMessageParam[];
|
|
22
|
+
/**
|
|
23
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
24
|
+
* given extensions, without sending the request? Lets an embedder route around
|
|
25
|
+
* a reasoning-replay failure (keep thinking on, disable it, or skip this
|
|
26
|
+
* candidate) before issuing the request. `ok: true` when this provider does
|
|
27
|
+
* not require reasoning replay for the current extensions.
|
|
28
|
+
*/
|
|
29
|
+
assessReplayability(context: RenderedContext, extensions?: Record<string, unknown>): ReplayabilityAssessment;
|
|
17
30
|
peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
18
31
|
seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
19
32
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
package/dist/providers/openai.js
CHANGED
|
@@ -42,21 +42,62 @@ export class OpenAIChatProvider {
|
|
|
42
42
|
runtimePolicy() {
|
|
43
43
|
return OPENAI_POLICIES[this.model] ?? {};
|
|
44
44
|
}
|
|
45
|
+
descriptor() {
|
|
46
|
+
return {
|
|
47
|
+
provider: "openai",
|
|
48
|
+
protocol: "openai-chat",
|
|
49
|
+
model: this.model,
|
|
50
|
+
reasoning: {
|
|
51
|
+
supported: true,
|
|
52
|
+
preserveAcrossToolTurns: false,
|
|
53
|
+
},
|
|
54
|
+
toolCalls: {
|
|
55
|
+
supported: true,
|
|
56
|
+
requiresStrictPairing: true,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
requireNonEmptyReasoningReplayForToolTurns(_extensions) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
degradeMissingReasoningReplay(extensions) {
|
|
64
|
+
return extensions?.degradeMissingReasoningReplay === true;
|
|
65
|
+
}
|
|
66
|
+
buildChatMessages(context, extensions) {
|
|
67
|
+
return this.chat.buildMessages(context, {
|
|
68
|
+
descriptor: this.descriptor(),
|
|
69
|
+
requireNonEmptyReasoningForToolCalls: this.requireNonEmptyReasoningReplayForToolTurns(extensions),
|
|
70
|
+
degradeMissingReasoning: this.degradeMissingReasoningReplay(extensions),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Pre-flight query: would this history validate against this provider with the
|
|
75
|
+
* given extensions, without sending the request? Lets an embedder route around
|
|
76
|
+
* a reasoning-replay failure (keep thinking on, disable it, or skip this
|
|
77
|
+
* candidate) before issuing the request. `ok: true` when this provider does
|
|
78
|
+
* not require reasoning replay for the current extensions.
|
|
79
|
+
*/
|
|
80
|
+
assessReplayability(context, extensions) {
|
|
81
|
+
if (!this.requireNonEmptyReasoningReplayForToolTurns(extensions)) {
|
|
82
|
+
return { ok: true, offendingCallIds: [] };
|
|
83
|
+
}
|
|
84
|
+
return this.chat.assessReasoning(context);
|
|
85
|
+
}
|
|
45
86
|
peekProviderReplay(message) {
|
|
46
87
|
const fields = this.chat.peekReplayFields(message);
|
|
47
|
-
if (!fields || !("reasoning_content" in fields))
|
|
88
|
+
if (!fields || !("reasoning_content" in fields || "reasoning_details" in fields))
|
|
48
89
|
return undefined;
|
|
49
|
-
return
|
|
90
|
+
return fields;
|
|
50
91
|
}
|
|
51
92
|
seedProviderReplay(message, replay) {
|
|
52
|
-
if (replay.reasoning_content !== undefined) {
|
|
53
|
-
this.chat.rememberReplayFields(message,
|
|
93
|
+
if (replay.reasoning_content !== undefined || replay.reasoning_details !== undefined) {
|
|
94
|
+
this.chat.rememberReplayFields(message, replay);
|
|
54
95
|
}
|
|
55
96
|
}
|
|
56
97
|
async complete(context, tools, extensions) {
|
|
57
98
|
if (this.circuit.isOpen())
|
|
58
99
|
throw new Error("Circuit breaker open");
|
|
59
|
-
const msgs = this.
|
|
100
|
+
const msgs = this.buildChatMessages(context, extensions);
|
|
60
101
|
let lastErr;
|
|
61
102
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
62
103
|
try {
|
|
@@ -81,7 +122,7 @@ export class OpenAIChatProvider {
|
|
|
81
122
|
throw lastErr;
|
|
82
123
|
}
|
|
83
124
|
async *stream(context, tools, extensions) {
|
|
84
|
-
const msgs = this.
|
|
125
|
+
const msgs = this.buildChatMessages(context, extensions);
|
|
85
126
|
const toolCallBufs = {};
|
|
86
127
|
const emittedToolCallIndexes = new Set();
|
|
87
128
|
const extractor = new ThinkingTagStreamExtractor();
|
|
@@ -190,7 +231,7 @@ export class OpenAIChatProvider {
|
|
|
190
231
|
yield { type: "usage", totalTokens, inputTokens, outputTokens };
|
|
191
232
|
}
|
|
192
233
|
requestExtensions(extensions) {
|
|
193
|
-
return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream", "stream_options"]);
|
|
234
|
+
return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream", "stream_options", "__deepstrikeThinkingEnabled"]);
|
|
194
235
|
}
|
|
195
236
|
}
|
|
196
237
|
export { OpenAIChatProvider as OpenAIProvider };
|
|
@@ -59,6 +59,12 @@ export declare const endpointProfiles: {
|
|
|
59
59
|
readonly protocol: "anthropic-messages";
|
|
60
60
|
readonly baseURL: "https://api.minimaxi.com/anthropic";
|
|
61
61
|
};
|
|
62
|
+
readonly "minimax.openai": {
|
|
63
|
+
readonly id: "minimax.openai";
|
|
64
|
+
readonly providerId: "minimax";
|
|
65
|
+
readonly protocol: "openai-chat";
|
|
66
|
+
readonly baseURL: "https://api.minimaxi.com/v1";
|
|
67
|
+
};
|
|
62
68
|
readonly "deepseek.openai": {
|
|
63
69
|
readonly id: "deepseek.openai";
|
|
64
70
|
readonly providerId: "deepseek";
|
|
@@ -29,6 +29,12 @@ export const endpointProfiles = {
|
|
|
29
29
|
protocol: "anthropic-messages",
|
|
30
30
|
baseURL: "https://api.minimaxi.com/anthropic",
|
|
31
31
|
},
|
|
32
|
+
"minimax.openai": {
|
|
33
|
+
id: "minimax.openai",
|
|
34
|
+
providerId: "minimax",
|
|
35
|
+
protocol: "openai-chat",
|
|
36
|
+
baseURL: "https://api.minimaxi.com/v1",
|
|
37
|
+
},
|
|
32
38
|
"deepseek.openai": {
|
|
33
39
|
id: "deepseek.openai",
|
|
34
40
|
providerId: "deepseek",
|
package/dist/providers/qwen.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { LLMProvider, Message, RenderedContext, StreamEvent, ToolSchema, RuntimePolicy, ProviderReplay } from "../types.js";
|
|
2
|
+
import type { LLMProvider, Message, ProviderDescriptor, RenderedContext, StreamEvent, ToolSchema, RuntimePolicy, ProviderReplay } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
5
|
export declare class QwenProvider implements LLMProvider {
|
|
@@ -14,6 +14,8 @@ export declare class QwenProvider implements LLMProvider {
|
|
|
14
14
|
baseDelay: number;
|
|
15
15
|
}, baseURL?: string);
|
|
16
16
|
runtimePolicy(): RuntimePolicy;
|
|
17
|
+
descriptor(): ProviderDescriptor;
|
|
18
|
+
private buildChatMessages;
|
|
17
19
|
peekProviderReplay(message: Pick<Message, "content" | "toolCalls">): ProviderReplay | undefined;
|
|
18
20
|
seedProviderReplay(message: Pick<Message, "content" | "toolCalls">, replay: ProviderReplay): void;
|
|
19
21
|
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
package/dist/providers/qwen.js
CHANGED
|
@@ -36,6 +36,24 @@ export class QwenProvider {
|
|
|
36
36
|
runtimePolicy() {
|
|
37
37
|
return QWEN_POLICIES[this.model] ?? {};
|
|
38
38
|
}
|
|
39
|
+
descriptor() {
|
|
40
|
+
return {
|
|
41
|
+
provider: "qwen",
|
|
42
|
+
protocol: "openai-chat",
|
|
43
|
+
model: this.model,
|
|
44
|
+
reasoning: {
|
|
45
|
+
supported: true,
|
|
46
|
+
preserveAcrossToolTurns: true,
|
|
47
|
+
},
|
|
48
|
+
toolCalls: {
|
|
49
|
+
supported: true,
|
|
50
|
+
requiresStrictPairing: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
buildChatMessages(context) {
|
|
55
|
+
return this.chat.buildMessages(context, { descriptor: this.descriptor() });
|
|
56
|
+
}
|
|
39
57
|
peekProviderReplay(message) {
|
|
40
58
|
const fields = this.chat.peekReplayFields(message);
|
|
41
59
|
if (!fields || !("reasoning_content" in fields))
|
|
@@ -50,7 +68,7 @@ export class QwenProvider {
|
|
|
50
68
|
async complete(context, tools, extensions) {
|
|
51
69
|
if (this.circuit.isOpen())
|
|
52
70
|
throw new Error("Circuit breaker open");
|
|
53
|
-
const msgs = this.
|
|
71
|
+
const msgs = this.buildChatMessages(context);
|
|
54
72
|
const extraBody = this.thinkingExtraBody(extensions);
|
|
55
73
|
let lastErr;
|
|
56
74
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
@@ -77,7 +95,7 @@ export class QwenProvider {
|
|
|
77
95
|
throw lastErr;
|
|
78
96
|
}
|
|
79
97
|
async *stream(context, tools, extensions) {
|
|
80
|
-
const msgs = this.
|
|
98
|
+
const msgs = this.buildChatMessages(context);
|
|
81
99
|
const toolCallBufs = {};
|
|
82
100
|
const emittedToolCallIndexes = new Set();
|
|
83
101
|
let reasoningContent = "";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Message, ProviderDescriptor, ProviderReplay, RenderedContext, ReplayabilityAssessment } from "../types.js";
|
|
2
|
+
export type { ReplayabilityAssessment };
|
|
3
|
+
export declare class ProviderReplayValidationError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Placeholder reasoning injected for an assistant tool-call turn that has no
|
|
8
|
+
* stored reasoning replay when the caller opted into graceful degradation
|
|
9
|
+
* (`degradeMissingReasoning`). It keeps the wire message well-formed for a
|
|
10
|
+
* thinking-on provider without fabricating substantive reasoning.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEGRADED_REASONING_PLACEHOLDER = "[reasoning unavailable on replay]";
|
|
13
|
+
export interface OpenAIChatReplayValidationOptions {
|
|
14
|
+
descriptor?: ProviderDescriptor;
|
|
15
|
+
requireNonEmptyReasoningForToolCalls?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* When true, an assistant tool-call turn that lacks reasoning replay is
|
|
18
|
+
* degraded (serialized without/with a placeholder reasoning) instead of
|
|
19
|
+
* throwing. Lets a recovery/fallback request succeed in degraded form
|
|
20
|
+
* rather than fail outright.
|
|
21
|
+
*/
|
|
22
|
+
degradeMissingReasoning?: boolean;
|
|
23
|
+
replayForAssistant?: (message: Pick<Message, "content" | "toolCalls">) => ProviderReplay | Record<string, unknown> | undefined;
|
|
24
|
+
}
|
|
25
|
+
export declare function validateOpenAIChatReplay(context: RenderedContext, options?: OpenAIChatReplayValidationOptions): void;
|
|
26
|
+
/**
|
|
27
|
+
* Pure, throw-free assessment: which assistant tool-call turns lack the
|
|
28
|
+
* non-empty reasoning replay a reasoning-requiring provider needs. Lets an
|
|
29
|
+
* embedder decide per-candidate whether to keep thinking on, disable it, or
|
|
30
|
+
* skip the candidate — before sending.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assessReasoningReplay(turns: Message[], options: Pick<OpenAIChatReplayValidationOptions, "replayForAssistant">): ReplayabilityAssessment;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export class ProviderReplayValidationError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "ProviderReplayValidationError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Placeholder reasoning injected for an assistant tool-call turn that has no
|
|
9
|
+
* stored reasoning replay when the caller opted into graceful degradation
|
|
10
|
+
* (`degradeMissingReasoning`). It keeps the wire message well-formed for a
|
|
11
|
+
* thinking-on provider without fabricating substantive reasoning.
|
|
12
|
+
*/
|
|
13
|
+
export const DEGRADED_REASONING_PLACEHOLDER = "[reasoning unavailable on replay]";
|
|
14
|
+
export function validateOpenAIChatReplay(context, options = {}) {
|
|
15
|
+
validateStrictToolResultPairing(context.turns);
|
|
16
|
+
if (options.requireNonEmptyReasoningForToolCalls && !options.degradeMissingReasoning) {
|
|
17
|
+
const assessment = assessReasoningReplay(context.turns, options);
|
|
18
|
+
if (!assessment.ok) {
|
|
19
|
+
throw reasoningReplayError(assessment.offendingCallIds, options.descriptor);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Pure, throw-free assessment: which assistant tool-call turns lack the
|
|
25
|
+
* non-empty reasoning replay a reasoning-requiring provider needs. Lets an
|
|
26
|
+
* embedder decide per-candidate whether to keep thinking on, disable it, or
|
|
27
|
+
* skip the candidate — before sending.
|
|
28
|
+
*/
|
|
29
|
+
export function assessReasoningReplay(turns, options) {
|
|
30
|
+
const offendingCallIds = [];
|
|
31
|
+
for (const message of turns) {
|
|
32
|
+
if (message.role !== "assistant" || !message.toolCalls?.length)
|
|
33
|
+
continue;
|
|
34
|
+
const replay = options.replayForAssistant?.(message);
|
|
35
|
+
const reasoning = typeof replay?.reasoning_content === "string" ? replay.reasoning_content.trim() : "";
|
|
36
|
+
if (!reasoning) {
|
|
37
|
+
for (const tc of message.toolCalls)
|
|
38
|
+
offendingCallIds.push(tc.id);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { ok: offendingCallIds.length === 0, offendingCallIds };
|
|
42
|
+
}
|
|
43
|
+
function reasoningReplayError(callIds, descriptor) {
|
|
44
|
+
const provider = descriptor ? `${descriptor.provider}/${descriptor.model}` : "provider";
|
|
45
|
+
return new ProviderReplayValidationError(`${provider} replay requires non-empty reasoning_content for assistant tool call turn ${callIds.join(", ")}. ` +
|
|
46
|
+
"Disable thinking, rebuild this history with provider replay, switch to a provider that can replay this turn, " +
|
|
47
|
+
"or pass extensions.degradeMissingReasoningReplay to send a degraded turn.");
|
|
48
|
+
}
|
|
49
|
+
function toolResultParts(message) {
|
|
50
|
+
return (message.contentParts ?? [])
|
|
51
|
+
.filter((part) => part.type === "tool_result");
|
|
52
|
+
}
|
|
53
|
+
function validateStrictToolResultPairing(turns) {
|
|
54
|
+
let pendingIds;
|
|
55
|
+
let completedIds = new Set();
|
|
56
|
+
const assertAllCompleted = () => {
|
|
57
|
+
if (!pendingIds)
|
|
58
|
+
return;
|
|
59
|
+
const missing = [...pendingIds].filter(id => !completedIds.has(id));
|
|
60
|
+
if (missing.length) {
|
|
61
|
+
throw new ProviderReplayValidationError(`OpenAI-compatible replay has assistant tool_calls with no tool result for ${missing.join(", ")}: ` +
|
|
62
|
+
"every tool_call must be answered by a tool message before the next assistant or user turn.");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
for (const message of turns) {
|
|
66
|
+
if (message.role === "assistant") {
|
|
67
|
+
assertAllCompleted();
|
|
68
|
+
const toolCalls = message.toolCalls ?? [];
|
|
69
|
+
pendingIds = toolCalls.length ? new Set(toolCalls.map(tc => tc.id)) : undefined;
|
|
70
|
+
completedIds = new Set();
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (message.role !== "tool") {
|
|
74
|
+
assertAllCompleted();
|
|
75
|
+
pendingIds = undefined;
|
|
76
|
+
completedIds = new Set();
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
for (const part of toolResultParts(message)) {
|
|
80
|
+
if (!pendingIds?.has(part.callId)) {
|
|
81
|
+
throw new ProviderReplayValidationError(`OpenAI-compatible replay has orphan tool result ${part.callId}: no preceding assistant tool_call with the same id.`);
|
|
82
|
+
}
|
|
83
|
+
if (completedIds.has(part.callId)) {
|
|
84
|
+
throw new ProviderReplayValidationError(`OpenAI-compatible replay has duplicate tool result ${part.callId}.`);
|
|
85
|
+
}
|
|
86
|
+
completedIds.add(part.callId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
assertAllCompleted();
|
|
90
|
+
}
|