create-bk-agent 0.23.3 → 0.23.5

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.
Files changed (42) hide show
  1. package/dist/src/add/patcher.js +106 -68
  2. package/dist/src/scaffold.js +28 -15
  3. package/package.json +2 -2
  4. package/templates/base/autonomous/package.json +1 -1
  5. package/templates/base/autonomous/src/index.ts +83 -83
  6. package/templates/base/coding/package.json +1 -1
  7. package/templates/base/enterprise/package.json +1 -1
  8. package/templates/base/minimal/package.json +1 -1
  9. package/templates/base/minimal/src/index.ts +43 -43
  10. package/templates/base/multi-agent/package.json +2 -2
  11. package/templates/base/multi-agent/src/engine.ts +78 -78
  12. package/templates/base/orchestrator/_common/package.json +1 -1
  13. package/templates/base/single/package.json +2 -2
  14. package/templates/base/single/src/engine.ts +67 -67
  15. package/templates/autonomous/.env.example +0 -8
  16. package/templates/autonomous/package.json +0 -25
  17. package/templates/autonomous/src/index.ts +0 -64
  18. package/templates/autonomous/tsconfig.json +0 -16
  19. package/templates/base/multi-agent/src/providers/openai-compatible.ts +0 -104
  20. package/templates/base/single/src/providers/openai-compatible.ts +0 -104
  21. package/templates/coding/.env.example +0 -8
  22. package/templates/coding/package.json +0 -26
  23. package/templates/coding/src/index.ts +0 -42
  24. package/templates/coding/tsconfig.json +0 -17
  25. package/templates/domain-agent/.env.example +0 -7
  26. package/templates/domain-agent/package.json +0 -27
  27. package/templates/domain-agent/src/agents/orchestrator.ts +0 -22
  28. package/templates/domain-agent/src/agents/specialist-a.ts +0 -17
  29. package/templates/domain-agent/src/agents/specialist-b.ts +0 -16
  30. package/templates/domain-agent/src/config.ts +0 -19
  31. package/templates/domain-agent/src/engine.ts +0 -49
  32. package/templates/domain-agent/src/index.ts +0 -27
  33. package/templates/domain-agent/src/providers/openai-compatible.ts +0 -104
  34. package/templates/domain-agent/src/tools/example-tools.ts +0 -28
  35. package/templates/domain-agent/tsconfig.json +0 -17
  36. package/templates/mcp-server/package.json +0 -30
  37. package/templates/mcp-server/src/server.ts +0 -40
  38. package/templates/mcp-server/src/tools/example.ts +0 -10
  39. package/templates/mcp-server/tsconfig.json +0 -16
  40. package/templates/minimal/package.json +0 -25
  41. package/templates/minimal/src/index.ts +0 -40
  42. package/templates/minimal/tsconfig.json +0 -16
