@deepstrike/sdk 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -6
- package/dist/agent.js +4 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +10 -1
- package/dist/providers/anthropic.d.ts +11 -2
- package/dist/providers/anthropic.js +46 -10
- package/dist/providers/base.d.ts +3 -0
- package/dist/providers/base.js +79 -0
- package/dist/providers/catalog.d.ts +14 -0
- package/dist/providers/catalog.js +45 -0
- package/dist/providers/deepseek.d.ts +9 -0
- package/dist/providers/deepseek.js +64 -0
- package/dist/providers/gemini.d.ts +14 -0
- package/dist/providers/gemini.js +142 -0
- package/dist/providers/kimi.d.ts +7 -0
- package/dist/providers/kimi.js +8 -0
- package/dist/providers/minimax.d.ts +7 -0
- package/dist/providers/minimax.js +10 -0
- package/dist/providers/openai-chat.d.ts +27 -0
- package/dist/providers/openai-chat.js +41 -0
- package/dist/providers/openai-responses.d.ts +42 -0
- package/dist/providers/openai-responses.js +220 -0
- package/dist/providers/openai.d.ts +4 -36
- package/dist/providers/openai.js +12 -121
- package/dist/providers/profiles.d.ts +342 -0
- package/dist/providers/profiles.js +190 -0
- package/dist/providers/qwen.d.ts +18 -0
- package/dist/providers/qwen.js +103 -0
- package/dist/runtime/server.d.ts +7 -0
- package/dist/runtime/server.js +37 -0
- package/dist/types.d.ts +17 -2
- package/package.json +5 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
2
|
+
import { endpointProfiles } from "./profiles.js";
|
|
3
|
+
const MOONSHOT_BASE = endpointProfiles["kimi.openai"].baseURL;
|
|
4
|
+
export class KimiProvider extends OpenAIChatProvider {
|
|
5
|
+
constructor(apiKey, model = "kimi-k2.6", retry, baseURL = MOONSHOT_BASE) {
|
|
6
|
+
super(apiKey, model, retry, baseURL);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AnthropicProvider } from "./anthropic.js";
|
|
2
|
+
export declare class MiniMaxProvider extends AnthropicProvider {
|
|
3
|
+
constructor(apiKey: string, model?: "MiniMax-M2.7" | "MiniMax-M2.5", retry?: {
|
|
4
|
+
maxRetries: number;
|
|
5
|
+
baseDelay: number;
|
|
6
|
+
}, baseURL?: string);
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnthropicProvider } from "./anthropic.js";
|
|
2
|
+
import { endpointProfiles } from "./profiles.js";
|
|
3
|
+
export class MiniMaxProvider extends AnthropicProvider {
|
|
4
|
+
constructor(apiKey, model = "MiniMax-M2.7", retry, baseURL = endpointProfiles["minimax.anthropic"].baseURL) {
|
|
5
|
+
super(apiKey, model, retry, {
|
|
6
|
+
baseURL,
|
|
7
|
+
authMode: "api-key",
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type OpenAI from "openai";
|
|
2
|
+
import type { Message, ToolSchema } from "../types.js";
|
|
3
|
+
export declare class OpenAIChatAdapter {
|
|
4
|
+
private replayFields;
|
|
5
|
+
buildTools(tools: ToolSchema[]): {
|
|
6
|
+
type: "function";
|
|
7
|
+
function: {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: any;
|
|
11
|
+
};
|
|
12
|
+
}[];
|
|
13
|
+
buildMessages(messages: Message[]): OpenAI.ChatCompletionMessageParam[];
|
|
14
|
+
normalizeToolCalls(toolCalls?: Array<{
|
|
15
|
+
id: string;
|
|
16
|
+
function: {
|
|
17
|
+
name: string;
|
|
18
|
+
arguments: string;
|
|
19
|
+
};
|
|
20
|
+
}>): Array<{
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
arguments: string;
|
|
24
|
+
}>;
|
|
25
|
+
rememberReplayFields(message: Pick<Message, "content" | "toolCalls">, fields: Record<string, unknown>): void;
|
|
26
|
+
private assistantReplayKey;
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { normalizeToolCall, toOpenAIMessageParams } from "./base.js";
|
|
2
|
+
export class OpenAIChatAdapter {
|
|
3
|
+
replayFields = new Map();
|
|
4
|
+
buildTools(tools) {
|
|
5
|
+
return tools.map(t => ({
|
|
6
|
+
type: "function",
|
|
7
|
+
function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
buildMessages(messages) {
|
|
11
|
+
const serialized = toOpenAIMessageParams(messages);
|
|
12
|
+
let cursor = 0;
|
|
13
|
+
for (const source of messages) {
|
|
14
|
+
if (source.role === "tool") {
|
|
15
|
+
cursor += (source.contentParts ?? []).filter(p => p.type === "tool_result").length;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (source.role === "assistant") {
|
|
19
|
+
const replay = this.replayFields.get(this.assistantReplayKey(source));
|
|
20
|
+
if (replay)
|
|
21
|
+
serialized[cursor] = { ...serialized[cursor], ...replay };
|
|
22
|
+
}
|
|
23
|
+
cursor += 1;
|
|
24
|
+
}
|
|
25
|
+
return serialized;
|
|
26
|
+
}
|
|
27
|
+
normalizeToolCalls(toolCalls = []) {
|
|
28
|
+
return toolCalls
|
|
29
|
+
.map(tc => normalizeToolCall(tc.id, tc.function.name, tc.function.arguments))
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
rememberReplayFields(message, fields) {
|
|
33
|
+
this.replayFields.set(this.assistantReplayKey(message), fields);
|
|
34
|
+
}
|
|
35
|
+
assistantReplayKey(message) {
|
|
36
|
+
return JSON.stringify({
|
|
37
|
+
content: message.content,
|
|
38
|
+
toolCalls: message.toolCalls ?? [],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import type { Message, ProviderRunState, StreamEvent, ToolSchema, LLMProvider } from "../types.js";
|
|
3
|
+
import { CircuitBreaker } from "./base.js";
|
|
4
|
+
export interface OpenAIResponsesRunState extends ProviderRunState {
|
|
5
|
+
previousResponseId?: string;
|
|
6
|
+
coveredMessageCount: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class OpenAIResponsesAdapter {
|
|
9
|
+
buildTools(tools: ToolSchema[]): {
|
|
10
|
+
type: "function";
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
parameters: any;
|
|
14
|
+
}[];
|
|
15
|
+
buildInstructions(messages: Message[]): string | undefined;
|
|
16
|
+
buildInput(messages: Message[], state?: OpenAIResponsesRunState): Array<Record<string, unknown>>;
|
|
17
|
+
decodeOutput(output: Array<Record<string, unknown>>): {
|
|
18
|
+
content: string;
|
|
19
|
+
toolCalls: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
arguments: string;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
private buildMessageContent;
|
|
26
|
+
}
|
|
27
|
+
export declare class OpenAIResponsesProvider implements LLMProvider {
|
|
28
|
+
protected readonly model: string;
|
|
29
|
+
protected client: OpenAI;
|
|
30
|
+
protected circuit: CircuitBreaker;
|
|
31
|
+
protected maxRetries: number;
|
|
32
|
+
protected baseDelay: number;
|
|
33
|
+
protected readonly responses: OpenAIResponsesAdapter;
|
|
34
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
35
|
+
maxRetries: number;
|
|
36
|
+
baseDelay: number;
|
|
37
|
+
}, baseURL?: string);
|
|
38
|
+
createRunState(): OpenAIResponsesRunState;
|
|
39
|
+
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
40
|
+
stream(messages: Message[], tools: ToolSchema[], _extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
|
|
41
|
+
private asRunState;
|
|
42
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
+
import { CircuitBreaker } from "./base.js";
|
|
4
|
+
import { normalizeToolCall } from "./base.js";
|
|
5
|
+
export class OpenAIResponsesAdapter {
|
|
6
|
+
buildTools(tools) {
|
|
7
|
+
return tools.map(t => ({
|
|
8
|
+
type: "function",
|
|
9
|
+
name: t.name,
|
|
10
|
+
description: t.description,
|
|
11
|
+
parameters: JSON.parse(t.parameters),
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
buildInstructions(messages) {
|
|
15
|
+
const instructions = messages
|
|
16
|
+
.filter(message => message.role === "system")
|
|
17
|
+
.map(message => message.content)
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
return instructions.length ? instructions.join("\n\n") : undefined;
|
|
20
|
+
}
|
|
21
|
+
buildInput(messages, state) {
|
|
22
|
+
const input = [];
|
|
23
|
+
const uncoveredMessages = state?.previousResponseId
|
|
24
|
+
? messages.slice(state.coveredMessageCount)
|
|
25
|
+
: messages;
|
|
26
|
+
for (const message of uncoveredMessages) {
|
|
27
|
+
if (message.role === "system") {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
31
|
+
if (message.content || message.contentParts?.length) {
|
|
32
|
+
input.push({
|
|
33
|
+
role: "assistant",
|
|
34
|
+
content: this.buildMessageContent(message),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
for (const tc of message.toolCalls) {
|
|
38
|
+
input.push({
|
|
39
|
+
type: "function_call",
|
|
40
|
+
call_id: tc.id,
|
|
41
|
+
name: tc.name,
|
|
42
|
+
arguments: tc.arguments,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (message.role === "tool") {
|
|
48
|
+
for (const part of message.contentParts ?? []) {
|
|
49
|
+
if (part.type !== "tool_result")
|
|
50
|
+
continue;
|
|
51
|
+
input.push({
|
|
52
|
+
type: "function_call_output",
|
|
53
|
+
call_id: part.callId,
|
|
54
|
+
output: part.output,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
input.push({
|
|
60
|
+
role: message.role,
|
|
61
|
+
content: this.buildMessageContent(message),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
decodeOutput(output) {
|
|
67
|
+
let content = "";
|
|
68
|
+
const toolCalls = [];
|
|
69
|
+
for (const item of output) {
|
|
70
|
+
if (item.type === "message") {
|
|
71
|
+
for (const part of item.content ?? []) {
|
|
72
|
+
if (part.type === "output_text")
|
|
73
|
+
content += String(part.text ?? "");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (item.type === "function_call") {
|
|
77
|
+
const toolCall = normalizeToolCall(String(item.call_id ?? item.id ?? ""), String(item.name ?? ""), item.arguments ?? "{}");
|
|
78
|
+
if (toolCall)
|
|
79
|
+
toolCalls.push(toolCall);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { content, toolCalls };
|
|
83
|
+
}
|
|
84
|
+
buildMessageContent(message) {
|
|
85
|
+
if (!message.contentParts?.length)
|
|
86
|
+
return message.content;
|
|
87
|
+
const content = [];
|
|
88
|
+
for (const part of message.contentParts) {
|
|
89
|
+
if (part.type === "text") {
|
|
90
|
+
content.push({ type: "input_text", text: part.text });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (part.type === "image") {
|
|
94
|
+
const imageUrl = part.url ?? (part.data && part.mediaType
|
|
95
|
+
? `data:${part.mediaType};base64,${part.data}`
|
|
96
|
+
: undefined);
|
|
97
|
+
if (imageUrl)
|
|
98
|
+
content.push({
|
|
99
|
+
type: "input_image",
|
|
100
|
+
detail: part.detail ?? "auto",
|
|
101
|
+
image_url: imageUrl,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return content;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export class OpenAIResponsesProvider {
|
|
109
|
+
model;
|
|
110
|
+
client;
|
|
111
|
+
circuit;
|
|
112
|
+
maxRetries;
|
|
113
|
+
baseDelay;
|
|
114
|
+
responses = new OpenAIResponsesAdapter();
|
|
115
|
+
constructor(apiKey, model = "gpt-4.1", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1") {
|
|
116
|
+
this.model = model;
|
|
117
|
+
this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey, baseURL }));
|
|
118
|
+
this.circuit = new CircuitBreaker();
|
|
119
|
+
this.maxRetries = retry.maxRetries;
|
|
120
|
+
this.baseDelay = retry.baseDelay;
|
|
121
|
+
}
|
|
122
|
+
createRunState() {
|
|
123
|
+
return { coveredMessageCount: 0 };
|
|
124
|
+
}
|
|
125
|
+
async complete(messages, tools) {
|
|
126
|
+
if (this.circuit.isOpen())
|
|
127
|
+
throw new Error("Circuit breaker open");
|
|
128
|
+
let lastErr;
|
|
129
|
+
for (let i = 0; i < this.maxRetries; i++) {
|
|
130
|
+
try {
|
|
131
|
+
const instructions = this.responses.buildInstructions(messages);
|
|
132
|
+
const resp = await this.client.responses.create({
|
|
133
|
+
model: this.model,
|
|
134
|
+
input: this.responses.buildInput(messages),
|
|
135
|
+
...(instructions ? { instructions } : {}),
|
|
136
|
+
...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
|
|
137
|
+
});
|
|
138
|
+
this.circuit.recordSuccess();
|
|
139
|
+
const decoded = this.responses.decodeOutput(resp.output);
|
|
140
|
+
return {
|
|
141
|
+
role: "assistant",
|
|
142
|
+
content: decoded.content,
|
|
143
|
+
toolCalls: decoded.toolCalls,
|
|
144
|
+
tokenCount: resp.usage?.total_tokens,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
lastErr = err;
|
|
149
|
+
this.circuit.recordFailure();
|
|
150
|
+
if (i < this.maxRetries - 1)
|
|
151
|
+
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw lastErr;
|
|
155
|
+
}
|
|
156
|
+
async *stream(messages, tools, _extensions, state) {
|
|
157
|
+
const runState = this.asRunState(state);
|
|
158
|
+
const functionCalls = new Map();
|
|
159
|
+
const instructions = this.responses.buildInstructions(messages);
|
|
160
|
+
const stream = await this.client.responses.create({
|
|
161
|
+
model: this.model,
|
|
162
|
+
input: this.responses.buildInput(messages, runState),
|
|
163
|
+
...(instructions ? { instructions } : {}),
|
|
164
|
+
...(runState.previousResponseId ? { previous_response_id: runState.previousResponseId } : {}),
|
|
165
|
+
...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
|
|
166
|
+
stream: true,
|
|
167
|
+
});
|
|
168
|
+
for await (const evt of stream) {
|
|
169
|
+
if (evt.type === "response.output_text.delta") {
|
|
170
|
+
yield { type: "text_delta", delta: evt.delta };
|
|
171
|
+
}
|
|
172
|
+
else if (evt.type === "response.output_item.added" && evt.item.type === "function_call") {
|
|
173
|
+
functionCalls.set(evt.output_index, {
|
|
174
|
+
id: evt.item.call_id,
|
|
175
|
+
name: evt.item.name,
|
|
176
|
+
argsBuf: evt.item.arguments ?? "",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else if (evt.type === "response.function_call_arguments.delta") {
|
|
180
|
+
const call = functionCalls.get(evt.output_index);
|
|
181
|
+
if (call)
|
|
182
|
+
call.argsBuf += evt.delta;
|
|
183
|
+
}
|
|
184
|
+
else if (evt.type === "response.function_call_arguments.done") {
|
|
185
|
+
const call = functionCalls.get(evt.output_index);
|
|
186
|
+
if (call)
|
|
187
|
+
call.argsBuf = evt.arguments;
|
|
188
|
+
}
|
|
189
|
+
else if (evt.type === "response.output_item.done" && evt.item.type === "function_call") {
|
|
190
|
+
const call = functionCalls.get(evt.output_index) ?? {
|
|
191
|
+
id: evt.item.call_id,
|
|
192
|
+
name: evt.item.name,
|
|
193
|
+
argsBuf: evt.item.arguments ?? "{}",
|
|
194
|
+
};
|
|
195
|
+
let args = {};
|
|
196
|
+
try {
|
|
197
|
+
args = JSON.parse(call.argsBuf || "{}");
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
args = {};
|
|
201
|
+
}
|
|
202
|
+
yield { type: "tool_call", id: call.id, name: call.name, arguments: args };
|
|
203
|
+
}
|
|
204
|
+
else if (evt.type === "response.completed") {
|
|
205
|
+
runState.previousResponseId = evt.response.id;
|
|
206
|
+
runState.coveredMessageCount = messages.length + 1;
|
|
207
|
+
if (evt.response.usage?.total_tokens) {
|
|
208
|
+
yield { type: "usage", totalTokens: evt.response.usage.total_tokens };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
asRunState(state) {
|
|
214
|
+
if (!state)
|
|
215
|
+
return this.createRunState();
|
|
216
|
+
if (typeof state.coveredMessageCount !== "number")
|
|
217
|
+
state.coveredMessageCount = 0;
|
|
218
|
+
return state;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -1,51 +1,19 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
|
-
|
|
4
|
+
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
|
+
export declare class OpenAIChatProvider implements LLMProvider {
|
|
5
6
|
protected readonly model: string;
|
|
6
7
|
protected client: OpenAI;
|
|
7
8
|
protected circuit: CircuitBreaker;
|
|
8
9
|
protected maxRetries: number;
|
|
9
10
|
protected baseDelay: number;
|
|
11
|
+
protected readonly chat: OpenAIChatAdapter;
|
|
10
12
|
constructor(apiKey: string, model?: string, retry?: {
|
|
11
13
|
maxRetries: number;
|
|
12
14
|
baseDelay: number;
|
|
13
15
|
}, baseURL?: string);
|
|
14
|
-
protected buildTools(tools: ToolSchema[]): {
|
|
15
|
-
type: "function";
|
|
16
|
-
function: {
|
|
17
|
-
name: string;
|
|
18
|
-
description: string;
|
|
19
|
-
parameters: any;
|
|
20
|
-
};
|
|
21
|
-
}[];
|
|
22
16
|
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
23
17
|
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
24
18
|
}
|
|
25
|
-
export
|
|
26
|
-
constructor(apiKey: string, model?: string, retry?: {
|
|
27
|
-
maxRetries: number;
|
|
28
|
-
baseDelay: number;
|
|
29
|
-
});
|
|
30
|
-
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
31
|
-
}
|
|
32
|
-
export declare class DeepSeekProvider extends OpenAIProvider {
|
|
33
|
-
constructor(apiKey: string, model?: string, retry?: {
|
|
34
|
-
maxRetries: number;
|
|
35
|
-
baseDelay: number;
|
|
36
|
-
});
|
|
37
|
-
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
38
|
-
}
|
|
39
|
-
export declare class MiniMaxProvider extends OpenAIProvider {
|
|
40
|
-
constructor(apiKey: string, model?: string, retry?: {
|
|
41
|
-
maxRetries: number;
|
|
42
|
-
baseDelay: number;
|
|
43
|
-
});
|
|
44
|
-
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
45
|
-
}
|
|
46
|
-
export declare class KimiProvider extends OpenAIProvider {
|
|
47
|
-
constructor(apiKey: string, model?: string, retry?: {
|
|
48
|
-
maxRetries: number;
|
|
49
|
-
baseDelay: number;
|
|
50
|
-
});
|
|
51
|
-
}
|
|
19
|
+
export { OpenAIChatProvider as OpenAIProvider };
|
package/dist/providers/openai.js
CHANGED
|
@@ -1,39 +1,36 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
+
import { CircuitBreaker } from "./base.js";
|
|
4
|
+
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
|
+
export class OpenAIChatProvider {
|
|
4
6
|
model;
|
|
5
7
|
client;
|
|
6
8
|
circuit;
|
|
7
9
|
maxRetries;
|
|
8
10
|
baseDelay;
|
|
11
|
+
chat = new OpenAIChatAdapter();
|
|
9
12
|
constructor(apiKey, model = "gpt-4o", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = "https://api.openai.com/v1") {
|
|
10
13
|
this.model = model;
|
|
11
|
-
this.client = new OpenAI({ apiKey, baseURL });
|
|
14
|
+
this.client = withServerRuntimeGuard(() => new OpenAI({ apiKey, baseURL }));
|
|
12
15
|
this.circuit = new CircuitBreaker();
|
|
13
16
|
this.maxRetries = retry.maxRetries;
|
|
14
17
|
this.baseDelay = retry.baseDelay;
|
|
15
18
|
}
|
|
16
|
-
buildTools(tools) {
|
|
17
|
-
return tools.map(t => ({
|
|
18
|
-
type: "function",
|
|
19
|
-
function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
|
|
20
|
-
}));
|
|
21
|
-
}
|
|
22
19
|
async complete(messages, tools) {
|
|
23
20
|
if (this.circuit.isOpen())
|
|
24
21
|
throw new Error("Circuit breaker open");
|
|
25
|
-
const msgs =
|
|
22
|
+
const msgs = this.chat.buildMessages(messages);
|
|
26
23
|
let lastErr;
|
|
27
24
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
28
25
|
try {
|
|
29
26
|
const resp = await this.client.chat.completions.create({
|
|
30
27
|
model: this.model,
|
|
31
28
|
messages: msgs,
|
|
32
|
-
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
29
|
+
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
33
30
|
});
|
|
34
31
|
this.circuit.recordSuccess();
|
|
35
32
|
const choice = resp.choices[0].message;
|
|
36
|
-
const toolCalls = (choice.tool_calls ?? [])
|
|
33
|
+
const toolCalls = this.chat.normalizeToolCalls(choice.tool_calls ?? []);
|
|
37
34
|
return { role: "assistant", content: choice.content ?? "", tokenCount: resp.usage?.total_tokens, toolCalls };
|
|
38
35
|
}
|
|
39
36
|
catch (err) {
|
|
@@ -46,12 +43,12 @@ export class OpenAIProvider {
|
|
|
46
43
|
throw lastErr;
|
|
47
44
|
}
|
|
48
45
|
async *stream(messages, tools, extensions) {
|
|
49
|
-
const msgs =
|
|
46
|
+
const msgs = this.chat.buildMessages(messages);
|
|
50
47
|
const toolCallBufs = {};
|
|
51
48
|
const stream = await this.client.chat.completions.create({
|
|
52
49
|
model: this.model,
|
|
53
50
|
messages: msgs,
|
|
54
|
-
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
51
|
+
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
55
52
|
stream: true,
|
|
56
53
|
stream_options: { include_usage: true },
|
|
57
54
|
});
|
|
@@ -92,110 +89,4 @@ export class OpenAIProvider {
|
|
|
92
89
|
yield { type: "usage", totalTokens };
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
const DEEPSEEK_BASE = "https://api.deepseek.com/v1";
|
|
97
|
-
const MINIMAX_BASE = "https://api.minimax.chat/v1";
|
|
98
|
-
const MOONSHOT_BASE = "https://api.moonshot.cn/v1";
|
|
99
|
-
const DEEPSEEK_REASONERS = new Set(["deepseek-reasoner", "deepseek-r1"]);
|
|
100
|
-
const MINIMAX_REASONERS = new Set(["MiniMax-M1", "minimax-m1"]);
|
|
101
|
-
export class QwenProvider extends OpenAIProvider {
|
|
102
|
-
constructor(apiKey, model = "qwen-max", retry) {
|
|
103
|
-
super(apiKey, model, retry, DASHSCOPE_BASE);
|
|
104
|
-
}
|
|
105
|
-
async *stream(messages, tools, extensions) {
|
|
106
|
-
const enableThinking = Boolean(extensions?.enableThinking);
|
|
107
|
-
const thinkingBudget = extensions?.thinkingBudget;
|
|
108
|
-
const msgs = messages.map(m => ({ role: m.role, content: toOpenAIContent(m) }));
|
|
109
|
-
const toolCallBufs = {};
|
|
110
|
-
const stream = await this.client.chat.completions.create({
|
|
111
|
-
model: this.model,
|
|
112
|
-
messages: msgs,
|
|
113
|
-
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
114
|
-
stream: true,
|
|
115
|
-
...(enableThinking ? { extra_body: { enable_thinking: true, ...(thinkingBudget ? { thinking_budget: thinkingBudget } : {}) } } : {}),
|
|
116
|
-
});
|
|
117
|
-
for await (const chunk of stream) {
|
|
118
|
-
const choice = chunk.choices[0];
|
|
119
|
-
if (!choice)
|
|
120
|
-
continue;
|
|
121
|
-
const delta = choice.delta;
|
|
122
|
-
if (delta.reasoning_content)
|
|
123
|
-
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
124
|
-
if (delta.content)
|
|
125
|
-
yield { type: "text_delta", delta: delta.content };
|
|
126
|
-
for (const tc of delta.tool_calls ?? []) {
|
|
127
|
-
const idx = tc.index;
|
|
128
|
-
if (!toolCallBufs[idx])
|
|
129
|
-
toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
|
|
130
|
-
if (tc.function?.name)
|
|
131
|
-
toolCallBufs[idx].name += tc.function.name;
|
|
132
|
-
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
133
|
-
}
|
|
134
|
-
if (choice.finish_reason === "tool_calls") {
|
|
135
|
-
for (const tb of Object.values(toolCallBufs)) {
|
|
136
|
-
let args = {};
|
|
137
|
-
try {
|
|
138
|
-
args = JSON.parse(tb.argsBuf || "{}");
|
|
139
|
-
}
|
|
140
|
-
catch {
|
|
141
|
-
args = {};
|
|
142
|
-
}
|
|
143
|
-
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
export class DeepSeekProvider extends OpenAIProvider {
|
|
150
|
-
constructor(apiKey, model = "deepseek-chat", retry) {
|
|
151
|
-
super(apiKey, model, retry, DEEPSEEK_BASE);
|
|
152
|
-
}
|
|
153
|
-
async *stream(messages, tools, extensions) {
|
|
154
|
-
const exposeReasoning = extensions?.exposeReasoning ?? false;
|
|
155
|
-
const msgs = messages.map(m => ({ role: m.role, content: toOpenAIContent(m) }));
|
|
156
|
-
const isReasoner = DEEPSEEK_REASONERS.has(this.model);
|
|
157
|
-
const stream = await this.client.chat.completions.create({
|
|
158
|
-
model: this.model,
|
|
159
|
-
messages: msgs,
|
|
160
|
-
...(!isReasoner && tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
161
|
-
stream: true,
|
|
162
|
-
});
|
|
163
|
-
for await (const chunk of stream) {
|
|
164
|
-
const delta = chunk.choices[0]?.delta;
|
|
165
|
-
if (exposeReasoning && delta.reasoning_content) {
|
|
166
|
-
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
167
|
-
}
|
|
168
|
-
if (delta.content)
|
|
169
|
-
yield { type: "text_delta", delta: delta.content };
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
export class MiniMaxProvider extends OpenAIProvider {
|
|
174
|
-
constructor(apiKey, model = "MiniMax-Text-01", retry) {
|
|
175
|
-
super(apiKey, model, retry, MINIMAX_BASE);
|
|
176
|
-
}
|
|
177
|
-
async *stream(messages, tools, extensions) {
|
|
178
|
-
const exposeReasoning = extensions?.exposeReasoning ?? false;
|
|
179
|
-
const msgs = messages.map(m => ({ role: m.role, content: toOpenAIContent(m) }));
|
|
180
|
-
const isReasoner = MINIMAX_REASONERS.has(this.model);
|
|
181
|
-
const stream = await this.client.chat.completions.create({
|
|
182
|
-
model: this.model,
|
|
183
|
-
messages: msgs,
|
|
184
|
-
...(!isReasoner && tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
185
|
-
stream: true,
|
|
186
|
-
});
|
|
187
|
-
for await (const chunk of stream) {
|
|
188
|
-
const delta = chunk.choices[0]?.delta;
|
|
189
|
-
if (exposeReasoning && delta.reasoning_content) {
|
|
190
|
-
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
191
|
-
}
|
|
192
|
-
if (delta.content)
|
|
193
|
-
yield { type: "text_delta", delta: delta.content };
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
export class KimiProvider extends OpenAIProvider {
|
|
198
|
-
constructor(apiKey, model = "moonshot-v1-8k", retry) {
|
|
199
|
-
super(apiKey, model, retry, MOONSHOT_BASE);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
92
|
+
export { OpenAIChatProvider as OpenAIProvider };
|