create-bk-agent 0.23.4 → 0.23.6
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 +0 -2
- package/dist/src/add/modules/knowledge.js +18 -18
- package/dist/src/add/modules/server.js +1 -1
- package/dist/src/add/patcher.js +106 -68
- package/dist/src/scaffold.js +29 -16
- package/package.json +2 -2
- package/templates/README.md +26 -0
- package/templates/base/autonomous/package.json +1 -1
- package/templates/base/autonomous/src/index.ts +83 -83
- package/templates/base/coding/package.json +1 -1
- package/templates/base/enterprise/package.json +1 -1
- package/templates/base/minimal/package.json +1 -1
- package/templates/base/minimal/src/index.ts +43 -43
- package/templates/base/multi-agent/package.json +2 -2
- package/templates/base/multi-agent/src/engine.ts +78 -78
- package/templates/base/single/package.json +2 -2
- package/templates/base/single/src/engine.ts +67 -67
- package/templates/autonomous/.env.example +0 -8
- package/templates/autonomous/package.json +0 -25
- package/templates/autonomous/src/index.ts +0 -64
- package/templates/autonomous/tsconfig.json +0 -16
- package/templates/base/multi-agent/src/providers/openai-compatible.ts +0 -104
- package/templates/base/single/src/providers/openai-compatible.ts +0 -104
- package/templates/coding/.env.example +0 -8
- package/templates/coding/package.json +0 -26
- package/templates/coding/src/index.ts +0 -42
- package/templates/coding/tsconfig.json +0 -17
- package/templates/domain-agent/.env.example +0 -7
- package/templates/domain-agent/package.json +0 -27
- package/templates/domain-agent/src/agents/orchestrator.ts +0 -22
- package/templates/domain-agent/src/agents/specialist-a.ts +0 -17
- package/templates/domain-agent/src/agents/specialist-b.ts +0 -16
- package/templates/domain-agent/src/config.ts +0 -19
- package/templates/domain-agent/src/engine.ts +0 -49
- package/templates/domain-agent/src/index.ts +0 -27
- package/templates/domain-agent/src/providers/openai-compatible.ts +0 -104
- package/templates/domain-agent/src/tools/example-tools.ts +0 -28
- package/templates/domain-agent/tsconfig.json +0 -17
- package/templates/mcp-server/package.json +0 -30
- package/templates/mcp-server/src/server.ts +0 -40
- package/templates/mcp-server/src/tools/example.ts +0 -10
- package/templates/mcp-server/tsconfig.json +0 -16
- package/templates/minimal/package.json +0 -25
- package/templates/minimal/src/index.ts +0 -40
- package/templates/minimal/tsconfig.json +0 -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,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-coding-agent",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "Coding assistant agent — edit files, run commands, search code",
|
|
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-coding": "^0.28.0",
|
|
15
|
-
"@backendkit-labs/agent-core": "^0.36.0",
|
|
16
|
-
"dotenv": "^16.0.0"
|
|
17
|
-
},
|
|
18
|
-
"devDependencies": {
|
|
19
|
-
"@types/node": "^20.0.0",
|
|
20
|
-
"ts-node": "^10.9.0",
|
|
21
|
-
"typescript": "^5.4.0"
|
|
22
|
-
},
|
|
23
|
-
"engines": {
|
|
24
|
-
"node": ">=20.0.0"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
import * as readline from 'readline';
|
|
3
|
-
import { createCodingEngine, TerminalTransport } from '@backendkit-labs/agent-coding';
|
|
4
|
-
|
|
5
|
-
// ── Provider setup ────────────────────────────────────────────────────────────
|
|
6
|
-
|
|
7
|
-
const provider = process.env.LLM_PROVIDER ?? 'anthropic';
|
|
8
|
-
const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.LLM_API_KEY ?? '';
|
|
9
|
-
const model = process.env.LLM_MODEL;
|
|
10
|
-
|
|
11
|
-
if (!apiKey) {
|
|
12
|
-
console.error('Missing API key. Set ANTHROPIC_API_KEY or OPENAI_API_KEY in .env');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ── Engine ────────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
const transport = new TerminalTransport();
|
|
19
|
-
|
|
20
|
-
const engine = createCodingEngine({
|
|
21
|
-
providers: { [provider]: { apiKey, ...(model ? { model } : {}) } },
|
|
22
|
-
defaultProvider: provider,
|
|
23
|
-
workingDir: process.cwd(),
|
|
24
|
-
appName: 'my-coding-agent',
|
|
25
|
-
transport,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
// ── REPL ──────────────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
31
|
-
|
|
32
|
-
function prompt() {
|
|
33
|
-
rl.question('\n❯ ', async (input) => {
|
|
34
|
-
const trimmed = input.trim();
|
|
35
|
-
if (!trimmed || trimmed === '/exit') { rl.close(); return; }
|
|
36
|
-
await engine.run(trimmed);
|
|
37
|
-
prompt();
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
console.log('Coding agent ready. Type your request or /exit to quit.\n');
|
|
42
|
-
prompt();
|
|
@@ -1,17 +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
|
-
"resolveJsonModule": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-domain-agent",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "Multi-agent system: orchestrator + domain specialists",
|
|
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.36.0",
|
|
15
|
-
"dotenv": "^16.0.0",
|
|
16
|
-
"openai": "^4.0.0",
|
|
17
|
-
"zod": "^4.6.6"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"@types/node": "^20.0.0",
|
|
21
|
-
"ts-node": "^10.9.0",
|
|
22
|
-
"typescript": "^5.4.0"
|
|
23
|
-
},
|
|
24
|
-
"engines": {
|
|
25
|
-
"node": ">=20.0.0"
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
-
|
|
3
|
-
// The orchestrator receives every user request and delegates to the right specialist.
|
|
4
|
-
// It does NOT call tools directly — it uses ask_agent to route work.
|
|
5
|
-
export const ORCHESTRATOR_PROFILE: AgentProfile = {
|
|
6
|
-
id: 'orchestrator',
|
|
7
|
-
name: 'Orchestrator',
|
|
8
|
-
icon: '🎯',
|
|
9
|
-
description: 'Routes requests to the right specialist agent',
|
|
10
|
-
delegatesTo: ['specialist-a', 'specialist-b'], // TODO: list your specialists
|
|
11
|
-
allowedTools: [],
|
|
12
|
-
systemPrompt: `You are the orchestrator for a TODO: <describe your domain> system.
|
|
13
|
-
|
|
14
|
-
Your ONLY job is to analyze the user request and delegate to the right specialist via ask_agent.
|
|
15
|
-
Do not attempt to handle requests directly.
|
|
16
|
-
|
|
17
|
-
Available specialists:
|
|
18
|
-
- specialist-a: TODO describe what this specialist handles
|
|
19
|
-
- specialist-b: TODO describe what this specialist handles
|
|
20
|
-
|
|
21
|
-
Always delegate. Never answer directly.`,
|
|
22
|
-
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
-
|
|
3
|
-
// TODO: Rename this file and agent to match your domain (e.g. docker-agent.ts, k8s-agent.ts)
|
|
4
|
-
export const SPECIALIST_A_PROFILE: AgentProfile = {
|
|
5
|
-
id: 'specialist-a',
|
|
6
|
-
name: 'Specialist A',
|
|
7
|
-
icon: '🔧',
|
|
8
|
-
description: 'TODO: describe what this specialist does',
|
|
9
|
-
delegatesTo: [],
|
|
10
|
-
allowedTools: [
|
|
11
|
-
'example_action', // TODO: list the tool names this agent can use
|
|
12
|
-
],
|
|
13
|
-
systemPrompt: `You are a specialist in TODO: <your domain area>.
|
|
14
|
-
|
|
15
|
-
Use the available tools to complete the task delegated to you.
|
|
16
|
-
Be precise and report results clearly.`,
|
|
17
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
-
|
|
3
|
-
// TODO: Rename and customize for your second specialist
|
|
4
|
-
export const SPECIALIST_B_PROFILE: AgentProfile = {
|
|
5
|
-
id: 'specialist-b',
|
|
6
|
-
name: 'Specialist B',
|
|
7
|
-
icon: '📊',
|
|
8
|
-
description: 'TODO: describe what this specialist does',
|
|
9
|
-
delegatesTo: [],
|
|
10
|
-
allowedTools: [
|
|
11
|
-
'example_query', // TODO: list the tool names this agent can use
|
|
12
|
-
],
|
|
13
|
-
systemPrompt: `You are a specialist in TODO: <your domain area>.
|
|
14
|
-
|
|
15
|
-
Use the available tools to complete the task delegated to you.`,
|
|
16
|
-
};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import 'dotenv/config';
|
|
2
|
-
|
|
3
|
-
export interface Config {
|
|
4
|
-
llmApiKey: string;
|
|
5
|
-
llmBaseUrl: string;
|
|
6
|
-
llmModel: string;
|
|
7
|
-
// TODO: add your domain-specific config fields
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function loadConfig(): Config {
|
|
11
|
-
const llmApiKey = process.env.LLM_API_KEY ?? '';
|
|
12
|
-
if (!llmApiKey) throw new Error('Missing LLM_API_KEY in environment');
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
llmApiKey,
|
|
16
|
-
llmBaseUrl: process.env.LLM_BASE_URL ?? 'https://api.openai.com/v1',
|
|
17
|
-
llmModel: process.env.LLM_MODEL ?? 'gpt-4o',
|
|
18
|
-
};
|
|
19
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AgentEngine,
|
|
3
|
-
AgentRegistry,
|
|
4
|
-
ToolRegistry,
|
|
5
|
-
ProviderRegistry,
|
|
6
|
-
CallbackTransport,
|
|
7
|
-
} from '@backendkit-labs/agent-core';
|
|
8
|
-
import { OpenAICompatibleProvider } from './providers/openai-compatible';
|
|
9
|
-
import { loadConfig } from './config';
|
|
10
|
-
import { ORCHESTRATOR_PROFILE } from './agents/orchestrator';
|
|
11
|
-
import { SPECIALIST_A_PROFILE } from './agents/specialist-a';
|
|
12
|
-
import { SPECIALIST_B_PROFILE } from './agents/specialist-b';
|
|
13
|
-
import { exampleAction, exampleQuery } from './tools/example-tools';
|
|
14
|
-
|
|
15
|
-
export function createDomainEngine(transport: CallbackTransport): AgentEngine {
|
|
16
|
-
const config = loadConfig();
|
|
17
|
-
|
|
18
|
-
const tools = new ToolRegistry();
|
|
19
|
-
tools
|
|
20
|
-
.register(exampleAction)
|
|
21
|
-
.register(exampleQuery);
|
|
22
|
-
// TODO: register more tools
|
|
23
|
-
|
|
24
|
-
const agents = new AgentRegistry();
|
|
25
|
-
agents
|
|
26
|
-
.register(ORCHESTRATOR_PROFILE)
|
|
27
|
-
.register(SPECIALIST_A_PROFILE)
|
|
28
|
-
.register(SPECIALIST_B_PROFILE);
|
|
29
|
-
// TODO: register more specialists
|
|
30
|
-
|
|
31
|
-
const providers = new ProviderRegistry();
|
|
32
|
-
providers.register('default', new OpenAICompatibleProvider({
|
|
33
|
-
apiKey: config.llmApiKey,
|
|
34
|
-
baseUrl: config.llmBaseUrl,
|
|
35
|
-
model: config.llmModel,
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
return new AgentEngine({
|
|
39
|
-
model: { provider: 'default', id: config.llmModel },
|
|
40
|
-
agents,
|
|
41
|
-
tools,
|
|
42
|
-
providers,
|
|
43
|
-
defaultProvider: 'default',
|
|
44
|
-
defaultAgentId: 'orchestrator',
|
|
45
|
-
transport,
|
|
46
|
-
maxIterations: 30,
|
|
47
|
-
iterationMode: 'auto',
|
|
48
|
-
});
|
|
49
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as readline from 'readline';
|
|
2
|
-
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
3
|
-
import { createDomainEngine } from './engine';
|
|
4
|
-
|
|
5
|
-
const transport = new CallbackTransport((event) => {
|
|
6
|
-
if (event.type === 'token') process.stdout.write(event.content);
|
|
7
|
-
if (event.type === 'tool_call') console.log(`\n⚙ ${event.name}`);
|
|
8
|
-
if (event.type === 'agent_switch') console.log(`\n→ ${event.to_name ?? event.to}`);
|
|
9
|
-
if (event.type === 'block_end') console.log();
|
|
10
|
-
if (event.type === 'error') console.error(`\nError: ${event.message}`);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const engine = createDomainEngine(transport);
|
|
14
|
-
|
|
15
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
16
|
-
|
|
17
|
-
function prompt() {
|
|
18
|
-
rl.question('\n> ', async (input) => {
|
|
19
|
-
const trimmed = input.trim();
|
|
20
|
-
if (!trimmed || trimmed === '/exit') { rl.close(); return; }
|
|
21
|
-
await engine.run(trimmed);
|
|
22
|
-
prompt();
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
console.log('Domain agent ready. Type your request or /exit to quit.\n');
|
|
27
|
-
prompt();
|
|
@@ -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,28 +0,0 @@
|
|
|
1
|
-
import { defineTool, z } from '@backendkit-labs/agent-core';
|
|
2
|
-
|
|
3
|
-
// TODO: Replace these with tools that call your actual domain SDK/API
|
|
4
|
-
|
|
5
|
-
export const exampleAction = defineTool({
|
|
6
|
-
name: 'example_action',
|
|
7
|
-
description: 'TODO: describe what this tool does',
|
|
8
|
-
input: z.object({
|
|
9
|
-
target: z.string().describe('Target to act on'),
|
|
10
|
-
// TODO: add more parameters
|
|
11
|
-
}),
|
|
12
|
-
execute: async ({ target }) => {
|
|
13
|
-
// TODO: implement the actual action
|
|
14
|
-
return `Action executed on: ${target}`;
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export const exampleQuery = defineTool({
|
|
19
|
-
name: 'example_query',
|
|
20
|
-
description: 'TODO: describe what this query returns',
|
|
21
|
-
input: z.object({
|
|
22
|
-
filter: z.string().optional().describe('Optional filter'),
|
|
23
|
-
}),
|
|
24
|
-
execute: async ({ filter }) => {
|
|
25
|
-
// TODO: implement the actual query
|
|
26
|
-
return JSON.stringify({ results: [], filter });
|
|
27
|
-
},
|
|
28
|
-
});
|
|
@@ -1,17 +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
|
-
"resolveJsonModule": true
|
|
14
|
-
},
|
|
15
|
-
"include": ["src/**/*"],
|
|
16
|
-
"exclude": ["node_modules", "dist"]
|
|
17
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "my-mcp-server",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"description": "MCP server — expose agent tools via Model Context Protocol",
|
|
6
|
-
"main": "dist/server.js",
|
|
7
|
-
"bin": {
|
|
8
|
-
"my-mcp-server": "./dist/server.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"dev": "ts-node src/server.ts",
|
|
12
|
-
"build": "tsc",
|
|
13
|
-
"start": "node dist/server.js",
|
|
14
|
-
"typecheck": "tsc --noEmit"
|
|
15
|
-
},
|
|
16
|
-
"dependencies": {
|
|
17
|
-
"@backendkit-labs/agent-core": "^0.36.0",
|
|
18
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
19
|
-
"dotenv": "^16.0.0",
|
|
20
|
-
"zod": "^4.6.6"
|
|
21
|
-
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@types/node": "^20.0.0",
|
|
24
|
-
"ts-node": "^10.9.0",
|
|
25
|
-
"typescript": "^5.4.0"
|
|
26
|
-
},
|
|
27
|
-
"engines": {
|
|
28
|
-
"node": ">=20.0.0"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
import { exampleHandler, exampleInputSchema } from './tools/example';
|
|
7
|
-
|
|
8
|
-
// ── MCP Server ────────────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const server = new McpServer({
|
|
11
|
-
name: 'my-mcp-server',
|
|
12
|
-
version: '0.1.0',
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
// ── Register tools ────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
// @ts-expect-error: MCP SDK + zod generic depth limit, safe at runtime
|
|
18
|
-
server.tool(
|
|
19
|
-
'example_query',
|
|
20
|
-
'TODO: describe what this tool does',
|
|
21
|
-
exampleInputSchema.shape,
|
|
22
|
-
async (args: z.infer<typeof exampleInputSchema>) => ({
|
|
23
|
-
content: [{ type: 'text' as const, text: await exampleHandler(args) }],
|
|
24
|
-
}),
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
// TODO: register more tools following the same pattern:
|
|
28
|
-
// server.tool('tool_name', 'description', schema.shape, handler)
|
|
29
|
-
|
|
30
|
-
// ── Transport: stdio (for Claude Desktop / Cursor / VS Code) ─────────────────
|
|
31
|
-
|
|
32
|
-
const transport = new StdioServerTransport();
|
|
33
|
-
await server.connect(transport);
|
|
34
|
-
|
|
35
|
-
// To use this server, add it to your MCP client config:
|
|
36
|
-
// {
|
|
37
|
-
// "mcpServers": {
|
|
38
|
-
// "my-server": { "command": "npx", "args": ["my-mcp-server"] }
|
|
39
|
-
// }
|
|
40
|
-
// }
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
|
-
export const exampleInputSchema = z.object({
|
|
4
|
-
query: z.string().describe('The query to process'),
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
export async function exampleHandler({ query }: z.infer<typeof exampleInputSchema>): Promise<string> {
|
|
8
|
-
// TODO: implement your tool logic
|
|
9
|
-
return `Result for: ${query}`;
|
|
10
|
-
}
|
|
@@ -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
|
-
}
|