@deepstrike/sdk 0.1.5 → 0.1.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/README.md +31 -20
- package/dist/agent.d.ts +2 -1
- package/dist/agent.js +18 -20
- package/dist/governance.d.ts +17 -0
- package/dist/governance.js +34 -0
- package/dist/harness/harness.js +4 -9
- package/dist/index.d.ts +15 -4
- package/dist/index.js +11 -8
- package/dist/kernel.d.ts +149 -0
- package/dist/kernel.js +9 -0
- package/dist/memory/protocols.d.ts +2 -1
- package/dist/memory/protocols.js +0 -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/safety/permissions.d.ts +1 -0
- package/dist/safety/permissions.js +3 -0
- package/dist/signals/gateway.js +4 -0
- package/dist/signals/scheduled.js +4 -0
- package/dist/signals/types.d.ts +10 -1
- package/dist/types.d.ts +17 -2
- package/package.json +9 -4
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
+
import { CircuitBreaker, normalizeToolCall, splitAnthropicSystem, toAnthropicMessages } from "./base.js";
|
|
3
4
|
export class AnthropicProvider {
|
|
4
5
|
model;
|
|
5
6
|
client;
|
|
6
7
|
circuit;
|
|
7
8
|
maxRetries;
|
|
8
9
|
baseDelay;
|
|
9
|
-
|
|
10
|
+
nativeAssistantBlocks = new Map();
|
|
11
|
+
constructor(apiKey, model = "claude-sonnet-4-6", retry = { maxRetries: 3, baseDelay: 1000 }, options = {}) {
|
|
10
12
|
this.model = model;
|
|
11
|
-
this.client = new Anthropic({
|
|
13
|
+
this.client = withServerRuntimeGuard(() => new Anthropic({
|
|
14
|
+
...(options.authMode === "bearer" ? { authToken: apiKey } : { apiKey }),
|
|
15
|
+
...(options.baseURL ? { baseURL: options.baseURL } : {}),
|
|
16
|
+
}));
|
|
12
17
|
this.circuit = new CircuitBreaker();
|
|
13
18
|
this.maxRetries = retry.maxRetries;
|
|
14
19
|
this.baseDelay = retry.baseDelay;
|
|
@@ -23,8 +28,8 @@ export class AnthropicProvider {
|
|
|
23
28
|
async complete(messages, tools) {
|
|
24
29
|
if (this.circuit.isOpen())
|
|
25
30
|
throw new Error("Circuit breaker open");
|
|
26
|
-
const system = messages
|
|
27
|
-
const msgs =
|
|
31
|
+
const system = splitAnthropicSystem(messages);
|
|
32
|
+
const msgs = this.buildMessages(messages);
|
|
28
33
|
let lastErr;
|
|
29
34
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
30
35
|
try {
|
|
@@ -47,7 +52,9 @@ export class AnthropicProvider {
|
|
|
47
52
|
toolCalls.push(tc);
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
|
-
|
|
55
|
+
const message = { role: "assistant", content, tokenCount: resp.usage.input_tokens + resp.usage.output_tokens, toolCalls };
|
|
56
|
+
this.rememberNativeBlocks(message, resp.content);
|
|
57
|
+
return message;
|
|
51
58
|
}
|
|
52
59
|
catch (err) {
|
|
53
60
|
lastErr = err;
|
|
@@ -59,9 +66,12 @@ export class AnthropicProvider {
|
|
|
59
66
|
throw lastErr;
|
|
60
67
|
}
|
|
61
68
|
async *stream(messages, tools, extensions) {
|
|
62
|
-
const system = messages
|
|
63
|
-
const msgs =
|
|
69
|
+
const system = splitAnthropicSystem(messages);
|
|
70
|
+
const msgs = this.buildMessages(messages);
|
|
64
71
|
const toolBlocks = {};
|
|
72
|
+
const nativeBlocks = {};
|
|
73
|
+
let finalText = "";
|
|
74
|
+
const finalToolCalls = [];
|
|
65
75
|
const stream = this.client.messages.stream({
|
|
66
76
|
model: this.model,
|
|
67
77
|
max_tokens: 8096,
|
|
@@ -70,17 +80,26 @@ export class AnthropicProvider {
|
|
|
70
80
|
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
71
81
|
});
|
|
72
82
|
for await (const evt of stream) {
|
|
73
|
-
if (evt.type === "content_block_start"
|
|
74
|
-
|
|
83
|
+
if (evt.type === "content_block_start") {
|
|
84
|
+
nativeBlocks[evt.index] = { ...evt.content_block };
|
|
85
|
+
if (evt.content_block.type === "tool_use") {
|
|
86
|
+
toolBlocks[evt.index] = { id: evt.content_block.id, name: evt.content_block.name, argsBuf: "" };
|
|
87
|
+
}
|
|
75
88
|
}
|
|
76
89
|
else if (evt.type === "content_block_delta") {
|
|
77
90
|
const d = evt.delta;
|
|
78
91
|
if (d.type === "text_delta") {
|
|
92
|
+
finalText += d.text;
|
|
93
|
+
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], text: String(nativeBlocks[evt.index]?.text ?? "") + d.text };
|
|
79
94
|
yield { type: "text_delta", delta: d.text };
|
|
80
95
|
}
|
|
81
96
|
else if (d.type === "thinking_delta") {
|
|
97
|
+
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], thinking: String(nativeBlocks[evt.index]?.thinking ?? "") + d.thinking };
|
|
82
98
|
yield { type: "thinking_delta", delta: d.thinking };
|
|
83
99
|
}
|
|
100
|
+
else if (d.type === "signature_delta") {
|
|
101
|
+
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], signature: String(nativeBlocks[evt.index]?.signature ?? "") + d.signature };
|
|
102
|
+
}
|
|
84
103
|
else if (d.type === "input_json_delta" && toolBlocks[evt.index]) {
|
|
85
104
|
toolBlocks[evt.index].argsBuf += d.partial_json;
|
|
86
105
|
}
|
|
@@ -95,8 +114,25 @@ export class AnthropicProvider {
|
|
|
95
114
|
catch {
|
|
96
115
|
args = {};
|
|
97
116
|
}
|
|
117
|
+
nativeBlocks[evt.index] = { ...nativeBlocks[evt.index], input: args };
|
|
118
|
+
finalToolCalls.push({ id: tb.id, name: tb.name, arguments: JSON.stringify(args) });
|
|
98
119
|
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
99
120
|
}
|
|
100
121
|
}
|
|
122
|
+
this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
|
|
123
|
+
}
|
|
124
|
+
buildMessages(messages) {
|
|
125
|
+
return toAnthropicMessages(messages, message => this.nativeAssistantBlocks.get(this.assistantReplayKey(message)));
|
|
126
|
+
}
|
|
127
|
+
rememberNativeBlocks(message, blocks) {
|
|
128
|
+
if (!message.toolCalls?.length)
|
|
129
|
+
return;
|
|
130
|
+
this.nativeAssistantBlocks.set(this.assistantReplayKey(message), blocks);
|
|
131
|
+
}
|
|
132
|
+
assistantReplayKey(message) {
|
|
133
|
+
return JSON.stringify({
|
|
134
|
+
content: message.content,
|
|
135
|
+
toolCalls: message.toolCalls ?? [],
|
|
136
|
+
});
|
|
101
137
|
}
|
|
102
138
|
}
|
package/dist/providers/base.d.ts
CHANGED
|
@@ -16,3 +16,6 @@ export declare function normalizeToolCall(id: string, name: string, args: unknow
|
|
|
16
16
|
import type { Message } from "../types.js";
|
|
17
17
|
export declare function toAnthropicContent(msg: Message): string | Array<Record<string, unknown>>;
|
|
18
18
|
export declare function toOpenAIContent(msg: Message): string | Array<Record<string, unknown>>;
|
|
19
|
+
export declare function splitAnthropicSystem(messages: Message[]): string;
|
|
20
|
+
export declare function toAnthropicMessages(messages: Message[], nativeReplay?: (message: Message) => Array<Record<string, unknown>> | undefined): Array<Record<string, unknown>>;
|
|
21
|
+
export declare function toOpenAIMessageParams(messages: Message[]): Array<Record<string, unknown>>;
|
package/dist/providers/base.js
CHANGED
|
@@ -59,6 +59,9 @@ export function toAnthropicContent(msg) {
|
|
|
59
59
|
if (p.type === "audio") {
|
|
60
60
|
return { type: "text", text: `[audio: ${p.mediaType}]` };
|
|
61
61
|
}
|
|
62
|
+
if (p.type === "tool_result") {
|
|
63
|
+
return { type: "tool_result", tool_use_id: p.callId, content: p.output, is_error: p.isError };
|
|
64
|
+
}
|
|
62
65
|
return { type: "text", text: "" };
|
|
63
66
|
});
|
|
64
67
|
}
|
|
@@ -75,6 +78,82 @@ export function toOpenAIContent(msg) {
|
|
|
75
78
|
if (p.type === "audio") {
|
|
76
79
|
return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
|
|
77
80
|
}
|
|
81
|
+
if (p.type === "tool_result") {
|
|
82
|
+
return { type: "text", text: p.output };
|
|
83
|
+
}
|
|
78
84
|
return { type: "text", text: "" };
|
|
79
85
|
});
|
|
80
86
|
}
|
|
87
|
+
function parseToolArguments(args) {
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(args || "{}");
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function splitAnthropicSystem(messages) {
|
|
96
|
+
return messages.filter(m => m.role === "system").map(m => m.content).join("\n\n");
|
|
97
|
+
}
|
|
98
|
+
export function toAnthropicMessages(messages, nativeReplay) {
|
|
99
|
+
const result = [];
|
|
100
|
+
for (const msg of messages.filter(m => m.role !== "system")) {
|
|
101
|
+
if (msg.role === "tool") {
|
|
102
|
+
const parts = (msg.contentParts ?? [])
|
|
103
|
+
.filter((p) => p.type === "tool_result")
|
|
104
|
+
.map(p => ({ type: "tool_result", tool_use_id: p.callId, content: p.output, is_error: p.isError }));
|
|
105
|
+
if (parts.length)
|
|
106
|
+
result.push({ role: "user", content: parts });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
110
|
+
const replay = nativeReplay?.(msg);
|
|
111
|
+
if (replay) {
|
|
112
|
+
result.push({ role: "assistant", content: replay });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const blocks = [];
|
|
116
|
+
if (msg.content)
|
|
117
|
+
blocks.push({ type: "text", text: msg.content });
|
|
118
|
+
blocks.push(...msg.toolCalls.map(tc => ({
|
|
119
|
+
type: "tool_use",
|
|
120
|
+
id: tc.id,
|
|
121
|
+
name: tc.name,
|
|
122
|
+
input: parseToolArguments(tc.arguments),
|
|
123
|
+
})));
|
|
124
|
+
result.push({ role: "assistant", content: blocks });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
result.push({
|
|
128
|
+
role: msg.role,
|
|
129
|
+
content: toAnthropicContent(msg),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
export function toOpenAIMessageParams(messages) {
|
|
135
|
+
const result = [];
|
|
136
|
+
for (const msg of messages) {
|
|
137
|
+
if (msg.role === "tool") {
|
|
138
|
+
const parts = (msg.contentParts ?? [])
|
|
139
|
+
.filter((p) => p.type === "tool_result");
|
|
140
|
+
for (const p of parts) {
|
|
141
|
+
result.push({ role: "tool", tool_call_id: p.callId, content: p.output });
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const next = {
|
|
146
|
+
role: msg.role,
|
|
147
|
+
content: toOpenAIContent(msg),
|
|
148
|
+
};
|
|
149
|
+
if (msg.role === "assistant" && msg.toolCalls?.length) {
|
|
150
|
+
next.tool_calls = msg.toolCalls.map(tc => ({
|
|
151
|
+
id: tc.id,
|
|
152
|
+
type: "function",
|
|
153
|
+
function: { name: tc.name, arguments: tc.arguments },
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
result.push(next);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LLMProvider } from "../types.js";
|
|
2
|
+
import { endpointProfiles, type ModelProfileId } from "./profiles.js";
|
|
3
|
+
export type EndpointProfileId = keyof typeof endpointProfiles;
|
|
4
|
+
export interface CreateProviderOptions {
|
|
5
|
+
model: ModelProfileId;
|
|
6
|
+
apiKey: string;
|
|
7
|
+
endpoint?: EndpointProfileId;
|
|
8
|
+
retry?: {
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
baseDelay: number;
|
|
11
|
+
};
|
|
12
|
+
baseURL?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createProvider(options: CreateProviderOptions): LLMProvider;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
2
|
+
import { DeepSeekProvider } from "./deepseek.js";
|
|
3
|
+
import { KimiProvider } from "./kimi.js";
|
|
4
|
+
import { OpenAIResponsesProvider } from "./openai-responses.js";
|
|
5
|
+
import { MiniMaxProvider } from "./minimax.js";
|
|
6
|
+
import { QwenProvider } from "./qwen.js";
|
|
7
|
+
import { GeminiProvider } from "./gemini.js";
|
|
8
|
+
import { endpointProfiles, getModelProfile } from "./profiles.js";
|
|
9
|
+
export function createProvider(options) {
|
|
10
|
+
const profile = getModelProfile(options.model);
|
|
11
|
+
const endpointId = (options.endpoint ?? profile.defaultEndpointId);
|
|
12
|
+
const endpoint = endpointProfiles[endpointId];
|
|
13
|
+
if (!endpoint) {
|
|
14
|
+
throw new Error(`Unknown endpoint profile: ${endpointId}`);
|
|
15
|
+
}
|
|
16
|
+
if (endpoint.providerId !== profile.providerId) {
|
|
17
|
+
throw new Error(`Endpoint ${endpoint.id} does not belong to provider ${profile.providerId}`);
|
|
18
|
+
}
|
|
19
|
+
const model = options.model.slice(`${profile.providerId}/`.length);
|
|
20
|
+
const baseURL = options.baseURL ?? endpoint.baseURL;
|
|
21
|
+
if (profile.providerId === "openai") {
|
|
22
|
+
if (endpoint.protocol === "openai-chat") {
|
|
23
|
+
return new OpenAIChatProvider(options.apiKey, model, options.retry, baseURL);
|
|
24
|
+
}
|
|
25
|
+
if (endpoint.protocol === "openai-responses") {
|
|
26
|
+
return new OpenAIResponsesProvider(options.apiKey, model, options.retry, baseURL);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (profile.providerId === "minimax" && endpoint.protocol === "anthropic-messages") {
|
|
30
|
+
return new MiniMaxProvider(options.apiKey, model, options.retry, baseURL);
|
|
31
|
+
}
|
|
32
|
+
if (profile.providerId === "deepseek" && endpoint.protocol === "openai-chat") {
|
|
33
|
+
return new DeepSeekProvider(options.apiKey, model, options.retry, baseURL);
|
|
34
|
+
}
|
|
35
|
+
if (profile.providerId === "kimi" && endpoint.protocol === "openai-chat") {
|
|
36
|
+
return new KimiProvider(options.apiKey, model, options.retry, baseURL);
|
|
37
|
+
}
|
|
38
|
+
if (profile.providerId === "qwen" && endpoint.protocol === "openai-chat") {
|
|
39
|
+
return new QwenProvider(options.apiKey, model, options.retry, baseURL);
|
|
40
|
+
}
|
|
41
|
+
if (profile.providerId === "gemini" && endpoint.protocol === "gemini") {
|
|
42
|
+
return new GeminiProvider(options.apiKey, model, options.retry, baseURL);
|
|
43
|
+
}
|
|
44
|
+
throw new Error(`No Node provider factory for ${profile.id} on ${endpoint.id}`);
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Message, ToolSchema, StreamEvent } from "../types.js";
|
|
2
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
3
|
+
export declare class DeepSeekProvider extends OpenAIChatProvider {
|
|
4
|
+
constructor(apiKey: string, model?: "deepseek-v4-flash" | "deepseek-v4-pro", retry?: {
|
|
5
|
+
maxRetries: number;
|
|
6
|
+
baseDelay: number;
|
|
7
|
+
}, baseURL?: string);
|
|
8
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { OpenAIChatProvider } from "./openai.js";
|
|
2
|
+
import { endpointProfiles } from "./profiles.js";
|
|
3
|
+
const DEEPSEEK_BASE = endpointProfiles["deepseek.openai"].baseURL;
|
|
4
|
+
export class DeepSeekProvider extends OpenAIChatProvider {
|
|
5
|
+
constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = DEEPSEEK_BASE) {
|
|
6
|
+
super(apiKey, model, retry, baseURL);
|
|
7
|
+
}
|
|
8
|
+
async *stream(messages, tools, extensions) {
|
|
9
|
+
const exposeReasoning = extensions?.exposeReasoning ?? false;
|
|
10
|
+
const thinking = extensions?.thinking === false ? "disabled" : "enabled";
|
|
11
|
+
const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
|
|
12
|
+
const msgs = this.chat.buildMessages(messages);
|
|
13
|
+
const toolCallBufs = {};
|
|
14
|
+
let reasoningContent = "";
|
|
15
|
+
let finalText = "";
|
|
16
|
+
const stream = await this.client.chat.completions.create({
|
|
17
|
+
model: this.model,
|
|
18
|
+
messages: msgs,
|
|
19
|
+
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
20
|
+
stream: true,
|
|
21
|
+
reasoning_effort: reasoningEffort,
|
|
22
|
+
extra_body: { thinking: { type: thinking } },
|
|
23
|
+
});
|
|
24
|
+
for await (const chunk of stream) {
|
|
25
|
+
const choice = chunk.choices[0];
|
|
26
|
+
if (!choice)
|
|
27
|
+
continue;
|
|
28
|
+
const delta = choice.delta;
|
|
29
|
+
if (exposeReasoning && delta.reasoning_content) {
|
|
30
|
+
yield { type: "thinking_delta", delta: delta.reasoning_content };
|
|
31
|
+
}
|
|
32
|
+
if (delta.reasoning_content)
|
|
33
|
+
reasoningContent += String(delta.reasoning_content);
|
|
34
|
+
if (delta.content) {
|
|
35
|
+
finalText += String(delta.content);
|
|
36
|
+
yield { type: "text_delta", delta: delta.content };
|
|
37
|
+
}
|
|
38
|
+
for (const tc of delta.tool_calls ?? []) {
|
|
39
|
+
const idx = tc.index;
|
|
40
|
+
if (!toolCallBufs[idx])
|
|
41
|
+
toolCallBufs[idx] = { id: tc.id ?? "", name: "", argsBuf: "" };
|
|
42
|
+
if (tc.function?.name)
|
|
43
|
+
toolCallBufs[idx].name += tc.function.name;
|
|
44
|
+
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
45
|
+
}
|
|
46
|
+
if (choice.finish_reason === "tool_calls") {
|
|
47
|
+
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
48
|
+
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
49
|
+
}));
|
|
50
|
+
this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
|
|
51
|
+
for (const tb of Object.values(toolCallBufs)) {
|
|
52
|
+
let args = {};
|
|
53
|
+
try {
|
|
54
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
args = {};
|
|
58
|
+
}
|
|
59
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
|
+
export declare class GeminiProvider implements LLMProvider {
|
|
3
|
+
private readonly model;
|
|
4
|
+
private genAI;
|
|
5
|
+
private circuit;
|
|
6
|
+
private maxRetries;
|
|
7
|
+
private baseDelay;
|
|
8
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
baseDelay: number;
|
|
11
|
+
}, baseURL?: string);
|
|
12
|
+
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
13
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
2
|
+
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
+
import { CircuitBreaker, normalizeToolCall } from "./base.js";
|
|
4
|
+
import { endpointProfiles } from "./profiles.js";
|
|
5
|
+
const GEMINI_BASE = endpointProfiles["gemini.google"].baseURL;
|
|
6
|
+
function buildContents(messages) {
|
|
7
|
+
const contents = [];
|
|
8
|
+
for (const msg of messages) {
|
|
9
|
+
if (msg.role === "system")
|
|
10
|
+
continue;
|
|
11
|
+
if (msg.role === "tool") {
|
|
12
|
+
const parts = (msg.contentParts ?? [])
|
|
13
|
+
.filter(p => p.type === "tool_result")
|
|
14
|
+
.map(p => p.type === "tool_result" ? ({
|
|
15
|
+
functionResponse: { name: p.callId, response: { output: p.output } },
|
|
16
|
+
}) : ({ text: "" }));
|
|
17
|
+
if (parts.length)
|
|
18
|
+
contents.push({ role: "user", parts });
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const role = msg.role === "assistant" ? "model" : "user";
|
|
22
|
+
const parts = [];
|
|
23
|
+
if (msg.toolCalls?.length) {
|
|
24
|
+
for (const tc of msg.toolCalls) {
|
|
25
|
+
let args = {};
|
|
26
|
+
try {
|
|
27
|
+
args = JSON.parse(tc.arguments);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
args = {};
|
|
31
|
+
}
|
|
32
|
+
parts.push({ functionCall: { name: tc.name, args } });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (msg.content)
|
|
36
|
+
parts.push({ text: msg.content });
|
|
37
|
+
if (parts.length)
|
|
38
|
+
contents.push({ role, parts });
|
|
39
|
+
}
|
|
40
|
+
return contents;
|
|
41
|
+
}
|
|
42
|
+
function buildTools(tools) {
|
|
43
|
+
if (!tools.length)
|
|
44
|
+
return [];
|
|
45
|
+
return [{
|
|
46
|
+
functionDeclarations: tools.map(t => ({
|
|
47
|
+
name: t.name,
|
|
48
|
+
description: t.description,
|
|
49
|
+
parameters: JSON.parse(t.parameters),
|
|
50
|
+
})),
|
|
51
|
+
}];
|
|
52
|
+
}
|
|
53
|
+
function systemInstruction(messages) {
|
|
54
|
+
return messages.find(m => m.role === "system")?.content;
|
|
55
|
+
}
|
|
56
|
+
export class GeminiProvider {
|
|
57
|
+
model;
|
|
58
|
+
genAI;
|
|
59
|
+
circuit;
|
|
60
|
+
maxRetries;
|
|
61
|
+
baseDelay;
|
|
62
|
+
constructor(apiKey, model = "gemini-2.0-flash", retry = { maxRetries: 3, baseDelay: 1000 }, baseURL = GEMINI_BASE) {
|
|
63
|
+
this.model = model;
|
|
64
|
+
this.genAI = withServerRuntimeGuard(() => new GoogleGenerativeAI(apiKey));
|
|
65
|
+
this.circuit = new CircuitBreaker();
|
|
66
|
+
this.maxRetries = retry.maxRetries;
|
|
67
|
+
this.baseDelay = retry.baseDelay;
|
|
68
|
+
}
|
|
69
|
+
async complete(messages, tools) {
|
|
70
|
+
if (this.circuit.isOpen())
|
|
71
|
+
throw new Error("Circuit breaker open");
|
|
72
|
+
const system = systemInstruction(messages);
|
|
73
|
+
const contents = buildContents(messages);
|
|
74
|
+
const geminiTools = buildTools(tools);
|
|
75
|
+
let lastErr;
|
|
76
|
+
for (let i = 0; i < this.maxRetries; i++) {
|
|
77
|
+
try {
|
|
78
|
+
const m = this.genAI.getGenerativeModel({
|
|
79
|
+
model: this.model,
|
|
80
|
+
...(system ? { systemInstruction: system } : {}),
|
|
81
|
+
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
82
|
+
});
|
|
83
|
+
const resp = await m.generateContent({ contents });
|
|
84
|
+
this.circuit.recordSuccess();
|
|
85
|
+
const candidate = resp.response.candidates?.[0];
|
|
86
|
+
let content = "";
|
|
87
|
+
const toolCalls = [];
|
|
88
|
+
for (const part of candidate?.content.parts ?? []) {
|
|
89
|
+
if (part.text)
|
|
90
|
+
content += part.text;
|
|
91
|
+
else if (part.functionCall) {
|
|
92
|
+
const tc = normalizeToolCall(part.functionCall.name, part.functionCall.name, part.functionCall.args);
|
|
93
|
+
if (tc)
|
|
94
|
+
toolCalls.push(tc);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const usage = resp.response.usageMetadata;
|
|
98
|
+
return {
|
|
99
|
+
role: "assistant",
|
|
100
|
+
content,
|
|
101
|
+
tokenCount: usage?.totalTokenCount,
|
|
102
|
+
toolCalls,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
lastErr = err;
|
|
107
|
+
this.circuit.recordFailure();
|
|
108
|
+
if (i < this.maxRetries - 1)
|
|
109
|
+
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
throw lastErr;
|
|
113
|
+
}
|
|
114
|
+
async *stream(messages, tools, extensions) {
|
|
115
|
+
const system = systemInstruction(messages);
|
|
116
|
+
const contents = buildContents(messages);
|
|
117
|
+
const geminiTools = buildTools(tools);
|
|
118
|
+
const m = this.genAI.getGenerativeModel({
|
|
119
|
+
model: this.model,
|
|
120
|
+
...(system ? { systemInstruction: system } : {}),
|
|
121
|
+
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
122
|
+
});
|
|
123
|
+
const result = await m.generateContentStream({ contents });
|
|
124
|
+
const toolCallBufs = {};
|
|
125
|
+
for await (const chunk of result.stream) {
|
|
126
|
+
for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
|
|
127
|
+
if (part.text)
|
|
128
|
+
yield { type: "text_delta", delta: part.text };
|
|
129
|
+
else if (part.functionCall) {
|
|
130
|
+
const { name, args } = part.functionCall;
|
|
131
|
+
toolCallBufs[name] = { name, args: args };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const [id, tc] of Object.entries(toolCallBufs)) {
|
|
136
|
+
yield { type: "tool_call", id, name: tc.name, arguments: tc.args };
|
|
137
|
+
}
|
|
138
|
+
const usage = (await result.response).usageMetadata;
|
|
139
|
+
if (usage?.totalTokenCount)
|
|
140
|
+
yield { type: "usage", totalTokens: usage.totalTokenCount };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -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: "bearer",
|
|
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
|
+
}
|