opc-agent 1.3.1 → 1.4.0

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.
@@ -86,9 +86,57 @@ export class SlackChannel extends BaseChannel {
86
86
 
87
87
  /** Start Events API HTTP server */
88
88
  private async startEventsAPI(): Promise<void> {
89
- // TODO: Implement with express or http
90
- // const port = this.config.port ?? 3001;
91
- // Listen for POST /slack/events and /slack/commands
89
+ const http = await import('http');
90
+ const port = this.config.port ?? 3001;
91
+
92
+ const server = http.createServer(async (req, res) => {
93
+ if (req.method !== 'POST') {
94
+ res.writeHead(404);
95
+ res.end();
96
+ return;
97
+ }
98
+
99
+ const chunks: Buffer[] = [];
100
+ for await (const chunk of req) chunks.push(chunk as Buffer);
101
+ const body = JSON.parse(Buffer.concat(chunks).toString());
102
+
103
+ // URL verification challenge
104
+ if (body.type === 'url_verification') {
105
+ res.writeHead(200, { 'Content-Type': 'application/json' });
106
+ res.end(JSON.stringify({ challenge: body.challenge }));
107
+ return;
108
+ }
109
+
110
+ // Event callback
111
+ if (body.type === 'event_callback' && body.event) {
112
+ const event = body.event as SlackMessageEvent;
113
+ if (event.type === 'message' || event.type === 'app_mention') {
114
+ // Don't block the HTTP response
115
+ this.handleMessage(event).catch(() => {});
116
+ }
117
+ }
118
+
119
+ // Slash commands (form-urlencoded, but we handle JSON for simplicity)
120
+ if (req.url === '/slack/commands' && body.command) {
121
+ const reply = await this.handleSlashCommand({
122
+ command: body.command,
123
+ text: body.text ?? '',
124
+ userId: body.user_id,
125
+ channelId: body.channel_id,
126
+ triggerId: body.trigger_id,
127
+ });
128
+ res.writeHead(200, { 'Content-Type': 'application/json' });
129
+ res.end(JSON.stringify({ response_type: 'ephemeral', text: reply }));
130
+ return;
131
+ }
132
+
133
+ res.writeHead(200);
134
+ res.end('ok');
135
+ });
136
+
137
+ server.listen(port, () => {
138
+ console.log(`[SlackChannel] Events API listening on port ${port}`);
139
+ });
92
140
  }
93
141
 
94
142
  /** Handle incoming Slack message */
@@ -149,12 +197,21 @@ export class SlackChannel extends BaseChannel {
149
197
 
150
198
  /** Send a message to a Slack channel */
151
199
  async sendMessage(channel: string, text: string, threadTs?: string): Promise<void> {
152
- // TODO: Implement with @slack/web-api
153
- // const { WebClient } = await import('@slack/web-api');
154
- // const client = new WebClient(this.config.botToken);
155
- // await client.chat.postMessage({ channel, text, thread_ts: threadTs });
156
- void channel;
157
- void text;
158
- void threadTs;
200
+ const body: Record<string, string> = { channel, text };
201
+ if (threadTs) body.thread_ts = threadTs;
202
+
203
+ const res = await fetch('https://slack.com/api/chat.postMessage', {
204
+ method: 'POST',
205
+ headers: {
206
+ 'Authorization': `Bearer ${this.config.botToken}`,
207
+ 'Content-Type': 'application/json',
208
+ },
209
+ body: JSON.stringify(body),
210
+ });
211
+
212
+ const data = await res.json() as { ok: boolean; error?: string };
213
+ if (!data.ok) {
214
+ console.error(`[SlackChannel] chat.postMessage failed: ${data.error}`);
215
+ }
159
216
  }
160
217
  }
@@ -288,6 +288,11 @@ export class WebChannel extends BaseChannel {
288
288
  private port: number;
289
289
  private streamHandler: ((msg: Message, res: Response) => Promise<void>) | null = null;
290
290
  private agentName: string = 'OPC Agent';
291
+ private agentVersion: string = '1.0.0';
292
+ private memoryType: string = 'in-memory';
293
+ private skillNames: string[] = [];
294
+ private channelNames: string[] = ['web'];
295
+ private analyticsProvider: (() => any) | null = null;
291
296
  private currentProvider: string = 'openai';
292
297
  private stats = { sessions: 0, messages: 0, totalResponseMs: 0, tokenUsage: 0, knowledgeFiles: 0, startedAt: Date.now(), errors: 0 };
293
298
  private eventHandlers: Map<string, Function[]> = new Map();
@@ -335,6 +340,26 @@ export class WebChannel extends BaseChannel {
335
340
  this.agentName = name;
336
341
  }
337
342
 
343
+ setAgentVersion(version: string): void {
344
+ this.agentVersion = version;
345
+ }
346
+
347
+ setMemoryType(type: string): void {
348
+ this.memoryType = type;
349
+ }
350
+
351
+ setSkillNames(names: string[]): void {
352
+ this.skillNames = names;
353
+ }
354
+
355
+ setChannelNames(names: string[]): void {
356
+ this.channelNames = names;
357
+ }
358
+
359
+ setAnalyticsProvider(fn: () => any): void {
360
+ this.analyticsProvider = fn;
361
+ }
362
+
338
363
  onStreamMessage(handler: (msg: Message, res: Response) => Promise<void>): void {
339
364
  this.streamHandler = handler;
340
365
  }
@@ -345,7 +370,17 @@ export class WebChannel extends BaseChannel {
345
370
  });
346
371
 
347
372
  this.app.get('/health', (_req: Request, res: Response) => {
348
- res.json({ status: 'ok', timestamp: Date.now() });
373
+ res.json({
374
+ status: 'ok',
375
+ agent: this.agentName,
376
+ version: this.agentVersion,
377
+ uptime: Date.now() - this.stats.startedAt,
378
+ memory: this.memoryType,
379
+ skills: this.skillNames,
380
+ channels: this.channelNames,
381
+ analytics: this.analyticsProvider ? this.analyticsProvider() : null,
382
+ timestamp: Date.now(),
383
+ });
349
384
  });
350
385
 
351
386
  this.app.get('/api/info', (_req: Request, res: Response) => {
@@ -426,7 +461,8 @@ export class WebChannel extends BaseChannel {
426
461
  });
427
462
 
428
463
  this.app.get('/api/dashboard', (_req: Request, res: Response) => {
429
- res.json(this.stats);
464
+ const analytics = this.analyticsProvider ? this.analyticsProvider() : null;
465
+ res.json({ ...this.stats, analytics });
430
466
  });
431
467
 
432
468
  // --- Knowledge Base Upload ---
package/src/cli.ts CHANGED
@@ -94,7 +94,7 @@ async function select(question: string, options: { value: string; label: string
94
94
  program
95
95
  .name('opc')
96
96
  .description('OPC Agent - Open Agent Framework for business workstations')
97
- .version('1.0.0');
97
+ .version('1.4.0');
98
98
 
99
99
  // ── Init command ─────────────────────────────────────────────
100
100
 
@@ -119,6 +119,8 @@ program
119
119
  }
120
120
 
121
121
  fs.mkdirSync(dir, { recursive: true });
122
+ fs.mkdirSync(path.join(dir, 'src', 'skills'), { recursive: true });
123
+
122
124
  const factory = TEMPLATES[template]?.factory ?? createCustomerServiceConfig;
123
125
  const config = factory();
124
126
  config.metadata.name = name;
@@ -130,6 +132,113 @@ program
130
132
 
131
133
  fs.writeFileSync(path.join(dir, 'oad.yaml'), yaml.dump(config, { lineWidth: 120 }));
132
134
 
135
+ // agent.yaml — standalone OAD config for runtime usage
136
+ fs.writeFileSync(
137
+ path.join(dir, 'agent.yaml'),
138
+ `apiVersion: opc/v1
139
+ kind: Agent
140
+ metadata:
141
+ name: ${name}
142
+ version: 1.0.0
143
+ description: My AI Agent
144
+ spec:
145
+ model: qwen2.5
146
+ provider:
147
+ default: ollama
148
+ systemPrompt: |
149
+ You are a helpful AI assistant named ${name}.
150
+ Be concise, helpful, and friendly.
151
+ channels:
152
+ - type: web
153
+ port: 3000
154
+ memory:
155
+ shortTerm: true
156
+ longTerm:
157
+ provider: deepbrain
158
+ skills:
159
+ - name: echo
160
+ description: Echo test skill
161
+ `,
162
+ );
163
+
164
+ // src/index.ts — entry point
165
+ fs.writeFileSync(
166
+ path.join(dir, 'src', 'index.ts'),
167
+ `import { AgentRuntime } from 'opc-agent';
168
+ import { EchoSkill } from './skills/echo';
169
+
170
+ async function main() {
171
+ const runtime = new AgentRuntime();
172
+
173
+ // Load OAD config
174
+ await runtime.loadConfig('./agent.yaml');
175
+
176
+ // Initialize agent with channels, memory, etc.
177
+ const agent = await runtime.initialize();
178
+
179
+ // Register custom skills
180
+ runtime.registerSkill(new EchoSkill());
181
+
182
+ // Start serving
183
+ await runtime.start();
184
+
185
+ console.log('🤖 Agent is running!');
186
+ console.log(' Web UI: http://localhost:3000');
187
+ console.log(' Press Ctrl+C to stop');
188
+ }
189
+
190
+ main().catch(console.error);
191
+ `,
192
+ );
193
+
194
+ // src/skills/echo.ts — example skill
195
+ fs.writeFileSync(
196
+ path.join(dir, 'src', 'skills', 'echo.ts'),
197
+ `import { BaseSkill } from 'opc-agent';
198
+ import type { AgentContext, Message, SkillResult } from 'opc-agent';
199
+
200
+ export class EchoSkill extends BaseSkill {
201
+ name = 'echo';
202
+ description = 'Echo back the message (test skill)';
203
+
204
+ async execute(context: AgentContext, message: Message): Promise<SkillResult> {
205
+ if (message.content.toLowerCase().startsWith('/echo ')) {
206
+ const text = message.content.slice(6);
207
+ return this.match(\`🔊 Echo: \${text}\`);
208
+ }
209
+ return this.noMatch();
210
+ }
211
+ }
212
+ `,
213
+ );
214
+
215
+ // tsconfig.json
216
+ fs.writeFileSync(
217
+ path.join(dir, 'tsconfig.json'),
218
+ JSON.stringify(
219
+ {
220
+ compilerOptions: {
221
+ target: 'ES2022',
222
+ module: 'commonjs',
223
+ lib: ['ES2022'],
224
+ outDir: 'dist',
225
+ rootDir: 'src',
226
+ strict: true,
227
+ esModuleInterop: true,
228
+ skipLibCheck: true,
229
+ forceConsistentCasingInFileNames: true,
230
+ resolveJsonModule: true,
231
+ declaration: true,
232
+ sourceMap: true,
233
+ },
234
+ include: ['src/**/*'],
235
+ exclude: ['node_modules', 'dist'],
236
+ },
237
+ null,
238
+ 2,
239
+ ),
240
+ );
241
+
133
242
  // .env.example
134
243
  fs.writeFileSync(
135
244
  path.join(dir, '.env.example'),
@@ -142,9 +251,9 @@ OPC_LLM_MODEL=gpt-4o-mini
142
251
  # OPC_LLM_BASE_URL=https://api.deepseek.com/v1
143
252
  # OPC_LLM_MODEL=deepseek-chat
144
253
 
145
- # For local Ollama:
254
+ # For local Ollama (default in agent.yaml):
146
255
  # OPC_LLM_BASE_URL=http://localhost:11434/v1
147
- # OPC_LLM_MODEL=llama3
256
+ # OPC_LLM_MODEL=qwen2.5
148
257
  `,
149
258
  );
150
259
 
@@ -167,10 +276,16 @@ OPC_LLM_MODEL=gpt-4o-mini
167
276
  private: true,
168
277
  scripts: {
169
278
  start: 'opc run',
279
+ dev: 'opc dev',
170
280
  chat: 'opc chat',
281
+ build: 'tsc',
171
282
  },
172
283
  dependencies: {
173
- 'opc-agent': '^0.5.0',
284
+ 'opc-agent': '^1.3.0',
285
+ },
286
+ devDependencies: {
287
+ typescript: '^5.5.0',
288
+ tsx: '^4.0.0',
174
289
  },
175
290
  },
176
291
  null,
@@ -179,7 +294,7 @@ OPC_LLM_MODEL=gpt-4o-mini
179
294
  );
180
295
 
181
296
  // .gitignore
182
- fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\n.env\n.opc-knowledge.json\ndata/\n');
297
+ fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules\ndist\n.env\n.opc-knowledge.json\ndata/\n');
183
298
 
184
299
  // Dockerfile
185
300
  fs.writeFileSync(
@@ -188,7 +303,8 @@ OPC_LLM_MODEL=gpt-4o-mini
188
303
  WORKDIR /app
189
304
  COPY package.json package-lock.json* ./
190
305
  RUN npm ci --production 2>/dev/null || npm install --production
191
- COPY oad.yaml .env* ./
306
+ COPY oad.yaml agent.yaml .env* ./
307
+ COPY src/ ./src/
192
308
  COPY prompts/ ./prompts/ 2>/dev/null || true
193
309
  EXPOSE 3000
194
310
  CMD ["npx", "opc", "run"]
@@ -207,7 +323,7 @@ services:
207
323
  env_file:
208
324
  - .env
209
325
  volumes:
210
- - ./oad.yaml:/app/oad.yaml:ro
326
+ - ./agent.yaml:/app/agent.yaml:ro
211
327
  restart: unless-stopped
212
328
  `,
213
329
  );
@@ -221,53 +337,69 @@ Created with [OPC Agent](https://github.com/Deepleaper/opc-agent) using the \`${
221
337
 
222
338
  ## Quick Start
223
339
 
224
- 1. **Set your API key:**
340
+ 1. **Install dependencies:**
225
341
  \`\`\`bash
226
- # Edit .env and add your API key
227
- cp .env.example .env
228
- # Then edit .env with your actual key
342
+ npm install
229
343
  \`\`\`
230
344
 
231
- 2. **Install dependencies:**
345
+ 2. **Run with Ollama (default):**
232
346
  \`\`\`bash
233
- npm install
347
+ # Make sure Ollama is running with qwen2.5 model
348
+ ollama pull qwen2.5
349
+ npx tsx src/index.ts
234
350
  \`\`\`
235
351
 
236
- 3. **Start the web server:**
352
+ 3. **Or use OpenAI/other providers:**
237
353
  \`\`\`bash
354
+ # Edit .env and set your API key
238
355
  npx opc run
239
356
  \`\`\`
240
357
 
241
358
  4. **Open browser:** [http://localhost:3000](http://localhost:3000)
242
359
 
243
- ## CLI Chat
360
+ ## Development
244
361
 
245
362
  \`\`\`bash
246
- npx opc chat
363
+ npx opc dev # Hot-reload mode
364
+ npx opc chat # CLI chat
365
+ \`\`\`
366
+
367
+ ## Project Structure
368
+
369
+ \`\`\`
370
+ ${name}/
371
+ ├── agent.yaml # OAD agent config (used by src/index.ts)
372
+ ├── oad.yaml # OAD config (used by opc CLI)
373
+ ├── src/
374
+ │ ├── index.ts # Entry point
375
+ │ └── skills/
376
+ │ └── echo.ts # Example skill
377
+ ├── package.json
378
+ └── tsconfig.json
247
379
  \`\`\`
248
380
 
249
381
  ## Configuration
250
382
 
251
- Edit \`oad.yaml\` to customize your agent's personality, skills, and behavior.
383
+ Edit \`agent.yaml\` to customize your agent's personality, skills, and behavior.
252
384
  `,
253
385
  );
254
386
 
255
387
  console.log(`\n${icon.success} Created agent project: ${color.bold(name + '/')}`);
256
- console.log(` ${icon.file} oad.yaml - Agent definition`);
257
- console.log(` ${icon.file} package.json - Dependencies`);
258
- console.log(` ${icon.file} .env.example - Environment template`);
259
- console.log(` ${icon.file} .env - Environment config (edit this!)`);
388
+ console.log(` ${icon.file} agent.yaml - Agent definition (OAD)`);
389
+ console.log(` ${icon.file} src/index.ts - Entry point`);
390
+ console.log(` ${icon.file} src/skills/echo.ts - Example skill`);
391
+ console.log(` ${icon.file} package.json - Dependencies`);
392
+ console.log(` ${icon.file} tsconfig.json - TypeScript config`);
393
+ console.log(` ${icon.file} .env.example - Environment template`);
260
394
  console.log(` ${icon.file} .gitignore`);
261
395
  console.log(` ${icon.file} Dockerfile`);
262
- console.log(` ${icon.file} docker-compose.yml`);
263
396
  console.log(` ${icon.file} README.md`);
264
397
  console.log(`\n Template: ${color.cyan(template)}`);
265
398
  console.log(`\n${color.bold('Next steps:')}`);
266
399
  console.log(` 1. cd ${name}`);
267
- console.log(` 2. Edit .env — set your OPC_LLM_API_KEY`);
268
- console.log(` 3. npm install`);
269
- console.log(` 4. npx opc run`);
270
- console.log(` 5. Open http://localhost:3000\n`);
400
+ console.log(` 2. npm install`);
401
+ console.log(` 3. npx tsx src/index.ts ${color.dim('# or: npx opc run')}`);
402
+ console.log(` 4. Open http://localhost:3000\n`);
271
403
  });
272
404
 
273
405
  // ── Chat command ─────────────────────────────────────────────
@@ -5,6 +5,7 @@ import { WebChannel } from '../channels/web';
5
5
  import { TelegramChannel } from '../channels/telegram';
6
6
  import { WebSocketChannel } from '../channels/websocket';
7
7
  import { DeepBrainMemoryStore } from '../memory/deepbrain';
8
+ import { Analytics } from '../analytics';
8
9
  import type { OADDocument } from '../schema/oad';
9
10
  import type { ISkill, MemoryStore, Message } from './types';
10
11
  import type { Response } from 'express';
@@ -25,6 +26,7 @@ export class AgentRuntime {
25
26
  private historyLimit: number = DEFAULT_HISTORY_LIMIT;
26
27
  private shutdownHandlers: (() => Promise<void>)[] = [];
27
28
  private isShuttingDown = false;
29
+ private analytics: Analytics = new Analytics();
28
30
 
29
31
  async loadConfig(filePath: string): Promise<OADDocument> {
30
32
  this.config = loadOAD(filePath);
@@ -64,6 +66,12 @@ export class AgentRuntime {
64
66
  const port = ch.port ?? 3000;
65
67
  const webChannel = new WebChannel(port);
66
68
  webChannel.setAgentName(cfg.metadata.name);
69
+ webChannel.setAgentVersion(cfg.metadata.version);
70
+ webChannel.setAnalyticsProvider(() => this.analytics.getSnapshot());
71
+ webChannel.setChannelNames(cfg.spec.channels.map((c: any) => c.type));
72
+ webChannel.setSkillNames(cfg.spec.skills.map((s: any) => s.name));
73
+ const memType = memCfg && typeof memCfg.longTerm === 'object' && memCfg.longTerm.provider === 'deepbrain' ? 'deepbrain' : 'in-memory';
74
+ webChannel.setMemoryType(memType);
67
75
  // Wire streaming
68
76
  webChannel.onStreamMessage(async (msg: Message, res: Response) => {
69
77
  res.writeHead(200, {
@@ -72,14 +80,17 @@ export class AgentRuntime {
72
80
  Connection: 'keep-alive',
73
81
  'Access-Control-Allow-Origin': '*',
74
82
  });
83
+ const startTime = Date.now();
75
84
  try {
76
85
  for await (const chunk of this.agent!.handleMessageStream(msg)) {
77
86
  res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
78
87
  }
79
88
  res.write('data: [DONE]\n\n');
89
+ this.analytics.recordMessage(Date.now() - startTime);
80
90
  } catch (err) {
81
91
  const errMsg = err instanceof Error ? err.message : String(err);
82
92
  res.write(`data: ${JSON.stringify({ error: errMsg })}\n\n`);
93
+ this.analytics.recordError();
83
94
  }
84
95
  res.end();
85
96
  });
@@ -98,6 +109,18 @@ export class AgentRuntime {
98
109
  }
99
110
 
100
111
  await this.agent.init();
112
+
113
+ // Wire analytics to agent events
114
+ this.agent.on('message:out', () => {
115
+ // responseTime is approximated; real timing is done via skill/llm events
116
+ });
117
+ this.agent.on('skill:execute', (skillName: string) => {
118
+ this.analytics.recordSkillUsage(skillName);
119
+ });
120
+ this.agent.on('error', () => {
121
+ this.analytics.recordError();
122
+ });
123
+
101
124
  this.logger.info('Agent initialized', { name: cfg.metadata.name });
102
125
  return this.agent;
103
126
  }
@@ -149,4 +172,12 @@ export class AgentRuntime {
149
172
  getAgent(): BaseAgent | null {
150
173
  return this.agent;
151
174
  }
175
+
176
+ getAnalytics(): Analytics {
177
+ return this.analytics;
178
+ }
179
+
180
+ getConfig(): OADDocument | null {
181
+ return this.config;
182
+ }
152
183
  }
@@ -309,6 +309,14 @@ function isGeminiNative(): boolean {
309
309
 
310
310
  export function createProvider(name: string = 'openai', model?: string, baseUrl?: string, apiKey?: string): LLMProvider {
311
311
  const finalModel = model || process.env.OPC_LLM_MODEL || 'gpt-4o-mini';
312
+
313
+ // Auto-detect ollama: use localhost:11434/v1 and dummy apiKey
314
+ if (name === 'ollama') {
315
+ const ollamaBase = baseUrl || process.env.OPC_LLM_BASE_URL || 'http://localhost:11434/v1';
316
+ const ollamaKey = apiKey || process.env.OPC_LLM_API_KEY || 'ollama';
317
+ return new OpenAICompatibleProvider('ollama', finalModel, ollamaBase, ollamaKey);
318
+ }
319
+
312
320
  const finalKey = apiKey || getApiKey();
313
321
  const finalBaseUrl = baseUrl || getBaseUrl();
314
322
 
@@ -328,4 +336,4 @@ export function createProvider(name: string = 'openai', model?: string, baseUrl?
328
336
  return new OpenAICompatibleProvider(resolvedName, finalModel, baseUrl, apiKey);
329
337
  }
330
338
 
331
- export const SUPPORTED_PROVIDERS = ['openai', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'] as const;
339
+ export const SUPPORTED_PROVIDERS = ['openai', 'ollama', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'] as const;
@@ -0,0 +1,9 @@
1
+ FROM node:22-alpine
2
+ WORKDIR /app
3
+ COPY package.json package-lock.json* ./
4
+ RUN npm ci --production 2>/dev/null || npm install --production
5
+ COPY oad.yaml agent.yaml .env* ./
6
+ COPY src/ ./src/
7
+ COPY prompts/ ./prompts/ 2>/dev/null || true
8
+ EXPOSE 3000
9
+ CMD ["npx", "opc", "run"]
@@ -0,0 +1,50 @@
1
+ # test-agent
2
+
3
+ Created with [OPC Agent](https://github.com/Deepleaper/opc-agent) using the `customer-service` template.
4
+
5
+ ## Quick Start
6
+
7
+ 1. **Install dependencies:**
8
+ ```bash
9
+ npm install
10
+ ```
11
+
12
+ 2. **Run with Ollama (default):**
13
+ ```bash
14
+ # Make sure Ollama is running with qwen2.5 model
15
+ ollama pull qwen2.5
16
+ npx tsx src/index.ts
17
+ ```
18
+
19
+ 3. **Or use OpenAI/other providers:**
20
+ ```bash
21
+ # Edit .env and set your API key
22
+ npx opc run
23
+ ```
24
+
25
+ 4. **Open browser:** [http://localhost:3000](http://localhost:3000)
26
+
27
+ ## Development
28
+
29
+ ```bash
30
+ npx opc dev # Hot-reload mode
31
+ npx opc chat # CLI chat
32
+ ```
33
+
34
+ ## Project Structure
35
+
36
+ ```
37
+ test-agent/
38
+ ├── agent.yaml # OAD agent config (used by src/index.ts)
39
+ ├── oad.yaml # OAD config (used by opc CLI)
40
+ ├── src/
41
+ │ ├── index.ts # Entry point
42
+ │ └── skills/
43
+ │ └── echo.ts # Example skill
44
+ ├── package.json
45
+ └── tsconfig.json
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ Edit `agent.yaml` to customize your agent's personality, skills, and behavior.
@@ -0,0 +1,23 @@
1
+ apiVersion: opc/v1
2
+ kind: Agent
3
+ metadata:
4
+ name: test-agent
5
+ version: 1.0.0
6
+ description: My AI Agent
7
+ spec:
8
+ model: qwen2.5
9
+ provider:
10
+ default: ollama
11
+ systemPrompt: |
12
+ You are a helpful AI assistant named test-agent.
13
+ Be concise, helpful, and friendly.
14
+ channels:
15
+ - type: web
16
+ port: 3000
17
+ memory:
18
+ shortTerm: true
19
+ longTerm:
20
+ provider: deepbrain
21
+ skills:
22
+ - name: echo
23
+ description: Echo test skill
@@ -0,0 +1,11 @@
1
+ version: '3.8'
2
+ services:
3
+ agent:
4
+ build: .
5
+ ports:
6
+ - "3000:3000"
7
+ env_file:
8
+ - .env
9
+ volumes:
10
+ - ./agent.yaml:/app/agent.yaml:ro
11
+ restart: unless-stopped
@@ -0,0 +1,31 @@
1
+ apiVersion: opc/v1
2
+ kind: Agent
3
+ metadata:
4
+ name: test-agent
5
+ version: 1.0.0
6
+ description: Customer service agent with FAQ and human handoff
7
+ author: OPC Agent
8
+ license: Apache-2.0
9
+ spec:
10
+ provider:
11
+ default: deepseek
12
+ allowed:
13
+ - openai
14
+ - deepseek
15
+ - qwen
16
+ model: deepseek-chat
17
+ systemPrompt: |-
18
+ You are a friendly and professional customer service agent.
19
+ You help customers with their questions about products, orders, shipping, and returns.
20
+ Be concise, helpful, and empathetic. If you're unsure, offer to connect them with a human agent.
21
+ skills:
22
+ - name: faq-lookup
23
+ description: Look up FAQ answers
24
+ - name: human-handoff
25
+ description: Hand off to human agent
26
+ channels:
27
+ - type: web
28
+ port: 3000
29
+ memory:
30
+ shortTerm: true
31
+ longTerm: false