@@ -1,78 +1,78 @@
1
- import * as os from 'os';
2
- import * as path from 'path';
3
- import {
4
- AgentEngine,
5
- AgentRegistry,
6
- ToolRegistry,
7
- ProviderRegistry,
8
- CallbackTransport,
9
- createMemorySystem,
10
- } from '@backendkit-labs/agent-core';
11
- import { OpenAICompatibleProvider } from './providers/openai-compatible';
12
- import { loadConfig } from './config';
13
- import { ORCHESTRATOR_PROFILE } from './agents/orchestrator';
14
- import { SPECIALIST_A_PROFILE } from './agents/specialist-a';
15
- import { SPECIALIST_B_PROFILE } from './agents/specialist-b';
16
- import { exampleAction, exampleQuery } from './tools/example-tools';
17
-
18
- // ── Persistent storage paths ──────────────────────────────────────────────────
19
- // All state lives in ~/.my-agent/ so it survives restarts.
20
-
21
- const APP_DIR = path.join(os.homedir(), '.my-agent');
22
- const HISTORY = path.join(APP_DIR, 'history.json');
23
- const MEM_DIR = path.join(APP_DIR, 'memory');
24
-
25
- // ── Memory system ─────────────────────────────────────────────────────────────
26
- // Three-tier: episodic (past experiences) + semantic (knowledge) + procedural (patterns).
27
- // buildContextBlock() auto-injects relevant memories into each agent turn.
28
-
29
- const memory = createMemorySystem({
30
- episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
31
- semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
32
- procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
33
- retention: { maxEpisodes: 500 },
34
- });
35
-
36
- export function createEngine(transport: CallbackTransport): AgentEngine {
37
- const config = loadConfig();
38
-
39
- const tools = new ToolRegistry();
40
- tools
41
- .register(exampleAction)
42
- .register(exampleQuery);
43
- // Register memory tools so agents can explicitly save/recall memories
44
- for (const t of memory.getTools()) tools.register(t);
45
- // TODO: register more tools
46
-
47
- const agents = new AgentRegistry();
48
- agents
49
- .register(ORCHESTRATOR_PROFILE)
50
- .register(SPECIALIST_A_PROFILE)
51
- .register(SPECIALIST_B_PROFILE);
52
- // TODO: register more specialists
53
-
54
- const providers = new ProviderRegistry();
55
- providers.register('default', new OpenAICompatibleProvider({
56
- apiKey: config.llmApiKey,
57
- baseUrl: config.llmBaseUrl,
58
- model: config.llmModel,
59
- }));
60
-
61
- return new AgentEngine({
62
- model: { provider: 'default', id: config.llmModel },
63
- agents,
64
- tools,
65
- providers,
66
- defaultProvider: 'default',
67
- defaultAgentId: 'orchestrator',
68
- transport,
69
- maxIterations: 30,
70
- iterationMode: 'auto',
71
- // Conversation history — persists between runs
72
- historyPath: HISTORY,
73
- maxContextMessages: 100,
74
- // Long-term memory — injects relevant context each turn
75
- memorySystem: memory,
76
- additionalRoots: [APP_DIR],
77
- });
78
- }
1
+ import * as os from 'os';
2
+ import * as path from 'path';
3
+ import {
4
+ AgentEngine,
5
+ AgentRegistry,
6
+ ToolRegistry,
7
+ ProviderRegistry,
8
+ CallbackTransport,
9
+ createMemorySystem,
10
+ } from '@backendkit-labs/agent-core';
11
+ import { OpenAICompatibleProvider } from '@backendkit-labs/agent-providers';
12
+ import { loadConfig } from './config';
13
+ import { ORCHESTRATOR_PROFILE } from './agents/orchestrator';
14
+ import { SPECIALIST_A_PROFILE } from './agents/specialist-a';
15
+ import { SPECIALIST_B_PROFILE } from './agents/specialist-b';
16
+ import { exampleAction, exampleQuery } from './tools/example-tools';
17
+
18
+ // ── Persistent storage paths ──────────────────────────────────────────────────
19
+ // All state lives in ~/.my-agent/ so it survives restarts.
20
+
21
+ const APP_DIR = path.join(os.homedir(), '.my-agent');
22
+ const HISTORY = path.join(APP_DIR, 'history.json');
23
+ const MEM_DIR = path.join(APP_DIR, 'memory');
24
+
25
+ // ── Memory system ─────────────────────────────────────────────────────────────
26
+ // Three-tier: episodic (past experiences) + semantic (knowledge) + procedural (patterns).
27
+ // buildContextBlock() auto-injects relevant memories into each agent turn.
28
+
29
+ const memory = createMemorySystem({
30
+ episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
31
+ semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
32
+ procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
33
+ retention: { maxEpisodes: 500 },
34
+ });
35
+
36
+ export function createEngine(transport: CallbackTransport): AgentEngine {
37
+ const config = loadConfig();
38
+
39
+ const tools = new ToolRegistry();
40
+ tools
41
+ .register(exampleAction)
42
+ .register(exampleQuery);
43
+ // Register memory tools so agents can explicitly save/recall memories
44
+ for (const t of memory.getTools()) tools.register(t);
45
+ // TODO: register more tools
46
+
47
+ const agents = new AgentRegistry();
48
+ agents
49
+ .register(ORCHESTRATOR_PROFILE)
50
+ .register(SPECIALIST_A_PROFILE)
51
+ .register(SPECIALIST_B_PROFILE);
52
+ // TODO: register more specialists
53
+
54
+ const providers = new ProviderRegistry();
55
+ providers.register('default', new OpenAICompatibleProvider({
56
+ apiKey: config.llmApiKey,
57
+ baseUrl: config.llmBaseUrl,
58
+ model: config.llmModel,
59
+ }));
60
+
61
+ return new AgentEngine({
62
+ model: { provider: 'default', id: config.llmModel },
63
+ agents,
64
+ tools,
65
+ providers,
66
+ defaultProvider: 'default',
67
+ defaultAgentId: 'orchestrator',
68
+ transport,
69
+ maxIterations: 30,
70
+ iterationMode: 'auto',
71
+ // Conversation history — persists between runs
72
+ historyPath: HISTORY,
73
+ maxContextMessages: 100,
74
+ // Long-term memory — injects relevant context each turn
75
+ memorySystem: memory,
76
+ additionalRoots: [APP_DIR],
77
+ });
78
+ }
@@ -8,7 +8,7 @@
8
8
  "test:flow": "node test-flow.mjs"
