create-bk-agent 0.17.0 → 0.17.2
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/dist/src/add/modules/server.js +1 -1
- package/dist/src/scaffold.js +5 -0
- package/package.json +1 -1
- package/templates/base/autonomous/.env.example +4 -0
- package/templates/base/autonomous/package.json +23 -0
- package/templates/base/autonomous/src/index.ts +83 -0
- package/templates/base/autonomous/tsconfig.json +17 -0
- package/templates/base/coding/.env.example +4 -0
- package/templates/base/coding/AGENT.md +13 -0
- package/templates/base/coding/package.json +24 -0
- package/templates/base/coding/src/index.ts +42 -0
- package/templates/base/coding/tsconfig.json +17 -0
- package/templates/base/minimal/.env.example +1 -0
- package/templates/base/minimal/package.json +23 -0
- package/templates/base/minimal/src/index.ts +43 -0
- package/templates/base/minimal/tsconfig.json +17 -0
- package/templates/base/multi-agent/.env.example +7 -0
- package/templates/base/multi-agent/package.json +26 -0
- package/templates/base/multi-agent/src/agents/orchestrator.ts +22 -0
- package/templates/base/multi-agent/src/agents/specialist-a.ts +17 -0
- package/templates/base/multi-agent/src/agents/specialist-b.ts +16 -0
- package/templates/base/multi-agent/src/config.ts +19 -0
- package/templates/base/multi-agent/src/engine.ts +77 -0
- package/templates/base/multi-agent/src/providers/openai-compatible.ts +104 -0
- package/templates/base/multi-agent/src/tools/example-tools.ts +28 -0
- package/templates/base/multi-agent/tsconfig.json +17 -0
- package/templates/base/single/.env.example +4 -0
- package/templates/base/single/package.json +26 -0
- package/templates/base/single/src/agents/my-agent.ts +18 -0
- package/templates/base/single/src/config.ts +18 -0
- package/templates/base/single/src/engine.ts +66 -0
- package/templates/base/single/src/providers/openai-compatible.ts +104 -0
- package/templates/base/single/src/tools/example-tools.ts +28 -0
- package/templates/base/single/tsconfig.json +17 -0
- package/templates/deploy/hybrid/src/index.ts +27 -0
- package/templates/deploy/hybrid/src/server.ts +26 -0
- package/templates/deploy/mcp-http/src/server.ts +26 -0
- package/templates/deploy/mcp-stdio/src/server.ts +25 -0
- package/templates/deploy/repl/src/index.ts +27 -0
|
@@ -85,7 +85,7 @@ function addServer(info, args) {
|
|
|
85
85
|
}
|
|
86
86
|
fs.writeFileSync(info.serverFile, generateServerTs(info, opts));
|
|
87
87
|
console.log(`\n✓ Created src/server.ts (HTTP MCP on port ${opts.port})`);
|
|
88
|
-
const added = (0, patcher_1.addPackageDependency)(info.pkgFile, '@backendkit-labs/mcp-server', '^0.
|
|
88
|
+
const added = (0, patcher_1.addPackageDependency)(info.pkgFile, '@backendkit-labs/mcp-server', '^0.15.0');
|
|
89
89
|
(0, patcher_1.addPackageDependency)(info.pkgFile, 'zod', '^3.25.0');
|
|
90
90
|
(0, patcher_1.addPackageScript)(info.pkgFile, 'server', 'ts-node src/server.ts');
|
|
91
91
|
if (added) {
|
package/dist/src/scaffold.js
CHANGED
|
@@ -83,6 +83,7 @@ const DEPLOY_SCRIPTS = {
|
|
|
83
83
|
delete s['start'];
|
|
84
84
|
},
|
|
85
85
|
};
|
|
86
|
+
const MCP_DEPLOY_MODES = ['mcp-http', 'mcp-stdio', 'hybrid'];
|
|
86
87
|
function patchPackageJson(dir, projectName, deployMode) {
|
|
87
88
|
const pkgPath = path.join(dir, 'package.json');
|
|
88
89
|
if (!fs.existsSync(pkgPath))
|
|
@@ -93,6 +94,10 @@ function patchPackageJson(dir, projectName, deployMode) {
|
|
|
93
94
|
pkg.scripts ??= {};
|
|
94
95
|
DEPLOY_SCRIPTS[deployMode](pkg.scripts);
|
|
95
96
|
}
|
|
97
|
+
if (deployMode && MCP_DEPLOY_MODES.includes(deployMode)) {
|
|
98
|
+
pkg.dependencies ??= {};
|
|
99
|
+
pkg.dependencies['@backendkit-labs/mcp-server'] ??= '^0.15.0';
|
|
100
|
+
}
|
|
96
101
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
97
102
|
}
|
|
98
103
|
// ── Write .env stub ───────────────────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
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.16.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": { "node": ">=20.0.0" }
|
|
23
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { createAutonomousAgent, createMemorySystem } from '@backendkit-labs/agent-core';
|
|
5
|
+
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
6
|
+
|
|
7
|
+
// ── Agent definition ──────────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
const monitorAgent: AgentProfile = {
|
|
10
|
+
id: 'monitor',
|
|
11
|
+
name: 'Monitor',
|
|
12
|
+
icon: '👁',
|
|
13
|
+
description: 'Monitors events and executes scheduled tasks',
|
|
14
|
+
allowedTools: [],
|
|
15
|
+
delegatesTo: [],
|
|
16
|
+
systemPrompt: `You are an autonomous monitor agent.
|
|
17
|
+
// TODO: describe what this agent monitors and what actions it takes.
|
|
18
|
+
When activated by a trigger, analyze the input and take appropriate action.`,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// ── Provider ──────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const provider = process.env.LLM_PROVIDER ?? 'anthropic';
|
|
24
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY ?? '';
|
|
25
|
+
|
|
26
|
+
if (!apiKey) { console.error('Missing API key'); process.exit(1); }
|
|
27
|
+
|
|
28
|
+
// ── Memory — persists context between trigger executions ──────────────────────
|
|
29
|
+
|
|
30
|
+
const APP_DIR = path.join(os.homedir(), '.my-autonomous-agent');
|
|
31
|
+
const MEM_DIR = path.join(APP_DIR, 'memory');
|
|
32
|
+
|
|
33
|
+
const memory = createMemorySystem({
|
|
34
|
+
episodic: { provider: 'json', path: path.join(MEM_DIR, 'episodes.json') },
|
|
35
|
+
semantic: { provider: 'json', path: path.join(MEM_DIR, 'knowledge.json') },
|
|
36
|
+
procedural: { provider: 'json', path: path.join(MEM_DIR, 'patterns.json') },
|
|
37
|
+
retention: { maxEpisodes: 200 },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ── Autonomous agent ──────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
const agent = createAutonomousAgent({
|
|
44
|
+
providers: { [provider]: { apiKey } } as any,
|
|
45
|
+
defaultProvider: provider,
|
|
46
|
+
agents: [monitorAgent],
|
|
47
|
+
defaultAgent: 'monitor',
|
|
48
|
+
goal: 'TODO: describe the agent goal',
|
|
49
|
+
memorySystem: memory,
|
|
50
|
+
|
|
51
|
+
triggers: [
|
|
52
|
+
// Run every day at 9:00 AM
|
|
53
|
+
{
|
|
54
|
+
type: 'schedule',
|
|
55
|
+
cron: '0 9 * * *',
|
|
56
|
+
task: 'Run daily check and report findings',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// Listen for webhook events (e.g. from GitHub, Slack, etc.)
|
|
60
|
+
{
|
|
61
|
+
type: 'webhook',
|
|
62
|
+
path: '/events',
|
|
63
|
+
// secret: process.env.WEBHOOK_SECRET,
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// TODO: add more triggers as needed
|
|
67
|
+
// { type: 'event', topic: 'my.custom.event' },
|
|
68
|
+
],
|
|
69
|
+
|
|
70
|
+
webhookPort: parseInt(process.env.WEBHOOK_PORT ?? '3000'),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
void (async () => {
|
|
76
|
+
await agent.start();
|
|
77
|
+
console.log('Autonomous agent started.');
|
|
78
|
+
console.log('Webhook listening on port', process.env.WEBHOOK_PORT ?? 3000);
|
|
79
|
+
console.log(`Memory stored in: ${APP_DIR}`);
|
|
80
|
+
|
|
81
|
+
process.on('SIGTERM', async () => { await agent.stop(); process.exit(0); });
|
|
82
|
+
process.on('SIGINT', async () => { await agent.stop(); process.exit(0); });
|
|
83
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
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
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# My Coding Agent
|
|
2
|
+
|
|
3
|
+
TODO: Describe your project here. The agent reads this file to understand the codebase.
|
|
4
|
+
|
|
5
|
+
## Tech stack
|
|
6
|
+
- TODO: list your languages, frameworks, and tools
|
|
7
|
+
|
|
8
|
+
## Key directories
|
|
9
|
+
- `src/` — main source code
|
|
10
|
+
- `tests/` — test files
|
|
11
|
+
|
|
12
|
+
## Coding conventions
|
|
13
|
+
- TODO: describe naming conventions, patterns, and rules to follow
|
|
@@ -0,0 +1,24 @@
|
|
|
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.16.0",
|
|
15
|
+
"@backendkit-labs/agent-core": "^0.16.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": { "node": ">=20.0.0" }
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ANTHROPIC_API_KEY=sk-ant-...
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Minimal agent — full control over engine configuration",
|
|
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.16.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": { "node": ">=20.0.0" }
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { createBaseEngine } from '@backendkit-labs/agent-core';
|
|
3
|
+
import type { AgentProfile, LLMProvider } from '@backendkit-labs/agent-core';
|
|
4
|
+
|
|
5
|
+
// ── Provider ──────────────────────────────────────────────────────────────────
|
|
6
|
+
// TODO: replace with the actual provider for your chosen LLM
|
|
7
|
+
// e.g. AnthropicProvider from @backendkit-labs/agent-coding
|
|
8
|
+
// or implement LLMProvider interface directly
|
|
9
|
+
|
|
10
|
+
const myProvider: LLMProvider = {
|
|
11
|
+
chat: async (_messages, _tools, callbacks) => {
|
|
12
|
+
// TODO: call your LLM API here
|
|
13
|
+
callbacks.onDone({ role: 'assistant', content: 'Hello from my agent!' });
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// ── Agent ─────────────────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
const myAgent: AgentProfile = {
|
|
20
|
+
id: 'my-agent',
|
|
21
|
+
name: 'My Agent',
|
|
22
|
+
icon: '🤖',
|
|
23
|
+
description: 'TODO: describe your agent',
|
|
24
|
+
allowedTools: [],
|
|
25
|
+
delegatesTo: [],
|
|
26
|
+
systemPrompt: 'TODO: write your system prompt here.',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// ── Engine ────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const engine = createBaseEngine({
|
|
32
|
+
providers: { default: myProvider },
|
|
33
|
+
defaultProvider: 'default',
|
|
34
|
+
agents: [myAgent],
|
|
35
|
+
defaultAgent: 'my-agent',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ── Run ───────────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
void (async () => {
|
|
41
|
+
const input = process.argv[2] ?? 'Hello';
|
|
42
|
+
await engine.run(input);
|
|
43
|
+
})();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-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
|
+
"server": "ts-node src/server.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@backendkit-labs/agent-core": "^0.16.0",
|
|
16
|
+
"dotenv": "^16.0.0",
|
|
17
|
+
"openai": "^4.0.0",
|
|
18
|
+
"zod": "^3.25.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"ts-node": "^10.9.0",
|
|
23
|
+
"typescript": "^5.4.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": { "node": ">=20.0.0" }
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
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
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Single agent with tool registry",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "ts-node src/index.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "node dist/index.js",
|
|
11
|
+
"server": "ts-node src/server.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@backendkit-labs/agent-core": "^0.16.0",
|
|
16
|
+
"dotenv": "^16.0.0",
|
|
17
|
+
"openai": "^4.0.0",
|
|
18
|
+
"zod": "^3.25.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"ts-node": "^10.9.0",
|
|
23
|
+
"typescript": "^5.4.0"
|
|
24
|
+
},
|
|
25
|
+
"engines": { "node": ">=20.0.0" }
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AgentProfile } from '@backendkit-labs/agent-core';
|
|
2
|
+
|
|
3
|
+
export const MY_AGENT_PROFILE: AgentProfile = {
|
|
4
|
+
id: 'my-agent',
|
|
5
|
+
name: 'My Agent',
|
|
6
|
+
icon: '🤖',
|
|
7
|
+
description: 'TODO: describe what this agent does',
|
|
8
|
+
delegatesTo: [],
|
|
9
|
+
allowedTools: [
|
|
10
|
+
'example_action',
|
|
11
|
+
'example_query',
|
|
12
|
+
// TODO: list the tool names this agent can use
|
|
13
|
+
],
|
|
14
|
+
systemPrompt: `You are a helpful agent that TODO: <describe your domain>.
|
|
15
|
+
|
|
16
|
+
Use the available tools to complete tasks requested by the user.
|
|
17
|
+
Be precise and report results clearly.`,
|
|
18
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
|
|
3
|
+
export interface Config {
|
|
4
|
+
llmApiKey: string;
|
|
5
|
+
llmBaseUrl: string;
|
|
6
|
+
llmModel: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function loadConfig(): Config {
|
|
10
|
+
const llmApiKey = process.env.LLM_API_KEY ?? '';
|
|
11
|
+
if (!llmApiKey) throw new Error('Missing LLM_API_KEY in environment');
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
llmApiKey,
|
|
15
|
+
llmBaseUrl: process.env.LLM_BASE_URL ?? 'https://api.openai.com/v1',
|
|
16
|
+
llmModel: process.env.LLM_MODEL ?? 'gpt-4o',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"moduleResolution": "node",
|
|
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
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
3
|
+
import { createEngine } 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 = createEngine(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('Agent ready. Type your request or /exit to quit.\n');
|
|
27
|
+
prompt();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { AgentMCPServer } from '@backendkit-labs/mcp-server';
|
|
3
|
+
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createEngine } from './engine';
|
|
6
|
+
|
|
7
|
+
const server = new AgentMCPServer({
|
|
8
|
+
name: process.env.npm_package_name ?? 'my-agent',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
port: parseInt(process.env.MCP_PORT ?? '3001'),
|
|
11
|
+
|
|
12
|
+
tools: [
|
|
13
|
+
{
|
|
14
|
+
name: 'agent_request',
|
|
15
|
+
description: 'TODO: describe what requests this agent handles',
|
|
16
|
+
schema: {
|
|
17
|
+
request: z.string().describe('Natural-language request for the agent'),
|
|
18
|
+
},
|
|
19
|
+
buildPrompt: ({ request }: { request: string }) => request,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
engineFactory: (transport: CallbackTransport) => createEngine(transport),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
void (async () => { await server.start(); })();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { AgentMCPServer } from '@backendkit-labs/mcp-server';
|
|
3
|
+
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createEngine } from './engine';
|
|
6
|
+
|
|
7
|
+
const server = new AgentMCPServer({
|
|
8
|
+
name: process.env.npm_package_name ?? 'my-agent',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
port: parseInt(process.env.MCP_PORT ?? '3001'),
|
|
11
|
+
|
|
12
|
+
tools: [
|
|
13
|
+
{
|
|
14
|
+
name: 'agent_request',
|
|
15
|
+
description: 'TODO: describe what requests this agent handles',
|
|
16
|
+
schema: {
|
|
17
|
+
request: z.string().describe('Natural-language request for the agent'),
|
|
18
|
+
},
|
|
19
|
+
buildPrompt: ({ request }: { request: string }) => request,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
engineFactory: (transport: CallbackTransport) => createEngine(transport),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
void (async () => { await server.start(); })();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import 'dotenv/config';
|
|
2
|
+
import { AgentMCPServer } from '@backendkit-labs/mcp-server';
|
|
3
|
+
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { createEngine } from './engine';
|
|
6
|
+
|
|
7
|
+
const server = new AgentMCPServer({
|
|
8
|
+
name: process.env.npm_package_name ?? 'my-agent',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
|
|
11
|
+
tools: [
|
|
12
|
+
{
|
|
13
|
+
name: 'agent_request',
|
|
14
|
+
description: 'TODO: describe what requests this agent handles',
|
|
15
|
+
schema: {
|
|
16
|
+
request: z.string().describe('Natural-language request for the agent'),
|
|
17
|
+
},
|
|
18
|
+
buildPrompt: ({ request }: { request: string }) => request,
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
engineFactory: (transport: CallbackTransport) => createEngine(transport),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
void (async () => { await server.startStdio(); })();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import { CallbackTransport } from '@backendkit-labs/agent-core';
|
|
3
|
+
import { createEngine } 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 = createEngine(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('Agent ready. Type your request or /exit to quit.\n');
|
|
27
|
+
prompt();
|