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.
- package/LICENSE +201 -0
- package/README.md +179 -0
- package/README.zh-CN.md +126 -0
- package/dist/channels/index.d.ts +10 -0
- package/dist/channels/index.js +11 -0
- package/dist/channels/web.d.ts +12 -0
- package/dist/channels/web.js +81 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +146 -0
- package/dist/core/agent.d.ts +28 -0
- package/dist/core/agent.js +100 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +50 -0
- package/dist/core/runtime.d.ts +14 -0
- package/dist/core/runtime.js +54 -0
- package/dist/core/types.d.ts +58 -0
- package/dist/core/types.js +3 -0
- package/dist/dtv/data.d.ts +18 -0
- package/dist/dtv/data.js +25 -0
- package/dist/dtv/trust.d.ts +19 -0
- package/dist/dtv/trust.js +40 -0
- package/dist/dtv/value.d.ts +23 -0
- package/dist/dtv/value.js +38 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +33 -0
- package/dist/memory/index.d.ts +11 -0
- package/dist/memory/index.js +33 -0
- package/dist/providers/index.d.ts +12 -0
- package/dist/providers/index.js +68 -0
- package/dist/schema/oad.d.ts +553 -0
- package/dist/schema/oad.js +61 -0
- package/dist/skills/base.d.ts +9 -0
- package/dist/skills/base.js +13 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.js +25 -0
- package/dist/templates/customer-service.d.ts +56 -0
- package/dist/templates/customer-service.js +75 -0
- package/docs/api/oad-schema.md +64 -0
- package/docs/guide/concepts.md +51 -0
- package/docs/guide/getting-started.md +44 -0
- package/docs/guide/templates.md +28 -0
- package/package.json +37 -0
- package/src/channels/index.ts +15 -0
- package/src/channels/web.ts +85 -0
- package/src/cli.ts +131 -0
- package/src/core/agent.ts +116 -0
- package/src/core/config.ts +14 -0
- package/src/core/runtime.ts +57 -0
- package/src/core/types.ts +68 -0
- package/src/dtv/data.ts +29 -0
- package/src/dtv/trust.ts +43 -0
- package/src/dtv/value.ts +47 -0
- package/src/index.ts +16 -0
- package/src/memory/index.ts +34 -0
- package/src/providers/index.ts +44 -0
- package/src/schema/oad.ts +76 -0
- package/src/skills/base.ts +16 -0
- package/src/skills/index.ts +27 -0
- package/src/templates/customer-service.ts +80 -0
- package/templates/customer-service/README.md +22 -0
- package/templates/customer-service/oad.yaml +36 -0
- package/tests/agent.test.ts +72 -0
- package/tests/channel.test.ts +39 -0
- package/tests/oad.test.ts +68 -0
- package/tests/runtime.test.ts +42 -0
- package/tsconfig.json +19 -0
- 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
|
+
}
|