9
9
  },
10
10
  "dependencies": {
11
- "@backendkit-labs/orchestrator-agent": "^0.6.0"
11
+ "@backendkit-labs/orchestrator-agent": "^0.7.0"
12
12
  },
13
13
  "engines": {
14
14
  "node": ">=20.0.0"
@@ -12,9 +12,9 @@
12
12
  "typecheck": "tsc --noEmit"
13
13
  },
14
14
  "dependencies": {
15
- "@backendkit-labs/agent-core": "^0.35.0",
15
+ "@backendkit-labs/agent-core": "^0.37.0",
16
+ "@backendkit-labs/agent-providers": "^0.3.3",
16
17
  "dotenv": "^16.0.0",
17
- "openai": "^4.0.0",
18
18
  "zod": "^4.6.6"
19
19
  },
20
20
  "devDependencies": {
@@ -1,67 +1,67 @@
1
- import * as os from 'os';
2
- import * as path from 'path';
3
- import {
4
- AgentEngine,
5
- AgentRegistry,
6
- ToolRegistry,
7
- ProviderRegistry,
8
- CallbackTransport,
9
- createMemorySystem,
10
- } from '@backendkit-labs/agent-core';
11
- import { OpenAICompatibleProvider } from './providers/openai-compatible';
12
- import { loadConfig } from './config';
13
- import { MY_AGENT_PROFILE } from './agents/my-agent';
14
- import { exampleAction, exampleQuery } from './tools/example-tools';
15
-
16
- // ── Persistent storage paths ──────────────────────────────────────────────────
17
-
18
- const APP_DIR = path.join(os.homedir(), '.my-agent');
19
- const HISTORY = path.join(APP_DIR, 'history.json');
20
- const MEM_DIR = path.join(APP_DIR, 'memory');
21
-
22
- // ── Memory system ─────────────────────────────────────────────────────────────
23
-
24
- const memory = createMemorySystem({
25
- episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
26
- semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
27
- procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
28
- retention: { maxEpisodes: 500 },
29
- });
30
-
31
- export function createEngine(transport: CallbackTransport): AgentEngine {
32
- const config = loadConfig();
33
-
34
- const tools = new ToolRegistry();
35
- tools
36
- .register(exampleAction)
37
- .register(exampleQuery);
38
- for (const t of memory.getTools()) tools.register(t);
39
- // TODO: register more tools
40
-
41
- const agents = new AgentRegistry();
42
- agents.register(MY_AGENT_PROFILE);
43
- // TODO: register more specialists
44
-
45
- const providers = new ProviderRegistry();
46
- providers.register('default', new OpenAICompatibleProvider({
47
- apiKey: config.llmApiKey,
48
- baseUrl: config.llmBaseUrl,
49
- model: config.llmModel,
50
- }));
51
-
52
- return new AgentEngine({
53
- model: { provider: 'default', id: config.llmModel },
54
- agents,
55
- tools,
56
- providers,
57
- defaultProvider: 'default',
58
- defaultAgentId: 'my-agent',
59
- transport,
60
- maxIterations: 20,
61
- iterationMode: 'auto',
62
- historyPath: HISTORY,
63
- maxContextMessages: 100,
64
- memorySystem: memory,
65
- additionalRoots: [APP_DIR],
66
- });
67
- }
1
+ import * as os from 'os';
2
+ import * as path from 'path';
3
+ import {
4
+ AgentEngine,
5
+ AgentRegistry,
6
+ ToolRegistry,
7
+ ProviderRegistry,
8
+ CallbackTransport,
9
+ createMemorySystem,
10
+ } from '@backendkit-labs/agent-core';
11
+ import { OpenAICompatibleProvider } from '@backendkit-labs/agent-providers';
12
+ import { loadConfig } from './config';
13
+ import { MY_AGENT_PROFILE } from './agents/my-agent';
14
+ import { exampleAction, exampleQuery } from './tools/example-tools';
15
+
16
+ // ── Persistent storage paths ──────────────────────────────────────────────────
17
+
18
+ const APP_DIR = path.join(os.homedir(), '.my-agent');
19
+ const HISTORY = path.join(APP_DIR, 'history.json');
20
+ const MEM_DIR = path.join(APP_DIR, 'memory');
21
+
22
+ // ── Memory system ─────────────────────────────────────────────────────────────
23
+
24
+ const memory = createMemorySystem({
25
+ episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
26
+ semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
27
+ procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
28
+ retention: { maxEpisodes: 500 },
29
+ });
30
+
31
+ export function createEngine(transport: CallbackTransport): AgentEngine {
32
+ const config = loadConfig();
33
+
34
+ const tools = new ToolRegistry();
35
+ tools
36
+ .register(exampleAction)
37
+ .register(exampleQuery);
38
+ for (const t of memory.getTools()) tools.register(t);
39
+ // TODO: register more tools
40
+
41
+ const agents = new AgentRegistry();
42
+ agents.register(MY_AGENT_PROFILE);
43
+ // TODO: register more specialists
44
+
45
+ const providers = new ProviderRegistry();
46
+ providers.register('default', new OpenAICompatibleProvider({
47
+ apiKey: config.llmApiKey,
48
+ baseUrl: config.llmBaseUrl,
49
+ model: config.llmModel,
50
+ }));
51
+
52
+ return new AgentEngine({
53
+ model: { provider: 'default', id: config.llmModel },
54
+ agents,
55
+ tools,
56
+ providers,
57
+ defaultProvider: 'default',
58
+ defaultAgentId: 'my-agent',
59
+ transport,
60
+ maxIterations: 20,
61
+ iterationMode: 'auto',
62
+ historyPath: HISTORY,
63
+ maxContextMessages: 100,
64
+ memorySystem: memory,
65
+ additionalRoots: [APP_DIR],
66
+ });
67
+ }
@@ -1,8 +0,0 @@
1
- ANTHROPIC_API_KEY=sk-ant-...
2
- # OPENAI_API_KEY=sk-...
3
-
4
- LLM_PROVIDER=anthropic
5
- LLM_MODEL=claude-sonnet-4-6
6
-
7
- WEBHOOK_PORT=3000
8
- # WEBHOOK_SECRET=your-secret # for GitHub/Slack webhook validation
@@ -1,25 +0,0 @@
1
- {
2
- "name": "my-autonomous-agent",
3
- "version": "0.1.0",
4
- "private": true,
5
- "description": "Autonomous agent — activates on cron, webhooks, or custom events",
6
- "main": "dist/index.js",
7
- "scripts": {
8
- "dev": "ts-node src/index.ts",
9
- "build": "tsc",
10
- "start": "node dist/index.js",
11
- "typecheck": "tsc --noEmit"
12
- },
13
- "dependencies": {
14
- "@backendkit-labs/agent-core": "^0.35.0",
15
- "dotenv": "^16.0.0"
16
- },
17
- "devDependencies": {
18
- "@types/node": "^20.0.0",
19
- "ts-node": "^10.9.0",
20
- "typescript": "^5.4.0"
21
- },
22
- "engines": {
23
- "node": ">=20.0.0"
24
- }
25
- }
@@ -1,64 +0,0 @@
1
- import 'dotenv/config';
2
- import { createAutonomousAgent } from '@backendkit-labs/agent-core';
3
- import type { AgentProfile } from '@backendkit-labs/agent-core';
4
-
5
- // ── Agent definition ──────────────────────────────────────────────────────────
6
-
7
- const monitorAgent: AgentProfile = {
8
- id: 'monitor',
9
- name: 'Monitor',
10
- icon: '👁',
11
- description: 'Monitors events and executes scheduled tasks',
12
- allowedTools: [],
13
- delegatesTo: [],
14
- systemPrompt: `You are an autonomous monitor agent.
15
- // TODO: describe what this agent monitors and what actions it takes.
16
- When activated by a trigger, analyze the input and take appropriate action.`,
17
- };
18
-
19
- // ── Provider ──────────────────────────────────────────────────────────────────
20
-
21
- const provider = process.env.LLM_PROVIDER ?? 'anthropic';
22
- const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY ?? '';
23
-
24
- if (!apiKey) { console.error('Missing API key'); process.exit(1); }
25
-
26
- // ── Autonomous agent ──────────────────────────────────────────────────────────
27
-
28
- const agent = createAutonomousAgent({
29
- providers: { [provider]: { apiKey } },
30
- defaultProvider: provider,
31
- agents: [monitorAgent],
32
- defaultAgent: 'monitor',
33
- goal: 'TODO: describe the agent goal',
34
-
35
- triggers: [
36
- // Run every day at 9:00 AM
37
- {
38
- type: 'schedule',
39
- cron: '0 9 * * *',
40
- task: 'Run daily check and report findings',
41
- },
42
-
43
- // Listen for webhook events (e.g. from GitHub, Slack, etc.)
44
- {
45
- type: 'webhook',
46
- path: '/events',
47
- // secret: process.env.WEBHOOK_SECRET, // uncomment to validate signatures
48
- },
49
-
50
- // TODO: add more triggers as needed
51
- // { type: 'event', topic: 'my.custom.event' },
52
- ],
53
-
54
- webhookPort: parseInt(process.env.WEBHOOK_PORT ?? '3000'),
55
- });
56
-
57
- // ── Start ─────────────────────────────────────────────────────────────────────
58
-
59
- await agent.start();
60
- console.log('Autonomous agent started.');
61
- console.log('Webhook listening on port', process.env.WEBHOOK_PORT ?? 3000);
62
-
63
- process.on('SIGTERM', async () => { await agent.stop(); process.exit(0); });
64
- process.on('SIGINT', async () => { await agent.stop(); process.exit(0); });
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "commonjs",
5
- "moduleResolution": "node16",
6
- "lib": ["ES2022"],
7
- "types": ["node"],
8
- "outDir": "dist",
9
- "rootDir": "src",
10
- "strict": true,
11
- "skipLibCheck": true,
12
- "esModuleInterop": true
13
- },
14
- "include": ["src/**/*"],
15
- "exclude": ["node_modules", "dist"]
16
- }
@@ -1,104 +0,0 @@
1
- import OpenAI from 'openai';
2
- import type { LLMProvider, LLMMessage, LLMStreamCallbacks, ToolDefinition } from '@backendkit-labs/agent-core';
3
-
4
- export interface OpenAICompatibleProviderOptions {
5
- apiKey: string;
6
- baseUrl: string;
7
- model: string;
8
- maxRetries?: number;
9
- }
10
-
11
- function isRetryable(err: unknown): boolean {
12
- if (err && typeof err === 'object' && 'status' in err) {
13
- const s = (err as { status: number }).status;
14
- return s === 429 || s >= 500;
15
- }
16
- return false;
17
- }
18
-
19
- const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms));
20
-
21
- export class OpenAICompatibleProvider implements LLMProvider {
22
- private readonly client: OpenAI;
23
- private readonly model: string;
24
- private readonly maxRetries: number;
25
-
26
- constructor(opts: OpenAICompatibleProviderOptions) {
27
- this.client = new OpenAI({ apiKey: opts.apiKey, baseURL: opts.baseUrl, maxRetries: 0 });
28
- this.model = opts.model;
29
- this.maxRetries = opts.maxRetries ?? 3;
30
- }
31
-
32
- async chat(messages: LLMMessage[], tools: ToolDefinition[], callbacks: LLMStreamCallbacks): Promise<void> {
33
- const oaiTools = tools.map(t => ({
34
- type: 'function' as const,
35
- function: { name: t.name, description: t.description, parameters: t.parameters },
36
- })) as OpenAI.Chat.ChatCompletionTool[];
37
-
38
- let attempt = 0;
39
- let emitted = false;
40
-
41
- while (attempt < this.maxRetries) {
42
- attempt++;
43
- try {
44
- await this.stream(messages, oaiTools, {
45
- ...callbacks,
46
- onChunk: (d) => { emitted = true; callbacks.onChunk?.(d); },
47
- });
48
- return;
49
- } catch (err) {
50
- if (emitted || !isRetryable(err) || attempt >= this.maxRetries) {
51
- callbacks.onError(err instanceof Error ? err : new Error(String(err)));
52
- return;
53
- }
54
- await sleep(Math.min(1000 * 2 ** attempt, 16_000));
55
- }
56
- }
57
- }
58
-
59
- private async stream(
60
- messages: LLMMessage[],
61
- oaiTools: OpenAI.Chat.ChatCompletionTool[],
62
- callbacks: LLMStreamCallbacks,
63
- ): Promise<void> {
64
- const stream = await this.client.chat.completions.create({
65
- model: this.model,
66
- messages: messages as OpenAI.Chat.ChatCompletionMessageParam[],
67
- tools: oaiTools.length ? oaiTools : undefined,
68
- tool_choice: oaiTools.length ? 'auto' : undefined,
69
- stream: true,
70
- stream_options: { include_usage: true },
71
- });
72
-
73
- let content = '';
74
- const toolBuffers = new Map<number, { id: string; name: string; args: string }>();
75
-
76
- for await (const chunk of stream) {
77
- if (chunk.usage) callbacks.onMetrics?.(chunk.usage.prompt_tokens, chunk.usage.completion_tokens);
78
- const delta = chunk.choices[0]?.delta;
79
- if (!delta) continue;
80
- if (delta.content) { content += delta.content; callbacks.onChunk?.(delta.content); }
81
- if (delta.tool_calls) {
82
- for (const tc of delta.tool_calls) {
83
- if (!toolBuffers.has(tc.index)) toolBuffers.set(tc.index, { id: '', name: '', args: '' });
84
- const buf = toolBuffers.get(tc.index)!;
85
- if (tc.id) buf.id = tc.id;
86
- if (tc.function?.name) buf.name += tc.function.name;
87
- if (tc.function?.arguments) buf.args += tc.function.arguments;
88
- }
89
- }
90
- }
91
-
92
- const toolCalls = [...toolBuffers.entries()]
93
- .sort(([a], [b]) => a - b)
94
- .map(([, b]) => ({ id: b.id, type: 'function' as const, function: { name: b.name, arguments: b.args } }));
95
-
96
- for (const tc of toolCalls) callbacks.onToolCall?.(tc.function.name, tc.function.arguments, tc.id);
97
-
98
- callbacks.onDone({
99
- role: 'assistant',
100
- content: content || null,
101
- tool_calls: toolCalls.length ? toolCalls : undefined,
102
- });
103
- }
104
- }
@@ -1,104 +0,0 @@
1
- import OpenAI from 'openai';
2
- import type { LLMProvider, LLMMessage, LLMStreamCallbacks, ToolDefinition } from '@backendkit-labs/agent-core';
3
-
4
- export interface OpenAICompatibleProviderOptions {
5
- apiKey: string;
6
- baseUrl: string;
7
- model: string;
8
- maxRetries?: number;
9
- }
10
-
11
- function isRetryable(err: unknown): boolean {
12
- if (err && typeof err === 'object' && 'status' in err) {
13
- const s = (err as { status: number }).status;
14
- return s === 429 || s >= 500;
15
- }
16
- return false;
17
- }
18
-
19
- const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms));
20
-
21
- export class OpenAICompatibleProvider implements LLMProvider {
22
- private readonly client: OpenAI;
23
- private readonly model: string;
24
- private readonly maxRetries: number;
25
-
26
- constructor(opts: OpenAICompatibleProviderOptions) {
27
- this.client = new OpenAI({ apiKey: opts.apiKey, baseURL: opts.baseUrl, maxRetries: 0 });
28
- this.model = opts.model;
29
- this.maxRetries = opts.maxRetries ?? 3;
30
- }
31
-
32
- async chat(messages: LLMMessage[], tools: ToolDefinition[], callbacks: LLMStreamCallbacks): Promise<void> {
33
- const oaiTools = tools.map(t => ({
34
- type: 'function' as const,
35
- function: { name: t.name, description: t.description, parameters: t.parameters },
36
- })) as OpenAI.Chat.ChatCompletionTool[];
37
-
38
- let attempt = 0;
39
- let emitted = false;
40
-
41
- while (attempt < this.maxRetries) {
42
- attempt++;
43
- try {
44
- await this.stream(messages, oaiTools, {
45
- ...callbacks,
46
- onChunk: (d) => { emitted = true; callbacks.onChunk?.(d); },
47
- });
48
- return;
49
- } catch (err) {
50
- if (emitted || !isRetryable(err) || attempt >= this.maxRetries) {
51
- callbacks.onError(err instanceof Error ? err : new Error(String(err)));
52
- return;
53
- }
54
- await sleep(Math.min(1000 * 2 ** attempt, 16_000));
55
- }
56
- }
57
- }
58
-
59
- private async stream(
60
- messages: LLMMessage[],
61
- oaiTools: OpenAI.Chat.ChatCompletionTool[],
62
- callbacks: LLMStreamCallbacks,
63
- ): Promise<void> {
64
- const stream = await this.client.chat.completions.create({
65
- model: this.model,
66
- messages: messages as OpenAI.Chat.ChatCompletionMessageParam[],
67
- tools: oaiTools.length ? oaiTools : undefined,
68
- tool_choice: oaiTools.length ? 'auto' : undefined,
69
- stream: true,
70
- stream_options: { include_usage: true },
71
- });
72
-
73
- let content = '';
74
- const toolBuffers = new Map<number, { id: string; name: string; args: string }>();
75
-
76
- for await (const chunk of stream) {
77
- if (chunk.usage) callbacks.onMetrics?.(chunk.usage.prompt_tokens, chunk.usage.completion_tokens);
78
- const delta = chunk.choices[0]?.delta;
79
- if (!delta) continue;
80
- if (delta.content) { content += delta.content; callbacks.onChunk?.(delta.content); }
81
- if (delta.tool_calls) {
82
- for (const tc of delta.tool_calls) {
83
- if (!toolBuffers.has(tc.index)) toolBuffers.set(tc.index, { id: '', name: '', args: '' });
84
- const buf = toolBuffers.get(tc.index)!;
85
- if (tc.id) buf.id = tc.id;
86
- if (tc.function?.name) buf.name += tc.function.name;
87
- if (tc.function?.arguments) buf.args += tc.function.arguments;
88
- }
89
- }
90
- }
91
-
92
- const toolCalls = [...toolBuffers.entries()]
93
- .sort(([a], [b]) => a - b)
94
- .map(([, b]) => ({ id: b.id, type: 'function' as const, function: { name: b.name, arguments: b.args } }));
95
-
96
- for (const tc of toolCalls) callbacks.onToolCall?.(tc.function.name, tc.function.arguments, tc.id);
97
-
98
- callbacks.onDone({
99
- role: 'assistant',
100
- content: content || null,
101
- tool_calls: toolCalls.length ? toolCalls : undefined,
102
- });
103
- }
104
- }
@@ -1,8 +0,0 @@
1
- # LLM provider — pick one
2
- ANTHROPIC_API_KEY=sk-ant-...
3
- # OPENAI_API_KEY=sk-...
4
- # DEEPSEEK_API_KEY=...
5
-
6
- # Provider to use: anthropic | openai | deepseek
7
- LLM_PROVIDER=anthropic
8
- LLM_MODEL=claude-sonnet-4-6