opc-agent 0.1.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.
Files changed (67) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +179 -0
  3. package/README.zh-CN.md +126 -0
  4. package/dist/channels/index.d.ts +10 -0
  5. package/dist/channels/index.js +11 -0
  6. package/dist/channels/web.d.ts +12 -0
  7. package/dist/channels/web.js +81 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.js +146 -0
  10. package/dist/core/agent.d.ts +28 -0
  11. package/dist/core/agent.js +100 -0
  12. package/dist/core/config.d.ts +4 -0
  13. package/dist/core/config.js +50 -0
  14. package/dist/core/runtime.d.ts +14 -0
  15. package/dist/core/runtime.js +54 -0
  16. package/dist/core/types.d.ts +58 -0
  17. package/dist/core/types.js +3 -0
  18. package/dist/dtv/data.d.ts +18 -0
  19. package/dist/dtv/data.js +25 -0
  20. package/dist/dtv/trust.d.ts +19 -0
  21. package/dist/dtv/trust.js +40 -0
  22. package/dist/dtv/value.d.ts +23 -0
  23. package/dist/dtv/value.js +38 -0
  24. package/dist/index.d.ts +16 -0
  25. package/dist/index.js +33 -0
  26. package/dist/memory/index.d.ts +11 -0
  27. package/dist/memory/index.js +33 -0
  28. package/dist/providers/index.d.ts +12 -0
  29. package/dist/providers/index.js +68 -0
  30. package/dist/schema/oad.d.ts +553 -0
  31. package/dist/schema/oad.js +61 -0
  32. package/dist/skills/base.d.ts +9 -0
  33. package/dist/skills/base.js +13 -0
  34. package/dist/skills/index.d.ts +10 -0
  35. package/dist/skills/index.js +25 -0
  36. package/dist/templates/customer-service.d.ts +56 -0
  37. package/dist/templates/customer-service.js +75 -0
  38. package/docs/api/oad-schema.md +64 -0
  39. package/docs/guide/concepts.md +51 -0
  40. package/docs/guide/getting-started.md +44 -0
  41. package/docs/guide/templates.md +28 -0
  42. package/package.json +37 -0
  43. package/src/channels/index.ts +15 -0
  44. package/src/channels/web.ts +85 -0
  45. package/src/cli.ts +131 -0
  46. package/src/core/agent.ts +116 -0
  47. package/src/core/config.ts +14 -0
  48. package/src/core/runtime.ts +57 -0
  49. package/src/core/types.ts +68 -0
  50. package/src/dtv/data.ts +29 -0
  51. package/src/dtv/trust.ts +43 -0
  52. package/src/dtv/value.ts +47 -0
  53. package/src/index.ts +16 -0
  54. package/src/memory/index.ts +34 -0
  55. package/src/providers/index.ts +44 -0
  56. package/src/schema/oad.ts +76 -0
  57. package/src/skills/base.ts +16 -0
  58. package/src/skills/index.ts +27 -0
  59. package/src/templates/customer-service.ts +80 -0
  60. package/templates/customer-service/README.md +22 -0
  61. package/templates/customer-service/oad.yaml +36 -0
  62. package/tests/agent.test.ts +72 -0
  63. package/tests/channel.test.ts +39 -0
  64. package/tests/oad.test.ts +68 -0
  65. package/tests/runtime.test.ts +42 -0
  66. package/tsconfig.json +19 -0
  67. package/vitest.config.ts +9 -0
