@deepstrike/sdk 0.1.1
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 +296 -0
- package/dist/agent.d.ts +62 -0
- package/dist/agent.js +321 -0
- package/dist/harness/harness.d.ts +40 -0
- package/dist/harness/harness.js +100 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +12 -0
- package/dist/knowledge/source.d.ts +3 -0
- package/dist/knowledge/source.js +1 -0
- package/dist/memory/protocols.d.ts +48 -0
- package/dist/memory/protocols.js +2 -0
- package/dist/memory/working.d.ts +8 -0
- package/dist/memory/working.js +8 -0
- package/dist/providers/anthropic.d.ts +15 -0
- package/dist/providers/anthropic.js +102 -0
- package/dist/providers/base.d.ts +15 -0
- package/dist/providers/base.js +46 -0
- package/dist/providers/ollama.d.ts +9 -0
- package/dist/providers/ollama.js +58 -0
- package/dist/providers/openai.d.ts +44 -0
- package/dist/providers/openai.js +144 -0
- package/dist/safety/permissions.d.ts +17 -0
- package/dist/safety/permissions.js +32 -0
- package/dist/signals/gateway.d.ts +30 -0
- package/dist/signals/gateway.js +71 -0
- package/dist/signals/scheduled.d.ts +9 -0
- package/dist/signals/scheduled.js +18 -0
- package/dist/signals/types.d.ts +8 -0
- package/dist/signals/types.js +1 -0
- package/dist/skills/loader.d.ts +11 -0
- package/dist/skills/loader.js +43 -0
- package/dist/tools/index.d.ts +12 -0
- package/dist/tools/index.js +25 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.js +1 -0
- package/package.json +25 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Agent } from "../agent.js";
|
|
2
|
+
export interface HarnessRequest {
|
|
3
|
+
goal: string;
|
|
4
|
+
criteria?: string[];
|
|
5
|
+
extensions?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
export interface HarnessOutcome {
|
|
8
|
+
result: string;
|
|
9
|
+
passed: boolean;
|
|
10
|
+
iterations: number;
|
|
11
|
+
totalTokens: number;
|
|
12
|
+
status: string;
|
|
13
|
+
/** Feedback from the evaluator LLM — injected into the next attempt's goal. */
|
|
14
|
+
feedback?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class SinglePassHarness {
|
|
17
|
+
private agent;
|
|
18
|
+
constructor(agent: Agent);
|
|
19
|
+
run(request: HarnessRequest): Promise<HarnessOutcome>;
|
|
20
|
+
}
|
|
21
|
+
export interface HarnessLoopOptions {
|
|
22
|
+
maxAttempts?: number;
|
|
23
|
+
/** Directory to write distilled skills into. Requires the agent to have skillDir set. */
|
|
24
|
+
skillDir?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Eval loop with LLM-as-judge and feedback injection.
|
|
28
|
+
*
|
|
29
|
+
* Each failed attempt feeds the evaluator's feedback back into the next goal,
|
|
30
|
+
* so the agent knows *why* it failed. On success, if the evaluator proposes a
|
|
31
|
+
* skill candidate it is written to `skillDir` for future sessions to reuse.
|
|
32
|
+
*/
|
|
33
|
+
export declare class HarnessLoop {
|
|
34
|
+
private agent;
|
|
35
|
+
private evalProvider;
|
|
36
|
+
private maxAttempts;
|
|
37
|
+
private skillDir?;
|
|
38
|
+
constructor(agent: Agent, evalProvider: import("../types.js").LLMProvider, options?: HarnessLoopOptions);
|
|
39
|
+
run(request: HarnessRequest): Promise<HarnessOutcome>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { writeFile } from "fs/promises";
|
|
2
|
+
import path from "path";
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
async function loadKernel() {
|
|
5
|
+
return import("@deepstrike/core");
|
|
6
|
+
}
|
|
7
|
+
async function runOnce(agent, req) {
|
|
8
|
+
let text = "";
|
|
9
|
+
let done;
|
|
10
|
+
for await (const evt of agent.runStreaming(req.goal, req.criteria, req.extensions)) {
|
|
11
|
+
if (evt.type === "text_delta")
|
|
12
|
+
text += evt.delta;
|
|
13
|
+
else if (evt.type === "done")
|
|
14
|
+
done = evt;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
result: text,
|
|
18
|
+
passed: false,
|
|
19
|
+
iterations: done?.iterations ?? 0,
|
|
20
|
+
totalTokens: done?.totalTokens ?? 0,
|
|
21
|
+
status: done?.status ?? "error",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export class SinglePassHarness {
|
|
25
|
+
agent;
|
|
26
|
+
constructor(agent) {
|
|
27
|
+
this.agent = agent;
|
|
28
|
+
}
|
|
29
|
+
async run(request) {
|
|
30
|
+
return { ...await runOnce(this.agent, request), passed: true };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Eval loop with LLM-as-judge and feedback injection.
|
|
35
|
+
*
|
|
36
|
+
* Each failed attempt feeds the evaluator's feedback back into the next goal,
|
|
37
|
+
* so the agent knows *why* it failed. On success, if the evaluator proposes a
|
|
38
|
+
* skill candidate it is written to `skillDir` for future sessions to reuse.
|
|
39
|
+
*/
|
|
40
|
+
export class HarnessLoop {
|
|
41
|
+
agent;
|
|
42
|
+
evalProvider;
|
|
43
|
+
maxAttempts;
|
|
44
|
+
skillDir;
|
|
45
|
+
constructor(agent, evalProvider, options = {}) {
|
|
46
|
+
this.agent = agent;
|
|
47
|
+
this.evalProvider = evalProvider;
|
|
48
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
49
|
+
this.skillDir = options.skillDir;
|
|
50
|
+
}
|
|
51
|
+
async run(request) {
|
|
52
|
+
const kernel = await loadKernel();
|
|
53
|
+
const pipeline = new kernel.EvalPipeline({ extractSkillOnPass: true });
|
|
54
|
+
let outcome = { result: "", passed: false, iterations: 0, totalTokens: 0, status: "error" };
|
|
55
|
+
let currentGoal = request.goal;
|
|
56
|
+
for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {
|
|
57
|
+
outcome = await runOnce(this.agent, { ...request, goal: currentGoal });
|
|
58
|
+
// Phase 1: kernel builds eval prompt
|
|
59
|
+
const evalAction = pipeline.feedOutcome({
|
|
60
|
+
goal: request.goal,
|
|
61
|
+
criteria: request.criteria ?? [],
|
|
62
|
+
result: outcome.result,
|
|
63
|
+
attempt,
|
|
64
|
+
});
|
|
65
|
+
if (evalAction.kind !== "evaluate")
|
|
66
|
+
break;
|
|
67
|
+
// Phase 2: SDK calls evaluator LLM
|
|
68
|
+
let evalText = "";
|
|
69
|
+
for await (const evt of this.evalProvider.stream(evalAction.messages ?? [], [], undefined)) {
|
|
70
|
+
if (evt.type === "text_delta")
|
|
71
|
+
evalText += evt.delta;
|
|
72
|
+
}
|
|
73
|
+
// Phase 3: kernel parses verdict
|
|
74
|
+
const doneAction = pipeline.feedEvalResult({ content: evalText });
|
|
75
|
+
if (doneAction.kind !== "done")
|
|
76
|
+
break;
|
|
77
|
+
const evalResult = doneAction.result;
|
|
78
|
+
outcome = { ...outcome, passed: evalResult.passed, feedback: evalResult.feedback };
|
|
79
|
+
if (evalResult.passed) {
|
|
80
|
+
if (evalResult.skillCandidate && this.skillDir) {
|
|
81
|
+
const { name, description, whenToUse, content } = evalResult.skillCandidate;
|
|
82
|
+
const frontmatter = [
|
|
83
|
+
"---",
|
|
84
|
+
`name: ${name}`,
|
|
85
|
+
`description: ${description}`,
|
|
86
|
+
whenToUse ? `when_to_use: ${whenToUse}` : null,
|
|
87
|
+
"---",
|
|
88
|
+
"",
|
|
89
|
+
].filter(l => l !== null).join("\n");
|
|
90
|
+
await writeFile(path.join(this.skillDir, `${name}.md`), frontmatter + content, "utf8");
|
|
91
|
+
}
|
|
92
|
+
return outcome;
|
|
93
|
+
}
|
|
94
|
+
// Inject feedback into next attempt's goal
|
|
95
|
+
currentGoal = `${request.goal}\n\n[Previous attempt ${attempt} failed: ${evalResult.feedback}]`;
|
|
96
|
+
pipeline.reset();
|
|
97
|
+
}
|
|
98
|
+
return outcome;
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { Agent } from "./agent.js";
|
|
2
|
+
export type { AgentOptions } from "./agent.js";
|
|
3
|
+
export { AnthropicProvider } from "./providers/anthropic.js";
|
|
4
|
+
export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider } from "./providers/openai.js";
|
|
5
|
+
export { OllamaProvider } from "./providers/ollama.js";
|
|
6
|
+
export { CircuitBreaker, normalizeToolCall } from "./providers/base.js";
|
|
7
|
+
export { tool, executeTools, readFile } from "./tools/index.js";
|
|
8
|
+
export type { RegisteredTool } from "./tools/index.js";
|
|
9
|
+
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
|
10
|
+
export type { SkillMetadata } from "./skills/loader.js";
|
|
11
|
+
export { WorkingMemory } from "./memory/working.js";
|
|
12
|
+
export type { DreamStore, DreamResult, SessionData, SessionMessage, MemoryEntry, CurationResult, CurationStats, } from "./memory/protocols.js";
|
|
13
|
+
export type { KnowledgeSource } from "./knowledge/source.js";
|
|
14
|
+
export { SinglePassHarness, HarnessLoop } from "./harness/harness.js";
|
|
15
|
+
export type { HarnessRequest, HarnessOutcome, HarnessLoopOptions } from "./harness/harness.js";
|
|
16
|
+
export { ScheduledPrompt } from "./signals/scheduled.js";
|
|
17
|
+
export { SignalGateway } from "./signals/gateway.js";
|
|
18
|
+
export type { RuntimeSignal, SignalSource } from "./signals/types.js";
|
|
19
|
+
export { PermissionManager, PermissionMode } from "./safety/permissions.js";
|
|
20
|
+
export type { PermissionDecision } from "./safety/permissions.js";
|
|
21
|
+
export type { Message, ToolCall, ToolResult, ToolSchema, StreamEvent, TextDelta, ThinkingDelta, ToolCallEvent, ToolResultEvent, DoneEvent, ErrorEvent, PermissionRequestEvent, LLMProvider, RetryConfig, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { Agent } from "./agent.js";
|
|
2
|
+
export { AnthropicProvider } from "./providers/anthropic.js";
|
|
3
|
+
export { OpenAIProvider, QwenProvider, DeepSeekProvider, MiniMaxProvider } from "./providers/openai.js";
|
|
4
|
+
export { OllamaProvider } from "./providers/ollama.js";
|
|
5
|
+
export { CircuitBreaker, normalizeToolCall } from "./providers/base.js";
|
|
6
|
+
export { tool, executeTools, readFile } from "./tools/index.js";
|
|
7
|
+
export { scanSkillDir, readSkillFile } from "./skills/loader.js";
|
|
8
|
+
export { WorkingMemory } from "./memory/working.js";
|
|
9
|
+
export { SinglePassHarness, HarnessLoop } from "./harness/harness.js";
|
|
10
|
+
export { ScheduledPrompt } from "./signals/scheduled.js";
|
|
11
|
+
export { SignalGateway } from "./signals/gateway.js";
|
|
12
|
+
export { PermissionManager, PermissionMode } from "./safety/permissions.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface SessionMessage {
|
|
2
|
+
role: string;
|
|
3
|
+
content: string;
|
|
4
|
+
tokenCount?: number;
|
|
5
|
+
toolCalls?: Array<{
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
arguments: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface SessionData {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
agentId: string;
|
|
14
|
+
messages: SessionMessage[];
|
|
15
|
+
metadata: unknown;
|
|
16
|
+
createdAtMs: number;
|
|
17
|
+
updatedAtMs: number;
|
|
18
|
+
}
|
|
19
|
+
export interface MemoryEntry {
|
|
20
|
+
text: string;
|
|
21
|
+
score: number;
|
|
22
|
+
metadata: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface CurationStats {
|
|
25
|
+
insightsProcessed: number;
|
|
26
|
+
duplicatesRemoved: number;
|
|
27
|
+
conflictsResolved: number;
|
|
28
|
+
entriesAdded: number;
|
|
29
|
+
}
|
|
30
|
+
export interface CurationResult {
|
|
31
|
+
toAdd: MemoryEntry[];
|
|
32
|
+
/** Indices into the `existingMemories` array passed to `DreamStore.loadMemories`. */
|
|
33
|
+
toRemoveIndices: number[];
|
|
34
|
+
stats: CurationStats;
|
|
35
|
+
}
|
|
36
|
+
export interface DreamStore {
|
|
37
|
+
loadSessions(agentId: string): Promise<SessionData[]>;
|
|
38
|
+
loadMemories(agentId: string): Promise<MemoryEntry[]>;
|
|
39
|
+
commit(agentId: string, result: CurationResult, existing: MemoryEntry[]): Promise<void>;
|
|
40
|
+
/** Semantic search over the agent's long-term memories. Called on demand during a run. */
|
|
41
|
+
search(agentId: string, query: string, topK?: number): Promise<MemoryEntry[]>;
|
|
42
|
+
}
|
|
43
|
+
export interface DreamResult {
|
|
44
|
+
sessionsProcessed: number;
|
|
45
|
+
insightsExtracted: number;
|
|
46
|
+
entriesAdded: number;
|
|
47
|
+
entriesRemoved: number;
|
|
48
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export class WorkingMemory {
|
|
2
|
+
store = new Map();
|
|
3
|
+
set(key, value) { this.store.set(key, value); }
|
|
4
|
+
get(key, defaultValue) { return (this.store.get(key) ?? defaultValue); }
|
|
5
|
+
delete(key) { this.store.delete(key); }
|
|
6
|
+
clear() { this.store.clear(); }
|
|
7
|
+
has(key) { return this.store.has(key); }
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
|
+
export declare class AnthropicProvider implements LLMProvider {
|
|
3
|
+
private readonly model;
|
|
4
|
+
private client;
|
|
5
|
+
private circuit;
|
|
6
|
+
private maxRetries;
|
|
7
|
+
private baseDelay;
|
|
8
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
9
|
+
maxRetries: number;
|
|
10
|
+
baseDelay: number;
|
|
11
|
+
});
|
|
12
|
+
private buildTools;
|
|
13
|
+
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
14
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
2
|
+
import { CircuitBreaker, normalizeToolCall } from "./base.js";
|
|
3
|
+
export class AnthropicProvider {
|
|
4
|
+
model;
|
|
5
|
+
client;
|
|
6
|
+
circuit;
|
|
7
|
+
maxRetries;
|
|
8
|
+
baseDelay;
|
|
9
|
+
constructor(apiKey, model = "claude-sonnet-4-6", retry = { maxRetries: 3, baseDelay: 1000 }) {
|
|
10
|
+
this.model = model;
|
|
11
|
+
this.client = new Anthropic({ apiKey });
|
|
12
|
+
this.circuit = new CircuitBreaker();
|
|
13
|
+
this.maxRetries = retry.maxRetries;
|
|
14
|
+
this.baseDelay = retry.baseDelay;
|
|
15
|
+
}
|
|
16
|
+
buildTools(tools) {
|
|
17
|
+
return tools.map(t => ({
|
|
18
|
+
name: t.name,
|
|
19
|
+
description: t.description,
|
|
20
|
+
input_schema: JSON.parse(t.parameters),
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
async complete(messages, tools) {
|
|
24
|
+
if (this.circuit.isOpen())
|
|
25
|
+
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: m.content }));
|
|
28
|
+
let lastErr;
|
|
29
|
+
for (let i = 0; i < this.maxRetries; i++) {
|
|
30
|
+
try {
|
|
31
|
+
const resp = await this.client.messages.create({
|
|
32
|
+
model: this.model,
|
|
33
|
+
max_tokens: 8096,
|
|
34
|
+
...(system ? { system } : {}),
|
|
35
|
+
messages: msgs,
|
|
36
|
+
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
37
|
+
});
|
|
38
|
+
this.circuit.recordSuccess();
|
|
39
|
+
let content = "";
|
|
40
|
+
const toolCalls = [];
|
|
41
|
+
for (const block of resp.content) {
|
|
42
|
+
if (block.type === "text")
|
|
43
|
+
content += block.text;
|
|
44
|
+
else if (block.type === "tool_use") {
|
|
45
|
+
const tc = normalizeToolCall(block.id, block.name, block.input);
|
|
46
|
+
if (tc)
|
|
47
|
+
toolCalls.push(tc);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { role: "assistant", content, tokenCount: resp.usage.input_tokens + resp.usage.output_tokens, toolCalls };
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
lastErr = err;
|
|
54
|
+
this.circuit.recordFailure();
|
|
55
|
+
if (i < this.maxRetries - 1)
|
|
56
|
+
await new Promise(r => setTimeout(r, this.baseDelay * 2 ** i));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw lastErr;
|
|
60
|
+
}
|
|
61
|
+
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: m.content }));
|
|
64
|
+
const toolBlocks = {};
|
|
65
|
+
const stream = this.client.messages.stream({
|
|
66
|
+
model: this.model,
|
|
67
|
+
max_tokens: 8096,
|
|
68
|
+
...(system ? { system } : {}),
|
|
69
|
+
messages: msgs,
|
|
70
|
+
...(tools.length ? { tools: this.buildTools(tools) } : {}),
|
|
71
|
+
});
|
|
72
|
+
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: "" };
|
|
75
|
+
}
|
|
76
|
+
else if (evt.type === "content_block_delta") {
|
|
77
|
+
const d = evt.delta;
|
|
78
|
+
if (d.type === "text_delta") {
|
|
79
|
+
yield { type: "text_delta", delta: d.text };
|
|
80
|
+
}
|
|
81
|
+
else if (d.type === "thinking_delta") {
|
|
82
|
+
yield { type: "thinking_delta", delta: d.thinking };
|
|
83
|
+
}
|
|
84
|
+
else if (d.type === "input_json_delta" && toolBlocks[evt.index]) {
|
|
85
|
+
toolBlocks[evt.index].argsBuf += d.partial_json;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (evt.type === "content_block_stop" && toolBlocks[evt.index] !== undefined) {
|
|
89
|
+
const tb = toolBlocks[evt.index];
|
|
90
|
+
delete toolBlocks[evt.index];
|
|
91
|
+
let args = {};
|
|
92
|
+
try {
|
|
93
|
+
args = JSON.parse(tb.argsBuf || "{}");
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
args = {};
|
|
97
|
+
}
|
|
98
|
+
yield { type: "tool_call", id: tb.id, name: tb.name, arguments: args };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class CircuitBreaker {
|
|
2
|
+
private readonly openAfter;
|
|
3
|
+
private readonly resetAfter;
|
|
4
|
+
private failures;
|
|
5
|
+
private openedAt;
|
|
6
|
+
constructor(openAfter?: number, resetAfter?: number);
|
|
7
|
+
isOpen(): boolean;
|
|
8
|
+
recordSuccess(): void;
|
|
9
|
+
recordFailure(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function normalizeToolCall(id: string, name: string, args: unknown): {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
arguments: string;
|
|
15
|
+
} | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class CircuitBreaker {
|
|
2
|
+
openAfter;
|
|
3
|
+
resetAfter;
|
|
4
|
+
failures = 0;
|
|
5
|
+
openedAt = null;
|
|
6
|
+
constructor(openAfter = 5, resetAfter = 60_000) {
|
|
7
|
+
this.openAfter = openAfter;
|
|
8
|
+
this.resetAfter = resetAfter;
|
|
9
|
+
}
|
|
10
|
+
isOpen() {
|
|
11
|
+
if (this.openedAt === null)
|
|
12
|
+
return false;
|
|
13
|
+
if (Date.now() - this.openedAt >= this.resetAfter) {
|
|
14
|
+
this.openedAt = null;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
recordSuccess() {
|
|
20
|
+
this.failures = 0;
|
|
21
|
+
this.openedAt = null;
|
|
22
|
+
}
|
|
23
|
+
recordFailure() {
|
|
24
|
+
this.failures++;
|
|
25
|
+
if (this.failures >= this.openAfter)
|
|
26
|
+
this.openedAt = Date.now();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function normalizeToolCall(id, name, args) {
|
|
30
|
+
const n = String(name ?? "").trim();
|
|
31
|
+
if (!n)
|
|
32
|
+
return null;
|
|
33
|
+
let parsed = {};
|
|
34
|
+
if (typeof args === "string") {
|
|
35
|
+
try {
|
|
36
|
+
parsed = JSON.parse(args || "{}");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
parsed = {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (args && typeof args === "object") {
|
|
43
|
+
parsed = args;
|
|
44
|
+
}
|
|
45
|
+
return { id: String(id ?? ""), name: n, arguments: JSON.stringify(parsed) };
|
|
46
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
2
|
+
export declare class OllamaProvider implements LLMProvider {
|
|
3
|
+
private readonly model;
|
|
4
|
+
private readonly baseUrl;
|
|
5
|
+
constructor(model?: string, baseUrl?: string);
|
|
6
|
+
private toOllamaMessages;
|
|
7
|
+
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
8
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { normalizeToolCall } from "./base.js";
|
|
2
|
+
export class OllamaProvider {
|
|
3
|
+
model;
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(model = "llama3", baseUrl = "http://localhost:11434") {
|
|
6
|
+
this.model = model;
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
}
|
|
9
|
+
toOllamaMessages(messages) {
|
|
10
|
+
return messages.map(m => ({ role: m.role, content: m.content }));
|
|
11
|
+
}
|
|
12
|
+
async complete(messages, tools) {
|
|
13
|
+
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
14
|
+
method: "POST",
|
|
15
|
+
headers: { "Content-Type": "application/json" },
|
|
16
|
+
body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: false }),
|
|
17
|
+
});
|
|
18
|
+
if (!resp.ok)
|
|
19
|
+
throw new Error(`Ollama error: ${resp.status}`);
|
|
20
|
+
const data = await resp.json();
|
|
21
|
+
return { role: "assistant", content: data.message.content };
|
|
22
|
+
}
|
|
23
|
+
async *stream(messages, tools, extensions) {
|
|
24
|
+
const resp = await fetch(`${this.baseUrl}/api/chat`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: { "Content-Type": "application/json" },
|
|
27
|
+
body: JSON.stringify({ model: this.model, messages: this.toOllamaMessages(messages), stream: true }),
|
|
28
|
+
});
|
|
29
|
+
if (!resp.ok)
|
|
30
|
+
throw new Error(`Ollama error: ${resp.status}`);
|
|
31
|
+
const reader = resp.body.getReader();
|
|
32
|
+
const decoder = new TextDecoder();
|
|
33
|
+
let buf = "";
|
|
34
|
+
while (true) {
|
|
35
|
+
const { done, value } = await reader.read();
|
|
36
|
+
if (done)
|
|
37
|
+
break;
|
|
38
|
+
buf += decoder.decode(value, { stream: true });
|
|
39
|
+
const lines = buf.split("\n");
|
|
40
|
+
buf = lines.pop() ?? "";
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
if (!line.trim())
|
|
43
|
+
continue;
|
|
44
|
+
try {
|
|
45
|
+
const chunk = JSON.parse(line);
|
|
46
|
+
if (chunk.message?.content)
|
|
47
|
+
yield { type: "text_delta", delta: chunk.message.content };
|
|
48
|
+
for (const tc of chunk.message?.tool_calls ?? []) {
|
|
49
|
+
const norm = normalizeToolCall(crypto.randomUUID(), tc.function.name, tc.function.arguments);
|
|
50
|
+
if (norm)
|
|
51
|
+
yield { type: "tool_call", id: norm.id, name: norm.name, arguments: JSON.parse(norm.arguments) };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch { /* skip malformed lines */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import type { Message, ToolSchema, StreamEvent, LLMProvider } from "../types.js";
|
|
3
|
+
import { CircuitBreaker } from "./base.js";
|
|
4
|
+
export declare class OpenAIProvider implements LLMProvider {
|
|
5
|
+
protected readonly model: string;
|
|
6
|
+
protected client: OpenAI;
|
|
7
|
+
protected circuit: CircuitBreaker;
|
|
8
|
+
protected maxRetries: number;
|
|
9
|
+
protected baseDelay: number;
|
|
10
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
11
|
+
maxRetries: number;
|
|
12
|
+
baseDelay: number;
|
|
13
|
+
}, baseURL?: string);
|
|
14
|
+
protected buildTools(tools: ToolSchema[]): {
|
|
15
|
+
type: "function";
|
|
16
|
+
function: {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
parameters: any;
|
|
20
|
+
};
|
|
21
|
+
}[];
|
|
22
|
+
complete(messages: Message[], tools: ToolSchema[]): Promise<Message>;
|
|
23
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
24
|
+
}
|
|
25
|
+
export declare class QwenProvider extends OpenAIProvider {
|
|
26
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
27
|
+
maxRetries: number;
|
|
28
|
+
baseDelay: number;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export declare class DeepSeekProvider extends OpenAIProvider {
|
|
32
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
33
|
+
maxRetries: number;
|
|
34
|
+
baseDelay: number;
|
|
35
|
+
});
|
|
36
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
37
|
+
}
|
|
38
|
+
export declare class MiniMaxProvider extends OpenAIProvider {
|
|
39
|
+
constructor(apiKey: string, model?: string, retry?: {
|
|
40
|
+
maxRetries: number;
|
|
41
|
+
baseDelay: number;
|
|
42
|
+
});
|
|
43
|
+
stream(messages: Message[], tools: ToolSchema[], extensions?: Record<string, unknown>): AsyncIterable<StreamEvent>;
|
|
44
|
+
}
|