@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 CHANGED
@@ -33,9 +33,9 @@ The correct platform package is selected and installed automatically via `option
33
33
  ## Quick start
34
34
 
35
35
  ```typescript
36
- import { Agent, OpenAIProvider, tool } from "@deepstrike/sdk"
36
+ import { Agent, OpenAIResponsesProvider, tool } from "@deepstrike/sdk"
37
37
 
38
- const provider = new OpenAIProvider(process.env.OPENAI_API_KEY!, "gpt-5-mini")
38
+ const provider = new OpenAIResponsesProvider(process.env.OPENAI_API_KEY!, "gpt-5-mini")
39
39
 
40
40
  const add = tool("add", "Add two numbers.", {
41
41
  type: "object",
@@ -67,16 +67,29 @@ for await (const event of agent.runStreaming("Summarize README.md")) {
67
67
 
68
68
  | Class | Backend | Notes |
69
69
  |-------|---------|-------|
70
- | `OpenAIProvider` | OpenAI API | SSE tool-call accumulation |
70
+ | `OpenAIChatProvider` | OpenAI Chat Completions API | SSE tool-call accumulation |
71
+ | `OpenAIProvider` | OpenAI Chat Completions API | Compatibility alias for `OpenAIChatProvider` |
72
+ | `OpenAIResponsesProvider` | OpenAI Responses API | Native `previous_response_id` continuation |
71
73
  | `AnthropicProvider` | Anthropic API | Native SSE, `ThinkingDelta` support |
72
74
  | `QwenProvider` | DashScope | `enable_thinking` via extensions |
73
- | `DeepSeekProvider` | DeepSeek API | Reasoner models strip tools automatically |
74
- | `MiniMaxProvider` | MiniMax API | M1 reasoning via `expose_reasoning` |
75
+ | `DeepSeekProvider` | DeepSeek API | V4 thinking controls + reasoning replay across tool turns |
76
+ | `MiniMaxProvider` | MiniMax API | Anthropic-compatible M2.7/M2.5 path |
75
77
  | `OllamaProvider` | Local Ollama | `http://localhost:11434` default |
76
- | `KimiProvider` | Moonshot API | |
78
+ | `KimiProvider` | Moonshot API | K2.6 default; K2.5 also supported |
77
79
 
78
80
  All providers accept `RetryConfig` for exponential backoff and share a `CircuitBreaker`.
79
81
 
82
+ OpenAI can also be selected through the provider catalog:
83
+
84
+ ```typescript
85
+ import { createProvider } from "@deepstrike/sdk"
86
+
87
+ const provider = createProvider({
88
+ model: "openai/gpt-5-mini",
89
+ apiKey: process.env.OPENAI_API_KEY!,
90
+ })
91
+ ```
92
+
80
93
  ---
81
94
 
82
95
  ## Agent options
package/dist/agent.js CHANGED
@@ -62,6 +62,7 @@ export class Agent {
62
62
  }
63
63
  const kernel = getKernel();
64
64
  const ext = { ...this.extensions, ...(extensions ?? {}) };
65
+ const providerState = this.provider.createRunState?.();
65
66
  const sm = new kernel.LoopStateMachine({
66
67
  maxTokens: this.options.maxTokens,
67
68
  maxTurns: this.options.maxTurns ?? 25,
@@ -156,7 +157,7 @@ export class Agent {
156
157
  const tools = (action.tools ?? []);
157
158
  let turnTokens = 0;
158
159
  try {
159
- for await (const evt of this.provider.stream(messages, tools, Object.keys(ext).length ? ext : undefined)) {
160
+ for await (const evt of this.provider.stream(messages, tools, Object.keys(ext).length ? ext : undefined, providerState)) {
160
161
  if (evt.type === "usage") {
161
162
  turnTokens = evt.totalTokens;
162
163
  continue;
@@ -330,7 +331,8 @@ export class Agent {
330
331
  throw new Error(`unexpected action after feedTrigger: ${action1.kind}`);
331
332
  }
332
333
  let synthesisText = "";
333
- for await (const evt of this.provider.stream((action1.messages ?? []), [], undefined)) {
334
+ const providerState = this.provider.createRunState?.();
335
+ for await (const evt of this.provider.stream((action1.messages ?? []), [], undefined, providerState)) {
334
336
  if (evt.type === "text_delta")
335
337
  synthesisText += evt.delta;
336
338
  }
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  export { Agent } from "./agent.js";
2
2
  export type { AgentOptions } from "./agent.js";
3
3
  export { AnthropicProvider } from "./providers/anthropic.js";
4
- export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider, KimiProvider } from "./providers/openai.js";
4
+ export { OpenAIChatProvider, OpenAIProvider } from "./providers/openai.js";
5
+ export { DeepSeekProvider } from "./providers/deepseek.js";
6
+ export { KimiProvider } from "./providers/kimi.js";
7
+ export { QwenProvider } from "./providers/qwen.js";
8
+ export { GeminiProvider } from "./providers/gemini.js";
9
+ export { MiniMaxProvider } from "./providers/minimax.js";
5
10
  export { OllamaProvider } from "./providers/ollama.js";
6
11
  export { CircuitBreaker, normalizeToolCall } from "./providers/base.js";
12
+ export { OpenAIChatAdapter } from "./providers/openai-chat.js";
13
+ export { OpenAIResponsesAdapter, OpenAIResponsesProvider } from "./providers/openai-responses.js";
14
+ export type { OpenAIResponsesRunState } from "./providers/openai-responses.js";
15
+ export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
16
+ export { createProvider } from "./providers/catalog.js";
17
+ export type { CreateProviderOptions, EndpointProfileId } from "./providers/catalog.js";
7
18
  export { tool, executeTools, readFile } from "./tools/index.js";
8
19
  export type { RegisteredTool } from "./tools/index.js";
9
20
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
@@ -20,4 +31,4 @@ export { PermissionManager, PermissionMode } from "./safety/permissions.js";
20
31
  export type { PermissionDecision, Permission } from "./safety/permissions.js";
21
32
  export { Governance } from "./governance.js";
22
33
  export type { GovernanceVerdict } from "./governance.js";
23
- export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, } from "./types.js";
34
+ export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, } from "./types.js";
package/dist/index.js CHANGED
@@ -1,8 +1,17 @@
1
1
  export { Agent } from "./agent.js";
2
2
  export { AnthropicProvider } from "./providers/anthropic.js";
3
- export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider, KimiProvider } from "./providers/openai.js";
3
+ export { OpenAIChatProvider, OpenAIProvider } from "./providers/openai.js";
4
+ export { DeepSeekProvider } from "./providers/deepseek.js";
5
+ export { KimiProvider } from "./providers/kimi.js";
6
+ export { QwenProvider } from "./providers/qwen.js";
7
+ export { GeminiProvider } from "./providers/gemini.js";
8
+ export { MiniMaxProvider } from "./providers/minimax.js";
4
9
  export { OllamaProvider } from "./providers/ollama.js";
5
10
  export { CircuitBreaker, normalizeToolCall } from "./providers/base.js";
11
+ export { OpenAIChatAdapter } from "./providers/openai-chat.js";
12
+ export { OpenAIResponsesAdapter, OpenAIResponsesProvider } from "./providers/openai-responses.js";
13
+ export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
14
+ export { createProvider } from "./providers/catalog.js";
6
15
  export { tool, executeTools, readFile } from "./tools/index.js";
7
16
  export { scanSkillDir, readSkillFile } from "./skills/loader.js";
8
17
  export { WorkingMemory } from "./memory/working.js";
@@ -1,15 +1,24 @@
1
1
  import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
2
+ interface AnthropicProviderOptions {
3
+ baseURL?: string;
4
+ authMode?: "api-key" | "bearer";
5
+ }
2
6
  export declare class AnthropicProvider implements LLMProvider {
3
- private readonly model;
7
+ protected readonly model: string;
4
8
  private client;
5
9
  private circuit;
6
10
  private maxRetries;
7
11
  private baseDelay;
12
+ private nativeAssistantBlocks;
8
13
  constructor(apiKey: string, model?: string, retry?: {
9
14
  maxRetries: number;
10
15
  baseDelay: number;
11
- });
16
+ }, options?: AnthropicProviderOptions);
12
17
  private buildTools;
13
18
  complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
14
19
  stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
20
+ private buildMessages;
21
+ private rememberNativeBlocks;
22
+ private assistantReplayKey;
15
23
  }
24
+ export {};
@@ -1,14 +1,19 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
- import { CircuitBreaker, normalizeToolCall, toAnthropicContent } from "./base.js";
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
- constructor(apiKey, model = "claude-sonnet-4-6", retry = { maxRetries: 3, baseDelay: 1000 }) {
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({ apiKey });
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.filter(m => m.role === "system").map(m => m.content).join("\n\n");
27
- const msgs = messages.filter(m => m.role !== "system").map(m => ({ role: m.role, content: toAnthropicContent(m) }));
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
- return { role: "assistant", content, tokenCount: resp.usage.input_tokens + resp.usage.output_tokens, toolCalls };
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.filter(m => m.role === "system").map(m => m.content).join("\n\n");
63
- const msgs = messages.filter(m => m.role !== "system").map(m => ({ role: m.role, content: toAnthropicContent(m) }));
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" && evt.content_block.type === "tool_use") {
74
- toolBlocks[evt.index] = { id: evt.content_block.id, name: evt.content_block.name, argsBuf: "" };
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
  }
@@ -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>>;
@@ -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
+ }