@@ -0,0 +1,22 @@
1
+ # Customer Service Agent Template
2
+
3
+ A ready-to-use customer service agent with:
4
+ - **FAQ Lookup** — Matches common questions to predefined answers
5
+ - **Human Handoff** — Detects when a customer wants a real person
6
+ - **Web Channel** — HTTP API for chat integration
7
+
8
+ ## Quick Start
9
+
10
+ ```bash
11
+ opc init my-service --template customer-service
12
+ cd my-service
13
+ opc run
14
+ ```
15
+
16
+ ## Customization
17
+
18
+ Edit `oad.yaml` to:
19
+ - Change the system prompt
20
+ - Add custom FAQ entries
21
+ - Switch LLM provider
22
+ - Adjust trust level
@@ -0,0 +1,36 @@
1
+ apiVersion: opc/v1
2
+ kind: Agent
3
+ metadata:
4
+ name: customer-service
5
+ version: 1.0.0
6
+ description: "A customer service agent with FAQ lookup and human handoff"
7
+ author: Deepleaper
8
+ license: Apache-2.0
9
+ marketplace:
10
+ certified: false
11
+ category: customer-service
12
+ spec:
13
+ provider:
14
+ default: deepseek
15
+ allowed: [openai, deepseek, 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.
21
+ skills:
22
+ - name: faq-lookup
23
+ description: "Look up FAQ answers"
24
+ - name: human-handoff
25
+ description: "Hand off to human agent when needed"
26
+ channels:
27
+ - type: web
28
+ port: 3000
29
+ memory:
30
+ shortTerm: true
31
+ longTerm: false
32
+ dtv:
33
+ trust:
34
+ level: sandbox
35
+ value:
36
+ metrics: [response_time, satisfaction_score]
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { BaseAgent } from '../src/core/agent';
3
+
4
+ describe('BaseAgent', () => {
5
+ it('should start in init state', () => {
6
+ const agent = new BaseAgent({ name: 'test' });
7
+ expect(agent.state).toBe('init');
8
+ expect(agent.name).toBe('test');
9
+ });
10
+
11
+ it('should transition through lifecycle', async () => {
12
+ const agent = new BaseAgent({ name: 'test' });
13
+ const states: string[] = [];
14
+ agent.on('state:change', (_from, to) => states.push(to));
15
+
16
+ await agent.init();
17
+ expect(agent.state).toBe('ready');
18
+
19
+ await agent.start();
20
+ expect(agent.state).toBe('running');
21
+
22
+ await agent.stop();
23
+ expect(agent.state).toBe('stopped');
24
+
25
+ expect(states).toEqual(['ready', 'running', 'stopped']);
26
+ });
27
+
28
+ it('should not start if not ready', async () => {
29
+ const agent = new BaseAgent({ name: 'test' });
30
+ await expect(agent.start()).rejects.toThrow('Cannot start agent in state: init');
31
+ });
32
+
33
+ it('should handle messages with stub provider', async () => {
34
+ const agent = new BaseAgent({ name: 'test', systemPrompt: 'Be helpful' });
35
+ await agent.init();
36
+
37
+ const response = await agent.handleMessage({
38
+ id: 'msg_1',
39
+ role: 'user',
40
+ content: 'Hello',
41
+ timestamp: Date.now(),
42
+ });
43
+
44
+ expect(response.role).toBe('assistant');
45
+ expect(response.content).toContain('Hello');
46
+ });
47
+
48
+ it('should use skills before LLM fallback', async () => {
49
+ const agent = new BaseAgent({ name: 'test' });
50
+ await agent.init();
51
+
52
+ agent.registerSkill({
53
+ name: 'greeter',
54
+ description: 'Greets users',
55
+ execute: async (_ctx, msg) => {
56
+ if (msg.content.toLowerCase().includes('hello')) {
57
+ return { handled: true, response: 'Hi there!', confidence: 1.0 };
58
+ }
59
+ return { handled: false, confidence: 0 };
60
+ },
61
+ });
62
+
63
+ const response = await agent.handleMessage({
64
+ id: 'msg_1',
65
+ role: 'user',
66
+ content: 'Hello!',
67
+ timestamp: Date.now(),
68
+ });
69
+
70
+ expect(response.content).toBe('Hi there!');
71
+ });
72
+ });
@@ -0,0 +1,39 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { WebChannel } from '../src/channels/web';
3
+
4
+ describe('WebChannel', () => {
5
+ let channel: WebChannel | null = null;
6
+
7
+ afterEach(async () => {
8
+ if (channel) {
9
+ await channel.stop();
10
+ channel = null;
11
+ }
12
+ });
13
+
14
+ it('should create with default port', () => {
15
+ channel = new WebChannel();
16
+ expect(channel.type).toBe('web');
17
+ });
18
+
19
+ it('should start and stop', async () => {
20
+ channel = new WebChannel(0); // random port
21
+ channel.onMessage(async (msg) => ({
22
+ id: 'resp_1',
23
+ role: 'assistant',
24
+ content: `Echo: ${msg.content}`,
25
+ timestamp: Date.now(),
26
+ }));
27
+ await channel.start();
28
+ await channel.stop();
29
+ channel = null;
30
+ });
31
+
32
+ it('should handle health check', async () => {
33
+ channel = new WebChannel(0);
34
+ await channel.start();
35
+ // Channel is running — health endpoint is available
36
+ await channel.stop();
37
+ channel = null;
38
+ });
39
+ });
@@ -0,0 +1,68 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { OADSchema } from '../src/schema/oad';
3
+
4
+ describe('OAD Schema', () => {
5
+ it('should parse a valid OAD document', () => {
6
+ const doc = OADSchema.parse({
7
+ apiVersion: 'opc/v1',
8
+ kind: 'Agent',
9
+ metadata: { name: 'test-agent' },
10
+ spec: {},
11
+ });
12
+ expect(doc.metadata.name).toBe('test-agent');
13
+ expect(doc.metadata.version).toBe('1.0.0');
14
+ expect(doc.spec.model).toBe('deepseek-chat');
15
+ });
16
+
17
+ it('should reject invalid apiVersion', () => {
18
+ expect(() =>
19
+ OADSchema.parse({
20
+ apiVersion: 'v2',
21
+ kind: 'Agent',
22
+ metadata: { name: 'test' },
23
+ spec: {},
24
+ })
25
+ ).toThrow();
26
+ });
27
+
28
+ it('should parse full document with all fields', () => {
29
+ const doc = OADSchema.parse({
30
+ apiVersion: 'opc/v1',
31
+ kind: 'Agent',
32
+ metadata: {
33
+ name: 'full-agent',
34
+ version: '2.0.0',
35
+ description: 'A full agent',
36
+ author: 'Test',
37
+ license: 'MIT',
38
+ marketplace: { certified: true, category: 'support' },
39
+ },
40
+ spec: {
41
+ provider: { default: 'openai', allowed: ['openai'] },
42
+ model: 'gpt-4',
43
+ systemPrompt: 'You are helpful.',
44
+ skills: [{ name: 'faq', description: 'FAQ skill' }],
45
+ channels: [{ type: 'web', port: 8080 }],
46
+ memory: { shortTerm: true, longTerm: true },
47
+ dtv: {
48
+ trust: { level: 'certified' },
49
+ value: { metrics: ['response_time'] },
50
+ },
51
+ },
52
+ });
53
+ expect(doc.metadata.version).toBe('2.0.0');
54
+ expect(doc.spec.channels[0].port).toBe(8080);
55
+ expect(doc.spec.dtv?.trust?.level).toBe('certified');
56
+ });
57
+
58
+ it('should reject missing metadata.name', () => {
59
+ expect(() =>
60
+ OADSchema.parse({
61
+ apiVersion: 'opc/v1',
62
+ kind: 'Agent',
63
+ metadata: {},
64
+ spec: {},
65
+ })
66
+ ).toThrow();
67
+ });
68
+ });
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { AgentRuntime } from '../src/core/runtime';
3
+ import { createCustomerServiceConfig } from '../src/templates/customer-service';
4
+
5
+ describe('AgentRuntime', () => {
6
+ it('should initialize from config object', async () => {
7
+ const runtime = new AgentRuntime();
8
+ const config = createCustomerServiceConfig();
9
+ const agent = await runtime.initialize(config);
10
+ expect(agent.name).toBe('customer-service');
11
+ expect(agent.state).toBe('ready');
12
+ });
13
+
14
+ it('should throw if no config loaded', async () => {
15
+ const runtime = new AgentRuntime();
16
+ await expect(runtime.initialize()).rejects.toThrow('No config loaded');
17
+ });
18
+
19
+ it('should register skills after initialization', async () => {
20
+ const runtime = new AgentRuntime();
21
+ const config = createCustomerServiceConfig();
22
+ await runtime.initialize(config);
23
+
24
+ runtime.registerSkill({
25
+ name: 'test-skill',
26
+ description: 'Test',
27
+ execute: async () => ({ handled: false, confidence: 0 }),
28
+ });
29
+ // No throw = success
30
+ });
31
+
32
+ it('should throw registering skill before init', () => {
33
+ const runtime = new AgentRuntime();
34
+ expect(() =>
35
+ runtime.registerSkill({
36
+ name: 'test',
37
+ description: 'Test',
38
+ execute: async () => ({ handled: false, confidence: 0 }),
39
+ })
40
+ ).toThrow('Agent not initialized');
41
+ });
42
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "lib": ["ES2022"],
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist", "tests"]
19
+ }
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ include: ['tests/**/*.test.ts'],
8
+ },
9
+ });