@deepstrike/sdk 0.1.11 → 0.1.13
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 +30 -1
- package/dist/agent.d.ts +13 -10
- package/dist/agent.js +135 -14
- package/dist/harness/harness.d.ts +10 -0
- package/dist/harness/harness.js +15 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/kernel.d.ts +5 -2
- package/dist/memory/protocols.d.ts +8 -1
- package/dist/providers/anthropic.d.ts +7 -3
- package/dist/providers/anthropic.js +36 -15
- package/dist/providers/base.d.ts +8 -4
- package/dist/providers/base.js +47 -38
- package/dist/providers/deepseek.d.ts +3 -2
- package/dist/providers/deepseek.js +42 -3
- package/dist/providers/gemini.d.ts +4 -3
- package/dist/providers/gemini.js +20 -17
- package/dist/providers/ollama.d.ts +5 -3
- package/dist/providers/ollama.js +50 -12
- package/dist/providers/openai-chat.d.ts +2 -2
- package/dist/providers/openai-chat.js +6 -4
- package/dist/providers/openai-responses.d.ts +6 -5
- package/dist/providers/openai-responses.js +21 -20
- package/dist/providers/openai.d.ts +4 -3
- package/dist/providers/openai.js +30 -6
- package/dist/providers/qwen.d.ts +5 -3
- package/dist/providers/qwen.js +47 -14
- package/dist/tools/index.d.ts +7 -2
- package/dist/tools/index.js +83 -0
- package/dist/types.d.ts +47 -2
- package/package.json +2 -2
package/dist/providers/base.js
CHANGED
|
@@ -26,6 +26,12 @@ export class CircuitBreaker {
|
|
|
26
26
|
this.openedAt = Date.now();
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
export function omitExtensionKeys(extensions, keys) {
|
|
30
|
+
if (!extensions)
|
|
31
|
+
return {};
|
|
32
|
+
const blocked = new Set(keys);
|
|
33
|
+
return Object.fromEntries(Object.entries(extensions).filter(([key]) => !blocked.has(key)));
|
|
34
|
+
}
|
|
29
35
|
export function normalizeToolCall(id, name, args) {
|
|
30
36
|
const n = String(name ?? "").trim();
|
|
31
37
|
if (!n)
|
|
@@ -44,6 +50,15 @@ export function normalizeToolCall(id, name, args) {
|
|
|
44
50
|
}
|
|
45
51
|
return { id: String(id ?? ""), name: n, arguments: JSON.stringify(parsed) };
|
|
46
52
|
}
|
|
53
|
+
function parseToolArguments(args) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(args || "{}");
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ─── Anthropic message conversion ────────────────────────────────────────────
|
|
47
62
|
export function toAnthropicContent(msg) {
|
|
48
63
|
if (!msg.contentParts?.length)
|
|
49
64
|
return msg.content;
|
|
@@ -65,39 +80,11 @@ export function toAnthropicContent(msg) {
|
|
|
65
80
|
return { type: "text", text: "" };
|
|
66
81
|
});
|
|
67
82
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return msg.contentParts.map(p => {
|
|
72
|
-
if (p.type === "text")
|
|
73
|
-
return { type: "text", text: p.text };
|
|
74
|
-
if (p.type === "image") {
|
|
75
|
-
const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : p.url;
|
|
76
|
-
return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
|
|
77
|
-
}
|
|
78
|
-
if (p.type === "audio") {
|
|
79
|
-
return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
|
|
80
|
-
}
|
|
81
|
-
if (p.type === "tool_result") {
|
|
82
|
-
return { type: "text", text: p.output };
|
|
83
|
-
}
|
|
84
|
-
return { type: "text", text: "" };
|
|
85
|
-
});
|
|
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) {
|
|
83
|
+
/** Convert RenderedContext.turns to Anthropic messages array.
|
|
84
|
+
* `turns` contains only user / assistant / tool roles — no system filtering needed. */
|
|
85
|
+
export function toAnthropicMessages(turns, nativeReplay) {
|
|
99
86
|
const result = [];
|
|
100
|
-
for (const msg of
|
|
87
|
+
for (const msg of turns) {
|
|
101
88
|
if (msg.role === "tool") {
|
|
102
89
|
const parts = (msg.contentParts ?? [])
|
|
103
90
|
.filter((p) => p.type === "tool_result")
|
|
@@ -124,16 +111,38 @@ export function toAnthropicMessages(messages, nativeReplay) {
|
|
|
124
111
|
result.push({ role: "assistant", content: blocks });
|
|
125
112
|
continue;
|
|
126
113
|
}
|
|
127
|
-
result.push({
|
|
128
|
-
role: msg.role,
|
|
129
|
-
content: toAnthropicContent(msg),
|
|
130
|
-
});
|
|
114
|
+
result.push({ role: msg.role, content: toAnthropicContent(msg) });
|
|
131
115
|
}
|
|
132
116
|
return result;
|
|
133
117
|
}
|
|
134
|
-
|
|
118
|
+
// ─── OpenAI-compatible message conversion ────────────────────────────────────
|
|
119
|
+
export function toOpenAIContent(msg) {
|
|
120
|
+
if (!msg.contentParts?.length)
|
|
121
|
+
return msg.content;
|
|
122
|
+
return msg.contentParts.map(p => {
|
|
123
|
+
if (p.type === "text")
|
|
124
|
+
return { type: "text", text: p.text };
|
|
125
|
+
if (p.type === "image") {
|
|
126
|
+
const url = p.data ? `data:${p.mediaType ?? "image/png"};base64,${p.data}` : p.url;
|
|
127
|
+
return { type: "image_url", image_url: { url, ...(p.detail ? { detail: p.detail } : {}) } };
|
|
128
|
+
}
|
|
129
|
+
if (p.type === "audio") {
|
|
130
|
+
return { type: "input_audio", input_audio: { data: p.data, format: p.mediaType?.split("/")[1] ?? "wav" } };
|
|
131
|
+
}
|
|
132
|
+
if (p.type === "tool_result") {
|
|
133
|
+
return { type: "text", text: p.output };
|
|
134
|
+
}
|
|
135
|
+
return { type: "text", text: "" };
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/** Build the full OpenAI messages array from a RenderedContext.
|
|
139
|
+
* Prepends systemText as the first system message, then converts turns. */
|
|
140
|
+
export function toOpenAIMessageParams(context) {
|
|
135
141
|
const result = [];
|
|
136
|
-
|
|
142
|
+
if (context.systemText) {
|
|
143
|
+
result.push({ role: "system", content: context.systemText });
|
|
144
|
+
}
|
|
145
|
+
for (const msg of context.turns) {
|
|
137
146
|
if (msg.role === "tool") {
|
|
138
147
|
const parts = (msg.contentParts ?? [])
|
|
139
148
|
.filter((p) => p.type === "tool_result");
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { Message, ToolSchema, StreamEvent } from "../types.js";
|
|
1
|
+
import type { Message, RenderedContext, ToolSchema, StreamEvent } from "../types.js";
|
|
2
2
|
import { OpenAIChatProvider } from "./openai.js";
|
|
3
3
|
export declare class DeepSeekProvider extends OpenAIChatProvider {
|
|
4
4
|
constructor(apiKey: string, model?: "deepseek-v4-flash" | "deepseek-v4-pro", retry?: {
|
|
5
5
|
maxRetries: number;
|
|
6
6
|
baseDelay: number;
|
|
7
7
|
}, baseURL?: string);
|
|
8
|
-
|
|
8
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
9
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
9
10
|
}
|
|
@@ -1,19 +1,34 @@
|
|
|
1
1
|
import { OpenAIChatProvider } from "./openai.js";
|
|
2
2
|
import { endpointProfiles } from "./profiles.js";
|
|
3
|
+
import { omitExtensionKeys } from "./base.js";
|
|
3
4
|
const DEEPSEEK_BASE = endpointProfiles["deepseek.openai"].baseURL;
|
|
4
5
|
export class DeepSeekProvider extends OpenAIChatProvider {
|
|
5
6
|
constructor(apiKey, model = "deepseek-v4-flash", retry, baseURL = DEEPSEEK_BASE) {
|
|
6
7
|
super(apiKey, model, retry, baseURL);
|
|
7
8
|
}
|
|
8
|
-
async
|
|
9
|
+
async complete(context, tools, extensions) {
|
|
10
|
+
const thinking = extensions?.thinking === false ? "disabled" : "enabled";
|
|
11
|
+
const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
|
|
12
|
+
return super.complete(context, tools, {
|
|
13
|
+
...omitExtensionKeys(extensions, ["thinking", "reasoningEffort", "exposeReasoning", "extra_body", "reasoning_effort"]),
|
|
14
|
+
reasoning_effort: reasoningEffort,
|
|
15
|
+
extra_body: { thinking: { type: thinking } },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async *stream(context, tools, extensions) {
|
|
9
19
|
const exposeReasoning = extensions?.exposeReasoning ?? false;
|
|
10
20
|
const thinking = extensions?.thinking === false ? "disabled" : "enabled";
|
|
11
21
|
const reasoningEffort = extensions?.reasoningEffort === "max" ? "max" : "high";
|
|
12
|
-
const msgs = this.chat.buildMessages(
|
|
22
|
+
const msgs = this.chat.buildMessages(context);
|
|
13
23
|
const toolCallBufs = {};
|
|
24
|
+
const emittedToolCallIndexes = new Set();
|
|
14
25
|
let reasoningContent = "";
|
|
15
26
|
let finalText = "";
|
|
16
27
|
const stream = await this.client.chat.completions.create({
|
|
28
|
+
...omitExtensionKeys(extensions, [
|
|
29
|
+
"model", "messages", "tools", "stream", "extra_body", "reasoning_effort",
|
|
30
|
+
"exposeReasoning", "thinking", "reasoningEffort",
|
|
31
|
+
]),
|
|
17
32
|
model: this.model,
|
|
18
33
|
messages: msgs,
|
|
19
34
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
@@ -48,7 +63,10 @@ export class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
48
63
|
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
49
64
|
}));
|
|
50
65
|
this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
|
|
51
|
-
for (const tb of Object.
|
|
66
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
67
|
+
const idx = Number(index);
|
|
68
|
+
if (emittedToolCallIndexes.has(idx))
|
|
69
|
+
continue;
|
|
52
70
|
let args = {};
|
|
53
71
|
try {
|
|
54
72
|
args = JSON.parse(tb.argsBuf || "{}");
|
|
@@ -56,9 +74,30 @@ export class DeepSeekProvider extends OpenAIChatProvider {
|
|
|
56
74
|
catch {
|
|
57
75
|
args = {};
|
|
58
76
|
}
|
|
77
|
+
emittedToolCallIndexes.add(idx);
|
|
59
78
|
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
60
79
|
}
|
|
61
80
|
}
|
|
62
81
|
}
|
|
82
|
+
const toolCalls = Object.values(toolCallBufs).map(tb => ({
|
|
83
|
+
id: tb.id, name: tb.name, arguments: tb.argsBuf || "{}",
|
|
84
|
+
}));
|
|
85
|
+
if (toolCalls.length) {
|
|
86
|
+
this.chat.rememberReplayFields({ content: finalText, toolCalls }, { reasoning_content: reasoningContent });
|
|
87
|
+
}
|
|
88
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
89
|
+
const idx = Number(index);
|
|
90
|
+
if (emittedToolCallIndexes.has(idx))
|
|
91
|
+
continue;
|
|
92
|
+
let args = {};
|
|
93
|
+
try {
|
|
94
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
args = {};
|
|
98
|
+
}
|
|
99
|
+
emittedToolCallIndexes.add(idx);
|
|
100
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
101
|
+
}
|
|
63
102
|
}
|
|
64
103
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
1
|
+
import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
2
|
export declare class GeminiProvider implements LLMProvider {
|
|
3
3
|
private readonly model;
|
|
4
4
|
private genAI;
|
|
@@ -9,6 +9,7 @@ export declare class GeminiProvider implements LLMProvider {
|
|
|
9
9
|
maxRetries: number;
|
|
10
10
|
baseDelay: number;
|
|
11
11
|
}, baseURL?: string);
|
|
12
|
-
complete(
|
|
13
|
-
stream(
|
|
12
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
13
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
14
|
+
private modelExtensions;
|
|
14
15
|
}
|
package/dist/providers/gemini.js
CHANGED
|
@@ -3,11 +3,9 @@ import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
|
3
3
|
import { CircuitBreaker, normalizeToolCall } from "./base.js";
|
|
4
4
|
import { endpointProfiles } from "./profiles.js";
|
|
5
5
|
const GEMINI_BASE = endpointProfiles["gemini.google"].baseURL;
|
|
6
|
-
function buildContents(
|
|
6
|
+
function buildContents(turns) {
|
|
7
7
|
const contents = [];
|
|
8
|
-
for (const msg of
|
|
9
|
-
if (msg.role === "system")
|
|
10
|
-
continue;
|
|
8
|
+
for (const msg of turns) {
|
|
11
9
|
if (msg.role === "tool") {
|
|
12
10
|
const parts = (msg.contentParts ?? [])
|
|
13
11
|
.filter(p => p.type === "tool_result")
|
|
@@ -50,9 +48,6 @@ function buildTools(tools) {
|
|
|
50
48
|
})),
|
|
51
49
|
}];
|
|
52
50
|
}
|
|
53
|
-
function systemInstruction(messages) {
|
|
54
|
-
return messages.find(m => m.role === "system")?.content;
|
|
55
|
-
}
|
|
56
51
|
export class GeminiProvider {
|
|
57
52
|
model;
|
|
58
53
|
genAI;
|
|
@@ -66,16 +61,17 @@ export class GeminiProvider {
|
|
|
66
61
|
this.maxRetries = retry.maxRetries;
|
|
67
62
|
this.baseDelay = retry.baseDelay;
|
|
68
63
|
}
|
|
69
|
-
async complete(
|
|
64
|
+
async complete(context, tools, extensions) {
|
|
70
65
|
if (this.circuit.isOpen())
|
|
71
66
|
throw new Error("Circuit breaker open");
|
|
72
|
-
const system =
|
|
73
|
-
const contents = buildContents(
|
|
67
|
+
const system = context.systemText || undefined;
|
|
68
|
+
const contents = buildContents(context.turns);
|
|
74
69
|
const geminiTools = buildTools(tools);
|
|
75
70
|
let lastErr;
|
|
76
71
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
77
72
|
try {
|
|
78
73
|
const m = this.genAI.getGenerativeModel({
|
|
74
|
+
...this.modelExtensions(extensions),
|
|
79
75
|
model: this.model,
|
|
80
76
|
...(system ? { systemInstruction: system } : {}),
|
|
81
77
|
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
@@ -111,32 +107,39 @@ export class GeminiProvider {
|
|
|
111
107
|
}
|
|
112
108
|
throw lastErr;
|
|
113
109
|
}
|
|
114
|
-
async *stream(
|
|
115
|
-
const system =
|
|
116
|
-
const contents = buildContents(
|
|
110
|
+
async *stream(context, tools, extensions) {
|
|
111
|
+
const system = context.systemText || undefined;
|
|
112
|
+
const contents = buildContents(context.turns);
|
|
117
113
|
const geminiTools = buildTools(tools);
|
|
118
114
|
const m = this.genAI.getGenerativeModel({
|
|
115
|
+
...this.modelExtensions(extensions),
|
|
119
116
|
model: this.model,
|
|
120
117
|
...(system ? { systemInstruction: system } : {}),
|
|
121
118
|
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
122
119
|
});
|
|
123
120
|
const result = await m.generateContentStream({ contents });
|
|
124
|
-
const
|
|
121
|
+
const toolCalls = [];
|
|
125
122
|
for await (const chunk of result.stream) {
|
|
126
123
|
for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
|
|
127
124
|
if (part.text)
|
|
128
125
|
yield { type: "text_delta", delta: part.text };
|
|
129
126
|
else if (part.functionCall) {
|
|
130
127
|
const { name, args } = part.functionCall;
|
|
131
|
-
|
|
128
|
+
toolCalls.push({ id: `call_${toolCalls.length + 1}`, name, args: args });
|
|
132
129
|
}
|
|
133
130
|
}
|
|
134
131
|
}
|
|
135
|
-
for (const
|
|
136
|
-
yield { type: "tool_call", id, name: tc.name, arguments: tc.args };
|
|
132
|
+
for (const tc of toolCalls) {
|
|
133
|
+
yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.args };
|
|
137
134
|
}
|
|
138
135
|
const usage = (await result.response).usageMetadata;
|
|
139
136
|
if (usage?.totalTokenCount)
|
|
140
137
|
yield { type: "usage", totalTokens: usage.totalTokenCount };
|
|
141
138
|
}
|
|
139
|
+
modelExtensions(extensions) {
|
|
140
|
+
if (!extensions)
|
|
141
|
+
return {};
|
|
142
|
+
const { model: _model, systemInstruction: _systemInstruction, tools: _tools, ...rest } = extensions;
|
|
143
|
+
return rest;
|
|
144
|
+
}
|
|
142
145
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
1
|
+
import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
2
|
export declare class OllamaProvider implements LLMProvider {
|
|
3
3
|
private readonly model;
|
|
4
4
|
private readonly baseUrl;
|
|
5
5
|
constructor(model?: string, baseUrl?: string);
|
|
6
6
|
private toOllamaMessages;
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
private buildTools;
|
|
8
|
+
private requestExtensions;
|
|
9
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
10
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
9
11
|
}
|
package/dist/providers/ollama.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeToolCall } from "./base.js";
|
|
1
|
+
import { normalizeToolCall, omitExtensionKeys } from "./base.js";
|
|
2
2
|
export class OllamaProvider {
|
|
3
3
|
model;
|
|
4
4
|
baseUrl;
|
|
@@ -6,8 +6,11 @@ export class OllamaProvider {
|
|
|
6
6
|
this.model = model;
|
|
7
7
|
this.baseUrl = baseUrl;
|
|
8
8
|
}
|
|
9
|
-
toOllamaMessages(
|
|
10
|
-
|
|
9
|
+
toOllamaMessages(context) {
|
|
10
|
+
const result = [];
|
|
11
|
+
if (context.systemText)
|
|
12
|
+
result.push({ role: "system", content: context.systemText });
|
|
13
|
+
for (const m of context.turns) {
|
|
11
14
|
const images = [];
|
|
12
15
|
if (m.contentParts?.length) {
|
|
13
16
|
for (const p of m.contentParts) {
|
|
@@ -15,31 +18,54 @@ export class OllamaProvider {
|
|
|
15
18
|
images.push(p.data);
|
|
16
19
|
}
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
}
|
|
21
|
+
result.push({ role: m.role, content: m.content, ...(images.length ? { images } : {}) });
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
buildTools(tools) {
|
|
26
|
+
return tools.map(t => ({
|
|
27
|
+
type: "function",
|
|
28
|
+
function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
|
|
29
|
+
}));
|
|
20
30
|
}
|
|
21
|
-
|
|
31
|
+
requestExtensions(extensions) {
|
|
32
|
+
return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream"]);
|
|
33
|
+
}
|
|
34
|
+
async complete(context, tools, extensions) {
|
|
22
35
|
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
23
36
|
method: "POST",
|
|
24
37
|
headers: { "Content-Type": "application/json" },
|
|
25
|
-
body: JSON.stringify({
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
...this.requestExtensions(extensions),
|
|
40
|
+
model: this.model,
|
|
41
|
+
messages: this.toOllamaMessages(context),
|
|
42
|
+
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
43
|
+
stream: false,
|
|
44
|
+
}),
|
|
26
45
|
});
|
|
27
46
|
if (!resp.ok)
|
|
28
47
|
throw new Error(`Ollama error: ${resp.status}`);
|
|
29
48
|
const data = await resp.json();
|
|
30
49
|
return { role: "assistant", content: data.message.content };
|
|
31
50
|
}
|
|
32
|
-
async *stream(
|
|
51
|
+
async *stream(context, tools, extensions) {
|
|
33
52
|
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
34
53
|
method: "POST",
|
|
35
54
|
headers: { "Content-Type": "application/json" },
|
|
36
|
-
body: JSON.stringify({
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
...this.requestExtensions(extensions),
|
|
57
|
+
model: this.model,
|
|
58
|
+
messages: this.toOllamaMessages(context),
|
|
59
|
+
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
60
|
+
stream: true,
|
|
61
|
+
}),
|
|
37
62
|
});
|
|
38
63
|
if (!resp.ok)
|
|
39
64
|
throw new Error(`Ollama error: ${resp.status}`);
|
|
40
65
|
const reader = resp.body.getReader();
|
|
41
66
|
const decoder = new TextDecoder();
|
|
42
67
|
let buf = "";
|
|
68
|
+
const pendingToolCalls = new Map();
|
|
43
69
|
while (true) {
|
|
44
70
|
const { done, value } = await reader.read();
|
|
45
71
|
if (done)
|
|
@@ -55,13 +81,25 @@ export class OllamaProvider {
|
|
|
55
81
|
if (chunk.message?.content)
|
|
56
82
|
yield { type: "text_delta", delta: chunk.message.content };
|
|
57
83
|
for (const tc of chunk.message?.tool_calls ?? []) {
|
|
58
|
-
const norm = normalizeToolCall(
|
|
59
|
-
if (norm)
|
|
60
|
-
|
|
84
|
+
const norm = normalizeToolCall("", tc.function.name, tc.function.arguments);
|
|
85
|
+
if (!norm)
|
|
86
|
+
continue;
|
|
87
|
+
const args = JSON.parse(norm.arguments);
|
|
88
|
+
const key = `${norm.name}:${norm.arguments}`;
|
|
89
|
+
if (!pendingToolCalls.has(key)) {
|
|
90
|
+
pendingToolCalls.set(key, {
|
|
91
|
+
id: `call_${pendingToolCalls.size + 1}`,
|
|
92
|
+
name: norm.name,
|
|
93
|
+
arguments: args,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
61
96
|
}
|
|
62
97
|
}
|
|
63
98
|
catch { /* skip malformed lines */ }
|
|
64
99
|
}
|
|
65
100
|
}
|
|
101
|
+
for (const tc of pendingToolCalls.values()) {
|
|
102
|
+
yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments };
|
|
103
|
+
}
|
|
66
104
|
}
|
|
67
105
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type OpenAI from "openai";
|
|
2
|
-
import type { Message, ToolSchema } from "../types.js";
|
|
2
|
+
import type { Message, RenderedContext, ToolSchema } from "../types.js";
|
|
3
3
|
export declare class OpenAIChatAdapter {
|
|
4
4
|
private replayFields;
|
|
5
5
|
buildTools(tools: ToolSchema[]): {
|
|
@@ -10,7 +10,7 @@ export declare class OpenAIChatAdapter {
|
|
|
10
10
|
parameters: any;
|
|
11
11
|
};
|
|
12
12
|
}[];
|
|
13
|
-
buildMessages(
|
|
13
|
+
buildMessages(context: RenderedContext): OpenAI.ChatCompletionMessageParam[];
|
|
14
14
|
normalizeToolCalls(toolCalls?: Array<{
|
|
15
15
|
id: string;
|
|
16
16
|
function: {
|
|
@@ -7,10 +7,12 @@ export class OpenAIChatAdapter {
|
|
|
7
7
|
function: { name: t.name, description: t.description, parameters: JSON.parse(t.parameters) },
|
|
8
8
|
}));
|
|
9
9
|
}
|
|
10
|
-
buildMessages(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
buildMessages(context) {
|
|
11
|
+
// toOpenAIMessageParams prepends systemText as messages[0], then turns.
|
|
12
|
+
const serialized = toOpenAIMessageParams(context);
|
|
13
|
+
// Cursor starts at 1 to skip the system message injected by toOpenAIMessageParams.
|
|
14
|
+
let cursor = context.systemText ? 1 : 0;
|
|
15
|
+
for (const source of context.turns) {
|
|
14
16
|
if (source.role === "tool") {
|
|
15
17
|
cursor += (source.contentParts ?? []).filter(p => p.type === "tool_result").length;
|
|
16
18
|
continue;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { Message, ProviderRunState, StreamEvent, ToolSchema, LLMProvider } from "../types.js";
|
|
2
|
+
import type { Message, ProviderRunState, RenderedContext, StreamEvent, ToolSchema, LLMProvider } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
export interface OpenAIResponsesRunState extends ProviderRunState {
|
|
5
5
|
previousResponseId?: string;
|
|
@@ -12,8 +12,8 @@ export declare class OpenAIResponsesAdapter {
|
|
|
12
12
|
description: string;
|
|
13
13
|
parameters: any;
|
|
14
14
|
}[];
|
|
15
|
-
buildInstructions(
|
|
16
|
-
buildInput(
|
|
15
|
+
buildInstructions(context: RenderedContext): string | undefined;
|
|
16
|
+
buildInput(context: RenderedContext, state?: OpenAIResponsesRunState): Array<Record<string, unknown>>;
|
|
17
17
|
decodeOutput(output: Array<Record<string, unknown>>): {
|
|
18
18
|
content: string;
|
|
19
19
|
toolCalls: Array<{
|
|
@@ -36,7 +36,8 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
|
|
|
36
36
|
baseDelay: number;
|
|
37
37
|
}, baseURL?: string);
|
|
38
38
|
createRunState(): OpenAIResponsesRunState;
|
|
39
|
-
complete(
|
|
40
|
-
stream(
|
|
39
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
40
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
|
|
41
|
+
private requestExtensions;
|
|
41
42
|
private asRunState;
|
|
42
43
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
-
import { CircuitBreaker } from "./base.js";
|
|
3
|
+
import { CircuitBreaker, omitExtensionKeys } from "./base.js";
|
|
4
4
|
import { normalizeToolCall } from "./base.js";
|
|
5
5
|
export class OpenAIResponsesAdapter {
|
|
6
6
|
buildTools(tools) {
|
|
@@ -11,22 +11,16 @@ export class OpenAIResponsesAdapter {
|
|
|
11
11
|
parameters: JSON.parse(t.parameters),
|
|
12
12
|
}));
|
|
13
13
|
}
|
|
14
|
-
buildInstructions(
|
|
15
|
-
|
|
16
|
-
.filter(message => message.role === "system")
|
|
17
|
-
.map(message => message.content)
|
|
18
|
-
.filter(Boolean);
|
|
19
|
-
return instructions.length ? instructions.join("\n\n") : undefined;
|
|
14
|
+
buildInstructions(context) {
|
|
15
|
+
return context.systemText || undefined;
|
|
20
16
|
}
|
|
21
|
-
buildInput(
|
|
17
|
+
buildInput(context, state) {
|
|
22
18
|
const input = [];
|
|
19
|
+
const turns = context.turns;
|
|
23
20
|
const uncoveredMessages = state?.previousResponseId
|
|
24
|
-
?
|
|
25
|
-
:
|
|
21
|
+
? turns.slice(state.coveredMessageCount)
|
|
22
|
+
: turns;
|
|
26
23
|
for (const message of uncoveredMessages) {
|
|
27
|
-
if (message.role === "system") {
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
24
|
if (message.role === "assistant" && message.toolCalls?.length) {
|
|
31
25
|
if (message.content || message.contentParts?.length) {
|
|
32
26
|
input.push({
|
|
@@ -122,16 +116,17 @@ export class OpenAIResponsesProvider {
|
|
|
122
116
|
createRunState() {
|
|
123
117
|
return { coveredMessageCount: 0 };
|
|
124
118
|
}
|
|
125
|
-
async complete(
|
|
119
|
+
async complete(context, tools, extensions) {
|
|
126
120
|
if (this.circuit.isOpen())
|
|
127
121
|
throw new Error("Circuit breaker open");
|
|
128
122
|
let lastErr;
|
|
129
123
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
130
124
|
try {
|
|
131
|
-
const instructions = this.responses.buildInstructions(
|
|
125
|
+
const instructions = this.responses.buildInstructions(context);
|
|
132
126
|
const resp = await this.client.responses.create({
|
|
127
|
+
...this.requestExtensions(extensions),
|
|
133
128
|
model: this.model,
|
|
134
|
-
input: this.responses.buildInput(
|
|
129
|
+
input: this.responses.buildInput(context),
|
|
135
130
|
...(instructions ? { instructions } : {}),
|
|
136
131
|
...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
|
|
137
132
|
});
|
|
@@ -153,13 +148,14 @@ export class OpenAIResponsesProvider {
|
|
|
153
148
|
}
|
|
154
149
|
throw lastErr;
|
|
155
150
|
}
|
|
156
|
-
async *stream(
|
|
151
|
+
async *stream(context, tools, extensions, state) {
|
|
157
152
|
const runState = this.asRunState(state);
|
|
158
153
|
const functionCalls = new Map();
|
|
159
|
-
const instructions = this.responses.buildInstructions(
|
|
154
|
+
const instructions = this.responses.buildInstructions(context);
|
|
160
155
|
const stream = await this.client.responses.create({
|
|
156
|
+
...this.requestExtensions(extensions),
|
|
161
157
|
model: this.model,
|
|
162
|
-
input: this.responses.buildInput(
|
|
158
|
+
input: this.responses.buildInput(context, runState),
|
|
163
159
|
...(instructions ? { instructions } : {}),
|
|
164
160
|
...(runState.previousResponseId ? { previous_response_id: runState.previousResponseId } : {}),
|
|
165
161
|
...(tools.length ? { tools: this.responses.buildTools(tools) } : {}),
|
|
@@ -203,13 +199,18 @@ export class OpenAIResponsesProvider {
|
|
|
203
199
|
}
|
|
204
200
|
else if (evt.type === "response.completed") {
|
|
205
201
|
runState.previousResponseId = evt.response.id;
|
|
206
|
-
runState.coveredMessageCount =
|
|
202
|
+
runState.coveredMessageCount = context.turns.length + 1;
|
|
207
203
|
if (evt.response.usage?.total_tokens) {
|
|
208
204
|
yield { type: "usage", totalTokens: evt.response.usage.total_tokens };
|
|
209
205
|
}
|
|
210
206
|
}
|
|
211
207
|
}
|
|
212
208
|
}
|
|
209
|
+
requestExtensions(extensions) {
|
|
210
|
+
return omitExtensionKeys(extensions, [
|
|
211
|
+
"model", "input", "instructions", "tools", "stream", "previous_response_id",
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
213
214
|
asRunState(state) {
|
|
214
215
|
if (!state)
|
|
215
216
|
return this.createRunState();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
|
-
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
|
+
import type { Message, RenderedContext, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
3
3
|
import { CircuitBreaker } from "./base.js";
|
|
4
4
|
import { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
5
|
export declare class OpenAIChatProvider implements LLMProvider {
|
|
@@ -13,7 +13,8 @@ export declare class OpenAIChatProvider implements LLMProvider {
|
|
|
13
13
|
maxRetries: number;
|
|
14
14
|
baseDelay: number;
|
|
15
15
|
}, baseURL?: string);
|
|
16
|
-
complete(
|
|
17
|
-
stream(
|
|
16
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
17
|
+
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
18
|
+
protected requestExtensions(extensions?: Record<string, unknown>): Record<string, unknown>;
|
|
18
19
|
}
|
|
19
20
|
export { OpenAIChatProvider as OpenAIProvider };
|