create-bk-agent 0.17.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/src/add/modules/server.js +1 -1
  2. package/dist/src/scaffold.js +5 -0
  3. package/package.json +1 -1
  4. package/templates/base/autonomous/.env.example +4 -0
  5. package/templates/base/autonomous/package.json +23 -0
  6. package/templates/base/autonomous/src/index.ts +67 -0
  7. package/templates/base/autonomous/tsconfig.json +17 -0
  8. package/templates/base/coding/.env.example +4 -0
  9. package/templates/base/coding/AGENT.md +13 -0
  10. package/templates/base/coding/package.json +24 -0
  11. package/templates/base/coding/src/index.ts +42 -0
  12. package/templates/base/coding/tsconfig.json +17 -0
  13. package/templates/base/minimal/.env.example +1 -0
  14. package/templates/base/minimal/package.json +23 -0
  15. package/templates/base/minimal/src/index.ts +43 -0
  16. package/templates/base/minimal/tsconfig.json +17 -0
  17. package/templates/base/multi-agent/.env.example +7 -0
  18. package/templates/base/multi-agent/package.json +26 -0
  19. package/templates/base/multi-agent/src/agents/orchestrator.ts +22 -0
  20. package/templates/base/multi-agent/src/agents/specialist-a.ts +17 -0
  21. package/templates/base/multi-agent/src/agents/specialist-b.ts +16 -0
  22. package/templates/base/multi-agent/src/config.ts +19 -0
  23. package/templates/base/multi-agent/src/engine.ts +49 -0
  24. package/templates/base/multi-agent/src/providers/openai-compatible.ts +104 -0
  25. package/templates/base/multi-agent/src/tools/example-tools.ts +28 -0
  26. package/templates/base/multi-agent/tsconfig.json +17 -0
  27. package/templates/base/single/.env.example +4 -0
  28. package/templates/base/single/package.json +26 -0
  29. package/templates/base/single/src/agents/my-agent.ts +18 -0
  30. package/templates/base/single/src/config.ts +18 -0
  31. package/templates/base/single/src/engine.ts +44 -0
  32. package/templates/base/single/src/providers/openai-compatible.ts +104 -0
  33. package/templates/base/single/src/tools/example-tools.ts +28 -0
  34. package/templates/base/single/tsconfig.json +17 -0
  35. package/templates/deploy/hybrid/src/index.ts +27 -0
  36. package/templates/deploy/hybrid/src/server.ts +26 -0
  37. package/templates/deploy/mcp-http/src/server.ts +26 -0
  38. package/templates/deploy/mcp-stdio/src/server.ts +25 -0
  39. 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.16.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) {
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-bk-agent",
3
- "version": "0.17.0",
3
+ "version": "0.17.1",
4
4
  "description": "Create a new BackendKit agent project",
5
5
  "main": "dist/src/index.js",
6
6
  "bin": {
@@ -0,0 +1,4 @@
1
+ ANTHROPIC_API_KEY=sk-ant-...
2
+ # or: OPENAI_API_KEY=sk-...
3
+ LLM_PROVIDER=anthropic
4
+ WEBHOOK_PORT=3000
@@ -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,67 @@
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
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ const agent = createAutonomousAgent({
30
+ providers: { [provider]: { apiKey } } as any,
31
+ defaultProvider: provider,
32
+ agents: [monitorAgent],
33
+ defaultAgent: 'monitor',
34
+ goal: 'TODO: describe the agent goal',
35
+
36
+ triggers: [
37
+ // Run every day at 9:00 AM
38
+ {
39
+ type: 'schedule',
40
+ cron: '0 9 * * *',
41
+ task: 'Run daily check and report findings',
42
+ },
43
+
44
+ // Listen for webhook events (e.g. from GitHub, Slack, etc.)
45
+ {
46
+ type: 'webhook',
47
+ path: '/events',
48
+ // secret: process.env.WEBHOOK_SECRET, // uncomment to validate signatures
49
+ },
50
+
51
+ // TODO: add more triggers as needed
52
+ // { type: 'event', topic: 'my.custom.event' },
53
+ ],
54
+
55
+ webhookPort: parseInt(process.env.WEBHOOK_PORT ?? '3000'),
56
+ });
57
+
58
+ // ── Start ─────────────────────────────────────────────────────────────────────
59
+
60
+ void (async () => {
61
+ await agent.start();
62
+ console.log('Autonomous agent started.');
63
+ console.log('Webhook listening on port', process.env.WEBHOOK_PORT ?? 3000);
64
+
65
+ process.on('SIGTERM', async () => { await agent.stop(); process.exit(0); });
66
+ process.on('SIGINT', async () => { await agent.stop(); process.exit(0); });
67
+ })();
@@ -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,4 @@
1
+ ANTHROPIC_API_KEY=sk-ant-...
2
+ # or: OPENAI_API_KEY=sk-...
3
+ LLM_PROVIDER=anthropic
4
+ LLM_MODEL=claude-sonnet-4-6
@@ -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,7 @@
1
+ # LLM — any OpenAI-compatible endpoint
2
+ LLM_API_KEY=sk-...
3
+ LLM_BASE_URL=https://api.openai.com/v1
4
+ LLM_MODEL=gpt-4o
5
+
6
+ # TODO: Add your domain-specific env vars here
7
+ # MY_SERVICE_URL=http://localhost:8080
@@ -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,49 @@
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 createEngine(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
+ }
@@ -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,4 @@
1
+ # LLM — any OpenAI-compatible endpoint
2
+ LLM_API_KEY=sk-...
3
+ LLM_BASE_URL=https://api.openai.com/v1
4
+ LLM_MODEL=gpt-4o
@@ -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,44 @@
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 { MY_AGENT_PROFILE } from './agents/my-agent';
11
+ import { exampleAction, exampleQuery } from './tools/example-tools';
12
+
13
+ export function createEngine(transport: CallbackTransport): AgentEngine {
14
+ const config = loadConfig();
15
+
16
+ const tools = new ToolRegistry();
17
+ tools
18
+ .register(exampleAction)
19
+ .register(exampleQuery);
20
+ // TODO: register more tools
21
+
22
+ const agents = new AgentRegistry();
23
+ agents.register(MY_AGENT_PROFILE);
24
+ // TODO: register more specialists
25
+
26
+ const providers = new ProviderRegistry();
27
+ providers.register('default', new OpenAICompatibleProvider({
28
+ apiKey: config.llmApiKey,
29
+ baseUrl: config.llmBaseUrl,
30
+ model: config.llmModel,
31
+ }));
32
+
33
+ return new AgentEngine({
34
+ model: { provider: 'default', id: config.llmModel },
35
+ agents,
36
+ tools,
37
+ providers,
38
+ defaultProvider: 'default',
39
+ defaultAgentId: 'my-agent',
40
+ transport,
41
+ maxIterations: 20,
42
+ iterationMode: 'auto',
43
+ });
44
+ }
@@ -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();