@deepstrike/sdk 0.1.12 → 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 +5 -1
- package/dist/agent.d.ts +3 -1
- package/dist/agent.js +71 -2
- package/dist/harness/harness.d.ts +10 -0
- package/dist/harness/harness.js +8 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/providers/anthropic.d.ts +5 -1
- package/dist/providers/anthropic.js +29 -8
- package/dist/providers/base.d.ts +1 -0
- package/dist/providers/base.js +6 -0
- package/dist/providers/deepseek.d.ts +2 -1
- package/dist/providers/deepseek.js +40 -1
- package/dist/providers/gemini.d.ts +2 -1
- package/dist/providers/gemini.js +13 -5
- package/dist/providers/ollama.d.ts +3 -1
- package/dist/providers/ollama.js +41 -7
- package/dist/providers/openai-responses.d.ts +3 -2
- package/dist/providers/openai-responses.js +10 -3
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.js +27 -3
- package/dist/providers/qwen.d.ts +3 -1
- package/dist/providers/qwen.js +44 -11
- package/dist/tools/index.d.ts +7 -2
- package/dist/tools/index.js +83 -0
- package/dist/types.d.ts +36 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ for await (const event of agent.runStreaming("Summarize README.md")) {
|
|
|
88
88
|
|
|
89
89
|
All providers accept `RetryConfig` for exponential backoff and share a `CircuitBreaker`.
|
|
90
90
|
|
|
91
|
+
`extensions` are forwarded by every provider in both `complete()` and `stream()` while SDK-owned structural fields such as `model`, `messages`, `tools`, and streaming flags remain protected. Provider-specific controls still keep their native spellings: for example Anthropic `thinking` / `betas`, OpenAI Responses `reasoning`, Gemini `generationConfig`, Ollama `think` / `options`, DeepSeek `thinking` + `reasoningEffort`, and Qwen `enableThinking` + `thinkingBudget`.
|
|
92
|
+
|
|
91
93
|
OpenAI can also be selected through the provider catalog:
|
|
92
94
|
|
|
93
95
|
```typescript
|
|
@@ -108,7 +110,7 @@ const agent = new Agent(provider, {
|
|
|
108
110
|
maxTokens: 4096, // context window size
|
|
109
111
|
maxTurns: 25, // max turns (default 25)
|
|
110
112
|
timeoutMs: 60_000, // timeout in ms
|
|
111
|
-
extensions: { temperature: 0.1 }, //
|
|
113
|
+
extensions: { temperature: 0.1 }, // provider-native controls, passed through to the LLM
|
|
112
114
|
skillDir: "./skills", // skill .md files directory
|
|
113
115
|
knowledgeSource: myKS, // KnowledgeSource implementation
|
|
114
116
|
signalSource: rx, // SignalSource for external signals
|
|
@@ -307,6 +309,8 @@ console.log(out.passed, out.feedback)
|
|
|
307
309
|
| `text_delta` | `delta` |
|
|
308
310
|
| `thinking_delta` | `delta` |
|
|
309
311
|
| `tool_call` | `id`, `name`, `arguments` |
|
|
312
|
+
| `tool_delta` | `callId`, `delta?`, `chunk?` |
|
|
313
|
+
| `tool_suspend` | `callId`, `suspensionId`, `payload?` |
|
|
310
314
|
| `tool_result` | `callId`, `content`, `isError` |
|
|
311
315
|
| `permission_request` | `toolName`, `reason` |
|
|
312
316
|
| `done` | `iterations`, `totalTokens`, `status` |
|
package/dist/agent.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LLMProvider, StreamEvent } from "./types.js";
|
|
1
|
+
import type { LLMProvider, StreamEvent, ToolSuspendEvent } from "./types.js";
|
|
2
2
|
import type { RegisteredTool } from "./tools/index.js";
|
|
3
3
|
import type { DreamStore, DreamResult, SessionStore } from "./memory/protocols.js";
|
|
4
4
|
import type { KnowledgeSource } from "./knowledge/source.js";
|
|
@@ -34,6 +34,7 @@ export interface AgentOptions {
|
|
|
34
34
|
* `evaluate(toolName: string, argsJson: string): { kind: string; reason?: string; retryAfterMs?: number }`.
|
|
35
35
|
* When provided, every tool call is evaluated through the full pipeline.
|
|
36
36
|
*/
|
|
37
|
+
onToolSuspend?: (event: ToolSuspendEvent) => Promise<unknown> | unknown;
|
|
37
38
|
governance?: {
|
|
38
39
|
setTime?(nowMs: bigint): void;
|
|
39
40
|
evaluate(toolName: string, argsJson: string): {
|
|
@@ -71,6 +72,7 @@ export declare class Agent {
|
|
|
71
72
|
*/
|
|
72
73
|
run(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): Promise<string>;
|
|
73
74
|
runStreaming(goal: string, criteria?: string[], extensions?: Record<string, unknown>, sessionId?: string): AsyncIterable<StreamEvent>;
|
|
75
|
+
private runRegularTool;
|
|
74
76
|
private loadSession;
|
|
75
77
|
private saveSession;
|
|
76
78
|
private toMessage;
|
package/dist/agent.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAsyncIterable, normalizeToolChunk, toolChunkText, validateToolArguments } from "./tools/index.js";
|
|
2
2
|
import { readSkillFile, scanSkillDir } from "./skills/loader.js";
|
|
3
3
|
import { getKernel } from "./kernel.js";
|
|
4
4
|
export class Agent {
|
|
@@ -245,7 +245,22 @@ export class Agent {
|
|
|
245
245
|
: knowledgeCalls.map((c) => ({ callId: c.id, name: c.name, output: "Knowledge source not configured.", isError: true }));
|
|
246
246
|
for (const r of [...skillResults, ...memoryResults, ...knowledgeResults])
|
|
247
247
|
yield { type: "tool_result", callId: r.callId, name: r.name, content: r.output, isError: r.isError };
|
|
248
|
-
const results =
|
|
248
|
+
const results = [];
|
|
249
|
+
const active = regularCalls.map(call => {
|
|
250
|
+
const iterator = this.runRegularTool(call);
|
|
251
|
+
return { iterator, pending: iterator.next() };
|
|
252
|
+
});
|
|
253
|
+
while (active.length) {
|
|
254
|
+
const next = await Promise.race(active.map((task, index) => task.pending.then(result => ({ index, result }))));
|
|
255
|
+
const task = active[next.index];
|
|
256
|
+
if (next.result.done) {
|
|
257
|
+
results.push(next.result.value);
|
|
258
|
+
active.splice(next.index, 1);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
yield next.result.value;
|
|
262
|
+
task.pending = task.iterator.next();
|
|
263
|
+
}
|
|
249
264
|
for (const r of results) {
|
|
250
265
|
const name = regularCalls.find((c) => c.id === r.callId)?.name ?? "";
|
|
251
266
|
yield { type: "tool_result", callId: r.callId, name, content: r.output, isError: r.isError };
|
|
@@ -299,6 +314,60 @@ export class Agent {
|
|
|
299
314
|
status,
|
|
300
315
|
};
|
|
301
316
|
}
|
|
317
|
+
async *runRegularTool(call) {
|
|
318
|
+
const registeredTool = this.tools.get(call.name);
|
|
319
|
+
if (!registeredTool)
|
|
320
|
+
return { callId: call.id, output: `unknown tool: ${call.name}`, isError: true };
|
|
321
|
+
try {
|
|
322
|
+
const args = JSON.parse(call.arguments || "{}");
|
|
323
|
+
const validationError = validateToolArguments(registeredTool.schema.parameters, args);
|
|
324
|
+
if (validationError)
|
|
325
|
+
return { callId: call.id, output: `invalid arguments: ${validationError}`, isError: true };
|
|
326
|
+
const output = await registeredTool.execute(args);
|
|
327
|
+
if (isAsyncIterable(output)) {
|
|
328
|
+
let combined = "";
|
|
329
|
+
const iterator = output[Symbol.asyncIterator]();
|
|
330
|
+
let resumeValue;
|
|
331
|
+
while (true) {
|
|
332
|
+
const next = await iterator.next(resumeValue);
|
|
333
|
+
resumeValue = undefined;
|
|
334
|
+
if (next.done)
|
|
335
|
+
break;
|
|
336
|
+
const rawChunk = next.value;
|
|
337
|
+
const chunk = normalizeToolChunk(rawChunk);
|
|
338
|
+
if (chunk.type === "suspend") {
|
|
339
|
+
const event = {
|
|
340
|
+
type: "tool_suspend",
|
|
341
|
+
callId: call.id,
|
|
342
|
+
name: call.name,
|
|
343
|
+
suspensionId: chunk.suspensionId,
|
|
344
|
+
...(chunk.payload ? { payload: chunk.payload } : {}),
|
|
345
|
+
};
|
|
346
|
+
yield event;
|
|
347
|
+
if (!this.options.onToolSuspend) {
|
|
348
|
+
return { callId: call.id, output: `tool suspended without resume handler: ${chunk.suspensionId}`, isError: true };
|
|
349
|
+
}
|
|
350
|
+
resumeValue = await this.options.onToolSuspend(event);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const delta = toolChunkText(rawChunk);
|
|
354
|
+
combined += delta;
|
|
355
|
+
yield {
|
|
356
|
+
type: "tool_delta",
|
|
357
|
+
callId: call.id,
|
|
358
|
+
name: call.name,
|
|
359
|
+
...(delta ? { delta } : {}),
|
|
360
|
+
...(typeof rawChunk === "string" ? {} : { chunk }),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return { callId: call.id, output: combined, isError: false };
|
|
364
|
+
}
|
|
365
|
+
return { callId: call.id, output, isError: false };
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
return { callId: call.id, output: String(err), isError: true };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
302
371
|
async loadSession(sessionId) {
|
|
303
372
|
return this.options.sessionStore
|
|
304
373
|
? this.options.sessionStore.loadSession(sessionId)
|
|
@@ -38,6 +38,16 @@ export type HarnessEvent = {
|
|
|
38
38
|
type: "tool_call";
|
|
39
39
|
id: string;
|
|
40
40
|
name: string;
|
|
41
|
+
} | {
|
|
42
|
+
type: "tool_delta";
|
|
43
|
+
callId: string;
|
|
44
|
+
delta?: string;
|
|
45
|
+
chunk?: Record<string, unknown>;
|
|
46
|
+
} | {
|
|
47
|
+
type: "tool_suspend";
|
|
48
|
+
callId: string;
|
|
49
|
+
suspensionId: string;
|
|
50
|
+
payload?: Record<string, unknown>;
|
|
41
51
|
} | {
|
|
42
52
|
type: "tool_result";
|
|
43
53
|
callId: string;
|
package/dist/harness/harness.js
CHANGED
|
@@ -74,6 +74,14 @@ export class HarnessLoop {
|
|
|
74
74
|
const tc = evt;
|
|
75
75
|
yield { type: "tool_call", id: tc.id, name: tc.name };
|
|
76
76
|
}
|
|
77
|
+
else if (evt.type === "tool_delta") {
|
|
78
|
+
const td = evt;
|
|
79
|
+
yield { type: "tool_delta", callId: td.callId, ...(td.delta ? { delta: td.delta } : {}), ...(td.chunk ? { chunk: td.chunk } : {}) };
|
|
80
|
+
}
|
|
81
|
+
else if (evt.type === "tool_suspend") {
|
|
82
|
+
const ts = evt;
|
|
83
|
+
yield { type: "tool_suspend", callId: ts.callId, suspensionId: ts.suspensionId, ...(ts.payload ? { payload: ts.payload } : {}) };
|
|
84
|
+
}
|
|
77
85
|
else if (evt.type === "tool_result") {
|
|
78
86
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
87
|
const tr = evt;
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type { OpenAIResponsesRunState } from "./providers/openai-responses.js";
|
|
|
15
15
|
export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
|
|
16
16
|
export { createProvider } from "./providers/catalog.js";
|
|
17
17
|
export type { CreateProviderOptions, EndpointProfileId } from "./providers/catalog.js";
|
|
18
|
-
export { tool, executeTools, readFile } from "./tools/index.js";
|
|
18
|
+
export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
|
|
19
19
|
export type { RegisteredTool } from "./tools/index.js";
|
|
20
20
|
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
|
21
21
|
export type { SkillMetadata } from "./skills/loader.js";
|
|
@@ -31,7 +31,7 @@ export { PermissionManager, PermissionMode } from "./safety/permissions.js";
|
|
|
31
31
|
export type { PermissionDecision, Permission } from "./safety/permissions.js";
|
|
32
32
|
export { Governance } from "./governance.js";
|
|
33
33
|
export type { GovernanceVerdict } from "./governance.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, RenderedContext, } from "./types.js";
|
|
34
|
+
export type { Message, ToolCall, ToolResult, ToolSchema, ContentPart, TextPart, ImagePart, AudioPart, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolChunk, ToolDeltaEvent, ToolSuspendEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, TokenUsage, ProviderToolSpec, ProviderRunState, RenderedContext, } from "./types.js";
|
|
35
35
|
export type { AcceptanceCriterion, VerificationContract, ContractCheckResult, } from "./collaboration/contract.js";
|
|
36
36
|
export { ContractBuilder, formatContractForSystemPrompt, contractToCriteriaStrings, } from "./collaboration/contract.js";
|
|
37
37
|
export { AgentPool } from "./collaboration/pool.js";
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export { OpenAIChatAdapter } from "./providers/openai-chat.js";
|
|
|
12
12
|
export { OpenAIResponsesAdapter, OpenAIResponsesProvider } from "./providers/openai-responses.js";
|
|
13
13
|
export { endpointProfiles, modelProfiles, getModelProfile } from "./providers/profiles.js";
|
|
14
14
|
export { createProvider } from "./providers/catalog.js";
|
|
15
|
-
export { tool, executeTools, readFile } from "./tools/index.js";
|
|
15
|
+
export { tool, streamingTool, executeTools, readFile, validateToolArguments } from "./tools/index.js";
|
|
16
16
|
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
|
17
17
|
export { WorkingMemory } from "./memory/working.js";
|
|
18
18
|
export { SinglePassHarness, EvalLoopHarness, HarnessLoop } from "./harness/harness.js";
|
|
@@ -15,8 +15,12 @@ export declare class AnthropicProvider implements LLMProvider {
|
|
|
15
15
|
baseDelay: number;
|
|
16
16
|
}, options?: AnthropicProviderOptions);
|
|
17
17
|
private buildTools;
|
|
18
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
18
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
19
19
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
20
|
+
private requestExtensions;
|
|
21
|
+
private hasBetas;
|
|
22
|
+
private createMessage;
|
|
23
|
+
private streamMessage;
|
|
20
24
|
private buildMessages;
|
|
21
25
|
private rememberNativeBlocks;
|
|
22
26
|
private assistantReplayKey;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Anthropic from "@anthropic-ai/sdk";
|
|
2
2
|
import { withServerRuntimeGuard } from "../runtime/server.js";
|
|
3
|
-
import { CircuitBreaker, normalizeToolCall, toAnthropicMessages } from "./base.js";
|
|
3
|
+
import { CircuitBreaker, normalizeToolCall, omitExtensionKeys, toAnthropicMessages } from "./base.js";
|
|
4
4
|
export class AnthropicProvider {
|
|
5
5
|
model;
|
|
6
6
|
client;
|
|
@@ -25,21 +25,23 @@ export class AnthropicProvider {
|
|
|
25
25
|
input_schema: JSON.parse(t.parameters),
|
|
26
26
|
}));
|
|
27
27
|
}
|
|
28
|
-
async complete(context, tools) {
|
|
28
|
+
async complete(context, tools, extensions) {
|
|
29
29
|
if (this.circuit.isOpen())
|
|
30
30
|
throw new Error("Circuit breaker open");
|
|
31
31
|
const system = context.systemText || undefined;
|
|
32
32
|
const msgs = this.buildMessages(context);
|
|
33
|
+
const requestExtensions = this.requestExtensions(extensions);
|
|
33
34
|
let lastErr;
|
|
34
35
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
35
36
|
try {
|
|
36
|
-
const resp = await this.
|
|
37
|
+
const resp = await this.createMessage({
|
|
38
|
+
...requestExtensions,
|
|
37
39
|
model: this.model,
|
|
38
|
-
max_tokens: 8096,
|
|
40
|
+
max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
|
|
39
41
|
...(system ? { system } : {}),
|
|
40
42
|
messages: msgs,
|
|
41
43
|
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
42
|
-
});
|
|
44
|
+
}, extensions);
|
|
43
45
|
this.circuit.recordSuccess();
|
|
44
46
|
let content = "";
|
|
45
47
|
const toolCalls = [];
|
|
@@ -68,17 +70,19 @@ export class AnthropicProvider {
|
|
|
68
70
|
async *stream(context, tools, extensions) {
|
|
69
71
|
const system = context.systemText || undefined;
|
|
70
72
|
const msgs = this.buildMessages(context);
|
|
73
|
+
const requestExtensions = this.requestExtensions(extensions);
|
|
71
74
|
const toolBlocks = {};
|
|
72
75
|
const nativeBlocks = {};
|
|
73
76
|
let finalText = "";
|
|
74
77
|
const finalToolCalls = [];
|
|
75
|
-
const stream = this.
|
|
78
|
+
const stream = this.streamMessage({
|
|
79
|
+
...requestExtensions,
|
|
76
80
|
model: this.model,
|
|
77
|
-
max_tokens: 8096,
|
|
81
|
+
max_tokens: typeof extensions?.max_tokens === "number" ? extensions.max_tokens : 8096,
|
|
78
82
|
...(system ? { system } : {}),
|
|
79
83
|
messages: msgs,
|
|
80
84
|
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
81
|
-
});
|
|
85
|
+
}, extensions);
|
|
82
86
|
for await (const evt of stream) {
|
|
83
87
|
if (evt.type === "content_block_start") {
|
|
84
88
|
nativeBlocks[evt.index] = { ...evt.content_block };
|
|
@@ -121,6 +125,23 @@ export class AnthropicProvider {
|
|
|
121
125
|
}
|
|
122
126
|
this.rememberNativeBlocks({ content: finalText, toolCalls: finalToolCalls }, Object.keys(nativeBlocks).map(Number).sort((a, b) => a - b).map(index => nativeBlocks[index]));
|
|
123
127
|
}
|
|
128
|
+
requestExtensions(extensions) {
|
|
129
|
+
return omitExtensionKeys(extensions, ["model", "messages", "system", "tools", "max_tokens", "stream"]);
|
|
130
|
+
}
|
|
131
|
+
hasBetas(extensions) {
|
|
132
|
+
const betas = extensions?.betas;
|
|
133
|
+
return Array.isArray(betas) && betas.length > 0;
|
|
134
|
+
}
|
|
135
|
+
createMessage(params, extensions) {
|
|
136
|
+
return this.hasBetas(extensions)
|
|
137
|
+
? this.client.beta.messages.create(params)
|
|
138
|
+
: this.client.messages.create(params);
|
|
139
|
+
}
|
|
140
|
+
streamMessage(params, extensions) {
|
|
141
|
+
return (this.hasBetas(extensions)
|
|
142
|
+
? this.client.beta.messages.stream(params)
|
|
143
|
+
: this.client.messages.stream(params));
|
|
144
|
+
}
|
|
124
145
|
buildMessages(context) {
|
|
125
146
|
return toAnthropicMessages(context.turns, message => this.nativeAssistantBlocks.get(this.assistantReplayKey(message)));
|
|
126
147
|
}
|
package/dist/providers/base.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare class CircuitBreaker {
|
|
|
9
9
|
recordSuccess(): void;
|
|
10
10
|
recordFailure(): void;
|
|
11
11
|
}
|
|
12
|
+
export declare function omitExtensionKeys(extensions: Record<string, unknown> | undefined, keys: readonly string[]): Record<string, unknown>;
|
|
12
13
|
export declare function normalizeToolCall(id: string, name: string, args: unknown): {
|
|
13
14
|
id: string;
|
|
14
15
|
name: string;
|
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)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { RenderedContext, 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
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
8
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
|
}
|
|
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
|
+
}
|
|
8
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
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
|
}
|
|
@@ -9,6 +9,7 @@ export declare class GeminiProvider implements LLMProvider {
|
|
|
9
9
|
maxRetries: number;
|
|
10
10
|
baseDelay: number;
|
|
11
11
|
}, baseURL?: string);
|
|
12
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
12
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
13
13
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
14
|
+
private modelExtensions;
|
|
14
15
|
}
|
package/dist/providers/gemini.js
CHANGED
|
@@ -61,7 +61,7 @@ export class GeminiProvider {
|
|
|
61
61
|
this.maxRetries = retry.maxRetries;
|
|
62
62
|
this.baseDelay = retry.baseDelay;
|
|
63
63
|
}
|
|
64
|
-
async complete(context, tools) {
|
|
64
|
+
async complete(context, tools, extensions) {
|
|
65
65
|
if (this.circuit.isOpen())
|
|
66
66
|
throw new Error("Circuit breaker open");
|
|
67
67
|
const system = context.systemText || undefined;
|
|
@@ -71,6 +71,7 @@ export class GeminiProvider {
|
|
|
71
71
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
72
72
|
try {
|
|
73
73
|
const m = this.genAI.getGenerativeModel({
|
|
74
|
+
...this.modelExtensions(extensions),
|
|
74
75
|
model: this.model,
|
|
75
76
|
...(system ? { systemInstruction: system } : {}),
|
|
76
77
|
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
@@ -111,27 +112,34 @@ export class GeminiProvider {
|
|
|
111
112
|
const contents = buildContents(context.turns);
|
|
112
113
|
const geminiTools = buildTools(tools);
|
|
113
114
|
const m = this.genAI.getGenerativeModel({
|
|
115
|
+
...this.modelExtensions(extensions),
|
|
114
116
|
model: this.model,
|
|
115
117
|
...(system ? { systemInstruction: system } : {}),
|
|
116
118
|
...(geminiTools.length ? { tools: geminiTools } : {}),
|
|
117
119
|
});
|
|
118
120
|
const result = await m.generateContentStream({ contents });
|
|
119
|
-
const
|
|
121
|
+
const toolCalls = [];
|
|
120
122
|
for await (const chunk of result.stream) {
|
|
121
123
|
for (const part of chunk.candidates?.[0]?.content.parts ?? []) {
|
|
122
124
|
if (part.text)
|
|
123
125
|
yield { type: "text_delta", delta: part.text };
|
|
124
126
|
else if (part.functionCall) {
|
|
125
127
|
const { name, args } = part.functionCall;
|
|
126
|
-
|
|
128
|
+
toolCalls.push({ id: `call_${toolCalls.length + 1}`, name, args: args });
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
|
-
for (const
|
|
131
|
-
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 };
|
|
132
134
|
}
|
|
133
135
|
const usage = (await result.response).usageMetadata;
|
|
134
136
|
if (usage?.totalTokenCount)
|
|
135
137
|
yield { type: "usage", totalTokens: usage.totalTokenCount };
|
|
136
138
|
}
|
|
139
|
+
modelExtensions(extensions) {
|
|
140
|
+
if (!extensions)
|
|
141
|
+
return {};
|
|
142
|
+
const { model: _model, systemInstruction: _systemInstruction, tools: _tools, ...rest } = extensions;
|
|
143
|
+
return rest;
|
|
144
|
+
}
|
|
137
145
|
}
|
|
@@ -4,6 +4,8 @@ export declare class OllamaProvider implements LLMProvider {
|
|
|
4
4
|
private readonly baseUrl;
|
|
5
5
|
constructor(model?: string, baseUrl?: string);
|
|
6
6
|
private toOllamaMessages;
|
|
7
|
-
|
|
7
|
+
private buildTools;
|
|
8
|
+
private requestExtensions;
|
|
9
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
8
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;
|
|
@@ -22,11 +22,26 @@ export class OllamaProvider {
|
|
|
22
22
|
}
|
|
23
23
|
return result;
|
|
24
24
|
}
|
|
25
|
-
|
|
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
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
requestExtensions(extensions) {
|
|
32
|
+
return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream"]);
|
|
33
|
+
}
|
|
34
|
+
async complete(context, tools, extensions) {
|
|
26
35
|
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
27
36
|
method: "POST",
|
|
28
37
|
headers: { "Content-Type": "application/json" },
|
|
29
|
-
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
|
+
}),
|
|
30
45
|
});
|
|
31
46
|
if (!resp.ok)
|
|
32
47
|
throw new Error(`Ollama error: ${resp.status}`);
|
|
@@ -37,13 +52,20 @@ export class OllamaProvider {
|
|
|
37
52
|
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
38
53
|
method: "POST",
|
|
39
54
|
headers: { "Content-Type": "application/json" },
|
|
40
|
-
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
|
+
}),
|
|
41
62
|
});
|
|
42
63
|
if (!resp.ok)
|
|
43
64
|
throw new Error(`Ollama error: ${resp.status}`);
|
|
44
65
|
const reader = resp.body.getReader();
|
|
45
66
|
const decoder = new TextDecoder();
|
|
46
67
|
let buf = "";
|
|
68
|
+
const pendingToolCalls = new Map();
|
|
47
69
|
while (true) {
|
|
48
70
|
const { done, value } = await reader.read();
|
|
49
71
|
if (done)
|
|
@@ -59,13 +81,25 @@ export class OllamaProvider {
|
|
|
59
81
|
if (chunk.message?.content)
|
|
60
82
|
yield { type: "text_delta", delta: chunk.message.content };
|
|
61
83
|
for (const tc of chunk.message?.tool_calls ?? []) {
|
|
62
|
-
const norm = normalizeToolCall(
|
|
63
|
-
if (norm)
|
|
64
|
-
|
|
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
|
+
}
|
|
65
96
|
}
|
|
66
97
|
}
|
|
67
98
|
catch { /* skip malformed lines */ }
|
|
68
99
|
}
|
|
69
100
|
}
|
|
101
|
+
for (const tc of pendingToolCalls.values()) {
|
|
102
|
+
yield { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments };
|
|
103
|
+
}
|
|
70
104
|
}
|
|
71
105
|
}
|
|
@@ -36,7 +36,8 @@ export declare class OpenAIResponsesProvider implements LLMProvider {
|
|
|
36
36
|
baseDelay: number;
|
|
37
37
|
}, baseURL?: string);
|
|
38
38
|
createRunState(): OpenAIResponsesRunState;
|
|
39
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
40
|
-
stream(context: RenderedContext, tools: ToolSchema[],
|
|
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) {
|
|
@@ -116,7 +116,7 @@ export class OpenAIResponsesProvider {
|
|
|
116
116
|
createRunState() {
|
|
117
117
|
return { coveredMessageCount: 0 };
|
|
118
118
|
}
|
|
119
|
-
async complete(context, tools) {
|
|
119
|
+
async complete(context, tools, extensions) {
|
|
120
120
|
if (this.circuit.isOpen())
|
|
121
121
|
throw new Error("Circuit breaker open");
|
|
122
122
|
let lastErr;
|
|
@@ -124,6 +124,7 @@ export class OpenAIResponsesProvider {
|
|
|
124
124
|
try {
|
|
125
125
|
const instructions = this.responses.buildInstructions(context);
|
|
126
126
|
const resp = await this.client.responses.create({
|
|
127
|
+
...this.requestExtensions(extensions),
|
|
127
128
|
model: this.model,
|
|
128
129
|
input: this.responses.buildInput(context),
|
|
129
130
|
...(instructions ? { instructions } : {}),
|
|
@@ -147,11 +148,12 @@ export class OpenAIResponsesProvider {
|
|
|
147
148
|
}
|
|
148
149
|
throw lastErr;
|
|
149
150
|
}
|
|
150
|
-
async *stream(context, tools,
|
|
151
|
+
async *stream(context, tools, extensions, state) {
|
|
151
152
|
const runState = this.asRunState(state);
|
|
152
153
|
const functionCalls = new Map();
|
|
153
154
|
const instructions = this.responses.buildInstructions(context);
|
|
154
155
|
const stream = await this.client.responses.create({
|
|
156
|
+
...this.requestExtensions(extensions),
|
|
155
157
|
model: this.model,
|
|
156
158
|
input: this.responses.buildInput(context, runState),
|
|
157
159
|
...(instructions ? { instructions } : {}),
|
|
@@ -204,6 +206,11 @@ export class OpenAIResponsesProvider {
|
|
|
204
206
|
}
|
|
205
207
|
}
|
|
206
208
|
}
|
|
209
|
+
requestExtensions(extensions) {
|
|
210
|
+
return omitExtensionKeys(extensions, [
|
|
211
|
+
"model", "input", "instructions", "tools", "stream", "previous_response_id",
|
|
212
|
+
]);
|
|
213
|
+
}
|
|
207
214
|
asRunState(state) {
|
|
208
215
|
if (!state)
|
|
209
216
|
return this.createRunState();
|
|
@@ -13,7 +13,8 @@ export declare class OpenAIChatProvider implements LLMProvider {
|
|
|
13
13
|
maxRetries: number;
|
|
14
14
|
baseDelay: number;
|
|
15
15
|
}, baseURL?: string);
|
|
16
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
16
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
17
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 };
|
package/dist/providers/openai.js
CHANGED
|
@@ -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 { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
5
|
export class OpenAIChatProvider {
|
|
6
6
|
model;
|
|
@@ -16,7 +16,7 @@ export class OpenAIChatProvider {
|
|
|
16
16
|
this.maxRetries = retry.maxRetries;
|
|
17
17
|
this.baseDelay = retry.baseDelay;
|
|
18
18
|
}
|
|
19
|
-
async complete(context, tools) {
|
|
19
|
+
async complete(context, tools, extensions) {
|
|
20
20
|
if (this.circuit.isOpen())
|
|
21
21
|
throw new Error("Circuit breaker open");
|
|
22
22
|
const msgs = this.chat.buildMessages(context);
|
|
@@ -24,6 +24,7 @@ export class OpenAIChatProvider {
|
|
|
24
24
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
25
25
|
try {
|
|
26
26
|
const resp = await this.client.chat.completions.create({
|
|
27
|
+
...this.requestExtensions(extensions),
|
|
27
28
|
model: this.model,
|
|
28
29
|
messages: msgs,
|
|
29
30
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
@@ -45,7 +46,9 @@ export class OpenAIChatProvider {
|
|
|
45
46
|
async *stream(context, tools, extensions) {
|
|
46
47
|
const msgs = this.chat.buildMessages(context);
|
|
47
48
|
const toolCallBufs = {};
|
|
49
|
+
const emittedToolCallIndexes = new Set();
|
|
48
50
|
const stream = await this.client.chat.completions.create({
|
|
51
|
+
...this.requestExtensions(extensions),
|
|
49
52
|
model: this.model,
|
|
50
53
|
messages: msgs,
|
|
51
54
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
@@ -73,7 +76,10 @@ export class OpenAIChatProvider {
|
|
|
73
76
|
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
74
77
|
}
|
|
75
78
|
if (choice.finish_reason === "tool_calls") {
|
|
76
|
-
for (const tb of Object.
|
|
79
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
80
|
+
const idx = Number(index);
|
|
81
|
+
if (emittedToolCallIndexes.has(idx))
|
|
82
|
+
continue;
|
|
77
83
|
let args = {};
|
|
78
84
|
try {
|
|
79
85
|
args = JSON.parse(tb.argsBuf || "{}");
|
|
@@ -81,12 +87,30 @@ export class OpenAIChatProvider {
|
|
|
81
87
|
catch {
|
|
82
88
|
args = {};
|
|
83
89
|
}
|
|
90
|
+
emittedToolCallIndexes.add(idx);
|
|
84
91
|
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
94
|
}
|
|
95
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
96
|
+
const idx = Number(index);
|
|
97
|
+
if (emittedToolCallIndexes.has(idx))
|
|
98
|
+
continue;
|
|
99
|
+
let args = {};
|
|
100
|
+
try {
|
|
101
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
args = {};
|
|
105
|
+
}
|
|
106
|
+
emittedToolCallIndexes.add(idx);
|
|
107
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
108
|
+
}
|
|
88
109
|
if (totalTokens > 0)
|
|
89
110
|
yield { type: "usage", totalTokens };
|
|
90
111
|
}
|
|
112
|
+
requestExtensions(extensions) {
|
|
113
|
+
return omitExtensionKeys(extensions, ["model", "messages", "tools", "stream", "stream_options"]);
|
|
114
|
+
}
|
|
91
115
|
}
|
|
92
116
|
export { OpenAIChatProvider as OpenAIProvider };
|
package/dist/providers/qwen.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare class QwenProvider implements LLMProvider {
|
|
|
13
13
|
maxRetries: number;
|
|
14
14
|
baseDelay: number;
|
|
15
15
|
}, baseURL?: string);
|
|
16
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
16
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
17
17
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
18
|
+
private thinkingExtraBody;
|
|
19
|
+
private requestExtensions;
|
|
18
20
|
}
|
package/dist/providers/qwen.js
CHANGED
|
@@ -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 { OpenAIChatAdapter } from "./openai-chat.js";
|
|
5
5
|
import { endpointProfiles } from "./profiles.js";
|
|
6
6
|
const QWEN_BASE = endpointProfiles["qwen.dashscope"].baseURL;
|
|
@@ -18,17 +18,20 @@ export class QwenProvider {
|
|
|
18
18
|
this.maxRetries = retry.maxRetries;
|
|
19
19
|
this.baseDelay = retry.baseDelay;
|
|
20
20
|
}
|
|
21
|
-
async complete(context, tools) {
|
|
21
|
+
async complete(context, tools, extensions) {
|
|
22
22
|
if (this.circuit.isOpen())
|
|
23
23
|
throw new Error("Circuit breaker open");
|
|
24
24
|
const msgs = this.chat.buildMessages(context);
|
|
25
|
+
const extraBody = this.thinkingExtraBody(extensions);
|
|
25
26
|
let lastErr;
|
|
26
27
|
for (let i = 0; i < this.maxRetries; i++) {
|
|
27
28
|
try {
|
|
28
29
|
const resp = await this.client.chat.completions.create({
|
|
30
|
+
...this.requestExtensions(extensions),
|
|
29
31
|
model: this.model,
|
|
30
32
|
messages: msgs,
|
|
31
33
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
34
|
+
...(extraBody ? { extra_body: extraBody } : {}),
|
|
32
35
|
});
|
|
33
36
|
this.circuit.recordSuccess();
|
|
34
37
|
const choice = resp.choices[0].message;
|
|
@@ -45,22 +48,18 @@ export class QwenProvider {
|
|
|
45
48
|
throw lastErr;
|
|
46
49
|
}
|
|
47
50
|
async *stream(context, tools, extensions) {
|
|
48
|
-
const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
|
|
49
|
-
const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
|
|
50
51
|
const msgs = this.chat.buildMessages(context);
|
|
51
52
|
const toolCallBufs = {};
|
|
53
|
+
const emittedToolCallIndexes = new Set();
|
|
54
|
+
const extraBody = this.thinkingExtraBody(extensions);
|
|
52
55
|
const stream = await this.client.chat.completions.create({
|
|
56
|
+
...this.requestExtensions(extensions),
|
|
53
57
|
model: this.model,
|
|
54
58
|
messages: msgs,
|
|
55
59
|
...(tools.length ? { tools: this.chat.buildTools(tools) } : {}),
|
|
56
60
|
stream: true,
|
|
57
61
|
stream_options: { include_usage: true },
|
|
58
|
-
...(
|
|
59
|
-
extra_body: {
|
|
60
|
-
enable_thinking: true,
|
|
61
|
-
...(typeof thinkingBudget === "number" ? { thinking_budget: thinkingBudget } : {}),
|
|
62
|
-
},
|
|
63
|
-
} : {}),
|
|
62
|
+
...(extraBody ? { extra_body: extraBody } : {}),
|
|
64
63
|
});
|
|
65
64
|
let totalTokens = 0;
|
|
66
65
|
for await (const chunk of stream) {
|
|
@@ -85,7 +84,10 @@ export class QwenProvider {
|
|
|
85
84
|
toolCallBufs[idx].argsBuf += tc.function?.arguments ?? "";
|
|
86
85
|
}
|
|
87
86
|
if (choice.finish_reason === "tool_calls") {
|
|
88
|
-
for (const tb of Object.
|
|
87
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
88
|
+
const idx = Number(index);
|
|
89
|
+
if (emittedToolCallIndexes.has(idx))
|
|
90
|
+
continue;
|
|
89
91
|
let args = {};
|
|
90
92
|
try {
|
|
91
93
|
args = JSON.parse(tb.argsBuf || "{}");
|
|
@@ -93,11 +95,42 @@ export class QwenProvider {
|
|
|
93
95
|
catch {
|
|
94
96
|
args = {};
|
|
95
97
|
}
|
|
98
|
+
emittedToolCallIndexes.add(idx);
|
|
96
99
|
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
}
|
|
103
|
+
for (const [index, tb] of Object.entries(toolCallBufs)) {
|
|
104
|
+
const idx = Number(index);
|
|
105
|
+
if (emittedToolCallIndexes.has(idx))
|
|
106
|
+
continue;
|
|
107
|
+
let args = {};
|
|
108
|
+
try {
|
|
109
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
args = {};
|
|
113
|
+
}
|
|
114
|
+
emittedToolCallIndexes.add(idx);
|
|
115
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
116
|
+
}
|
|
100
117
|
if (totalTokens > 0)
|
|
101
118
|
yield { type: "usage", totalTokens };
|
|
102
119
|
}
|
|
120
|
+
thinkingExtraBody(extensions) {
|
|
121
|
+
const enableThinking = Boolean(extensions?.enableThinking ?? extensions?.enable_thinking);
|
|
122
|
+
const thinkingBudget = extensions?.thinkingBudget ?? extensions?.thinking_budget;
|
|
123
|
+
if (!enableThinking)
|
|
124
|
+
return undefined;
|
|
125
|
+
return {
|
|
126
|
+
enable_thinking: true,
|
|
127
|
+
...(typeof thinkingBudget === "number" ? { thinking_budget: thinkingBudget } : {}),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
requestExtensions(extensions) {
|
|
131
|
+
return omitExtensionKeys(extensions, [
|
|
132
|
+
"model", "messages", "tools", "stream", "stream_options", "extra_body",
|
|
133
|
+
"enableThinking", "enable_thinking", "thinkingBudget", "thinking_budget",
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
103
136
|
}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { ToolSchema, ToolResult } from "../types.js";
|
|
1
|
+
import type { ToolChunk, ToolSchema, ToolResult } from "../types.js";
|
|
2
2
|
export interface RegisteredTool {
|
|
3
3
|
schema: ToolSchema;
|
|
4
|
-
execute(args: Record<string, unknown>): Promise<string>;
|
|
4
|
+
execute(args: Record<string, unknown>): Promise<string> | AsyncIterable<ToolChunk>;
|
|
5
5
|
}
|
|
6
6
|
export declare function tool(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>) => Promise<string> | string): RegisteredTool;
|
|
7
|
+
export declare function streamingTool(name: string, description: string, parameters: Record<string, unknown>, fn: (args: Record<string, unknown>) => AsyncIterable<ToolChunk>): RegisteredTool;
|
|
8
|
+
export declare function isAsyncIterable<T>(value: unknown): value is AsyncIterable<T>;
|
|
9
|
+
export declare function normalizeToolChunk(chunk: ToolChunk): Exclude<ToolChunk, string>;
|
|
10
|
+
export declare function toolChunkText(chunk: ToolChunk): string;
|
|
11
|
+
export declare function validateToolArguments(schemaJson: string, args: Record<string, unknown>): string | undefined;
|
|
7
12
|
export declare function executeTools(calls: {
|
|
8
13
|
id: string;
|
|
9
14
|
name: string;
|
package/dist/tools/index.js
CHANGED
|
@@ -4,6 +4,80 @@ export function tool(name, description, parameters, fn) {
|
|
|
4
4
|
async execute(args) { return fn(args); },
|
|
5
5
|
};
|
|
6
6
|
}
|
|
7
|
+
export function streamingTool(name, description, parameters, fn) {
|
|
8
|
+
return {
|
|
9
|
+
schema: { name, description, parameters: JSON.stringify(parameters) },
|
|
10
|
+
execute(args) { return fn(args); },
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function isAsyncIterable(value) {
|
|
14
|
+
return typeof value === "object" && value !== null && Symbol.asyncIterator in value;
|
|
15
|
+
}
|
|
16
|
+
export function normalizeToolChunk(chunk) {
|
|
17
|
+
return typeof chunk === "string" ? { type: "text", text: chunk } : chunk;
|
|
18
|
+
}
|
|
19
|
+
export function toolChunkText(chunk) {
|
|
20
|
+
const normalized = normalizeToolChunk(chunk);
|
|
21
|
+
return normalized.type === "text" ? normalized.text : "";
|
|
22
|
+
}
|
|
23
|
+
export function validateToolArguments(schemaJson, args) {
|
|
24
|
+
let schema;
|
|
25
|
+
try {
|
|
26
|
+
schema = JSON.parse(schemaJson);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return "invalid tool schema";
|
|
30
|
+
}
|
|
31
|
+
return validateValue(schema, args, "$", true);
|
|
32
|
+
}
|
|
33
|
+
function validateValue(schema, value, path, isRoot = false) {
|
|
34
|
+
const expectedType = schema.type;
|
|
35
|
+
if (typeof expectedType === "string") {
|
|
36
|
+
if (expectedType === "object") {
|
|
37
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
38
|
+
return `${path} must be object`;
|
|
39
|
+
const obj = value;
|
|
40
|
+
for (const required of schema.required ?? []) {
|
|
41
|
+
if (!(required in obj))
|
|
42
|
+
return `${path}.${required} is required`;
|
|
43
|
+
}
|
|
44
|
+
const properties = schema.properties ?? {};
|
|
45
|
+
for (const [key, child] of Object.entries(properties)) {
|
|
46
|
+
if (key in obj) {
|
|
47
|
+
const err = validateValue(child, obj[key], `${path}.${key}`);
|
|
48
|
+
if (err)
|
|
49
|
+
return err;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else if (expectedType === "array") {
|
|
54
|
+
if (!Array.isArray(value))
|
|
55
|
+
return `${path} must be array`;
|
|
56
|
+
}
|
|
57
|
+
else if (expectedType === "string") {
|
|
58
|
+
if (typeof value !== "string")
|
|
59
|
+
return `${path} must be string`;
|
|
60
|
+
}
|
|
61
|
+
else if (expectedType === "number") {
|
|
62
|
+
if (typeof value !== "number" || Number.isNaN(value))
|
|
63
|
+
return `${path} must be number`;
|
|
64
|
+
}
|
|
65
|
+
else if (expectedType === "integer") {
|
|
66
|
+
if (!Number.isInteger(value))
|
|
67
|
+
return `${path} must be integer`;
|
|
68
|
+
}
|
|
69
|
+
else if (expectedType === "boolean") {
|
|
70
|
+
if (typeof value !== "boolean")
|
|
71
|
+
return `${path} must be boolean`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (isRoot && (!value || typeof value !== "object" || Array.isArray(value))) {
|
|
75
|
+
return `${path} must be object`;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value))
|
|
78
|
+
return `${path} must be one of enum values`;
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
7
81
|
export async function executeTools(calls, registry) {
|
|
8
82
|
return Promise.all(calls.map(async (c) => {
|
|
9
83
|
const t = registry.get(c.name);
|
|
@@ -11,7 +85,16 @@ export async function executeTools(calls, registry) {
|
|
|
11
85
|
return { callId: c.id, output: `unknown tool: ${c.name}`, isError: true };
|
|
12
86
|
try {
|
|
13
87
|
const args = JSON.parse(c.arguments || "{}");
|
|
88
|
+
const validationError = validateToolArguments(t.schema.parameters, args);
|
|
89
|
+
if (validationError)
|
|
90
|
+
return { callId: c.id, output: `invalid arguments: ${validationError}`, isError: true };
|
|
14
91
|
const output = await t.execute(args);
|
|
92
|
+
if (isAsyncIterable(output)) {
|
|
93
|
+
let combined = "";
|
|
94
|
+
for await (const chunk of output)
|
|
95
|
+
combined += toolChunkText(chunk);
|
|
96
|
+
return { callId: c.id, output: combined, isError: false };
|
|
97
|
+
}
|
|
15
98
|
return { callId: c.id, output, isError: false };
|
|
16
99
|
}
|
|
17
100
|
catch (err) {
|
package/dist/types.d.ts
CHANGED
|
@@ -69,6 +69,41 @@ export interface ToolCallEvent extends StreamEvent {
|
|
|
69
69
|
name: string;
|
|
70
70
|
arguments: Record<string, unknown>;
|
|
71
71
|
}
|
|
72
|
+
export type ToolChunk = string | {
|
|
73
|
+
type: "text";
|
|
74
|
+
text: string;
|
|
75
|
+
} | {
|
|
76
|
+
type: "progress";
|
|
77
|
+
progress: number;
|
|
78
|
+
message?: string;
|
|
79
|
+
} | {
|
|
80
|
+
type: "artifact";
|
|
81
|
+
artifactId: string;
|
|
82
|
+
mimeType?: string;
|
|
83
|
+
label?: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: "json_patch";
|
|
86
|
+
patch: Record<string, unknown>;
|
|
87
|
+
} | {
|
|
88
|
+
type: "suspend";
|
|
89
|
+
suspensionId: string;
|
|
90
|
+
payload?: Record<string, unknown>;
|
|
91
|
+
};
|
|
92
|
+
export interface ToolDeltaEvent extends StreamEvent {
|
|
93
|
+
type: "tool_delta";
|
|
94
|
+
callId: string;
|
|
95
|
+
name: string;
|
|
96
|
+
/** Backward-compatible text projection when the chunk carries text. */
|
|
97
|
+
delta?: string;
|
|
98
|
+
chunk: Exclude<ToolChunk, string>;
|
|
99
|
+
}
|
|
100
|
+
export interface ToolSuspendEvent extends StreamEvent {
|
|
101
|
+
type: "tool_suspend";
|
|
102
|
+
callId: string;
|
|
103
|
+
name: string;
|
|
104
|
+
suspensionId: string;
|
|
105
|
+
payload?: Record<string, unknown>;
|
|
106
|
+
}
|
|
72
107
|
export interface ToolResultEvent extends StreamEvent {
|
|
73
108
|
type: "tool_result";
|
|
74
109
|
callId: string;
|
|
@@ -129,6 +164,6 @@ export interface RenderedContext {
|
|
|
129
164
|
}
|
|
130
165
|
export interface LLMProvider {
|
|
131
166
|
createRunState?(): ProviderRunState;
|
|
132
|
-
complete(context: RenderedContext, tools: ToolSchema[]): Promise<Message>;
|
|
167
|
+
complete(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>): Promise<Message>;
|
|
133
168
|
stream(context: RenderedContext, tools: ToolSchema[], extensions?: Record<string, unknown>, state?: ProviderRunState): AsyncIterable<StreamEvent>;
|
|
134
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepstrike/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "DeepStrike Node.js SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
19
|
-
"@deepstrike/core": "0.1.
|
|
19
|
+
"@deepstrike/core": "0.1.13",
|
|
20
20
|
"@google/generative-ai": "^0.24.1",
|
|
21
21
|
"openai": "^4.77.0"
|
|
22
22
|
},
|