opc-agent 1.1.3 → 1.2.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.
- package/CHANGELOG.md +6 -0
- package/CONTRIBUTING.md +75 -75
- package/README.md +429 -429
- package/README.zh-CN.md +415 -415
- package/dist/channels/web.js +256 -256
- package/dist/core/streaming.d.ts +56 -0
- package/dist/core/streaming.js +160 -0
- package/dist/deploy/hermes.js +22 -22
- package/dist/deploy/openclaw.js +31 -31
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +13 -148
- package/dist/schema/oad.d.ts +3 -3
- package/dist/templates/code-reviewer.js +5 -5
- package/dist/templates/customer-service.js +2 -2
- package/dist/templates/data-analyst.js +5 -5
- package/dist/templates/knowledge-base.js +2 -2
- package/dist/templates/sales-assistant.js +4 -4
- package/dist/templates/teacher.js +6 -6
- package/dist/tools/gateway.d.ts +28 -0
- package/dist/tools/gateway.js +177 -0
- package/docs/.vitepress/config.ts +103 -103
- package/docs/api/cli.md +48 -48
- package/docs/api/oad-schema.md +64 -64
- package/docs/api/sdk.md +80 -80
- package/docs/guide/concepts.md +51 -51
- package/docs/guide/configuration.md +79 -79
- package/docs/guide/deployment.md +42 -42
- package/docs/guide/getting-started.md +44 -44
- package/docs/guide/templates.md +28 -28
- package/docs/guide/testing.md +84 -84
- package/docs/index.md +27 -27
- package/docs/zh/api/cli.md +54 -54
- package/docs/zh/api/oad-schema.md +87 -87
- package/docs/zh/api/sdk.md +102 -102
- package/docs/zh/guide/concepts.md +104 -104
- package/docs/zh/guide/configuration.md +135 -135
- package/docs/zh/guide/deployment.md +81 -81
- package/docs/zh/guide/getting-started.md +82 -82
- package/docs/zh/guide/templates.md +84 -84
- package/docs/zh/guide/testing.md +88 -88
- package/docs/zh/index.md +27 -27
- package/examples/customer-service-demo/README.md +90 -90
- package/examples/customer-service-demo/oad.yaml +107 -107
- package/package.json +1 -1
- package/src/analytics/index.ts +66 -66
- package/src/channels/discord.ts +192 -192
- package/src/channels/email.ts +177 -177
- package/src/channels/feishu.ts +236 -236
- package/src/channels/index.ts +15 -15
- package/src/channels/slack.ts +160 -160
- package/src/channels/telegram.ts +90 -90
- package/src/channels/voice.ts +106 -106
- package/src/channels/web.ts +17 -17
- package/src/channels/webhook.ts +199 -199
- package/src/channels/websocket.ts +87 -87
- package/src/channels/wechat.ts +149 -149
- package/src/core/a2a.ts +143 -143
- package/src/core/agent.ts +152 -152
- package/src/core/analytics-engine.ts +186 -186
- package/src/core/auth.ts +57 -57
- package/src/core/cache.ts +141 -141
- package/src/core/compose.ts +77 -77
- package/src/core/config.ts +14 -14
- package/src/core/errors.ts +148 -148
- package/src/core/hitl.ts +138 -138
- package/src/core/knowledge.ts +49 -4
- package/src/core/logger.ts +57 -57
- package/src/core/orchestrator.ts +215 -215
- package/src/core/performance.ts +187 -187
- package/src/core/rate-limiter.ts +128 -128
- package/src/core/room.ts +109 -109
- package/src/core/runtime.ts +152 -152
- package/src/core/sandbox.ts +101 -101
- package/src/core/security.ts +171 -171
- package/src/core/streaming.ts +195 -0
- package/src/core/types.ts +68 -68
- package/src/core/versioning.ts +106 -106
- package/src/core/watch.ts +178 -178
- package/src/core/workflow.ts +235 -235
- package/src/deploy/hermes.ts +156 -156
- package/src/deploy/openclaw.ts +200 -200
- package/src/dtv/data.ts +29 -29
- package/src/dtv/trust.ts +43 -43
- package/src/dtv/value.ts +47 -47
- package/src/i18n/index.ts +216 -216
- package/src/index.ts +6 -0
- package/src/marketplace/index.ts +223 -223
- package/src/memory/deepbrain.ts +108 -108
- package/src/memory/index.ts +34 -34
- package/src/plugins/index.ts +208 -208
- package/src/providers/index.ts +12 -3
- package/src/schema/oad.ts +155 -155
- package/src/skills/base.ts +16 -16
- package/src/skills/document.ts +100 -100
- package/src/skills/http.ts +35 -35
- package/src/skills/index.ts +27 -27
- package/src/skills/scheduler.ts +80 -80
- package/src/skills/webhook-trigger.ts +59 -59
- package/src/templates/code-reviewer.ts +34 -34
- package/src/templates/customer-service.ts +80 -80
- package/src/templates/data-analyst.ts +70 -70
- package/src/templates/executive-assistant.ts +71 -71
- package/src/templates/financial-advisor.ts +60 -60
- package/src/templates/knowledge-base.ts +31 -31
- package/src/templates/legal-assistant.ts +71 -71
- package/src/templates/sales-assistant.ts +79 -79
- package/src/templates/teacher.ts +79 -79
- package/src/testing/index.ts +181 -181
- package/src/tools/calculator.ts +73 -73
- package/src/tools/datetime.ts +149 -149
- package/src/tools/gateway.ts +220 -0
- package/src/tools/json-transform.ts +187 -187
- package/src/tools/mcp.ts +76 -76
- package/src/tools/text-analysis.ts +116 -116
- package/templates/Dockerfile +15 -15
- package/templates/code-reviewer/README.md +27 -27
- package/templates/code-reviewer/oad.yaml +41 -41
- package/templates/customer-service/README.md +22 -22
- package/templates/customer-service/oad.yaml +36 -36
- package/templates/docker-compose.yml +21 -21
- package/templates/ecommerce-assistant/README.md +45 -0
- package/templates/ecommerce-assistant/oad.yaml +47 -0
- package/templates/knowledge-base/README.md +28 -28
- package/templates/knowledge-base/oad.yaml +38 -38
- package/templates/sales-assistant/README.md +26 -26
- package/templates/sales-assistant/oad.yaml +43 -43
- package/templates/tech-support/README.md +43 -0
- package/templates/tech-support/oad.yaml +45 -0
- package/tests/a2a.test.ts +66 -66
- package/tests/agent.test.ts +72 -72
- package/tests/analytics.test.ts +50 -50
- package/tests/channel.test.ts +39 -39
- package/tests/e2e.test.ts +134 -134
- package/tests/errors.test.ts +83 -83
- package/tests/gateway.test.ts +71 -0
- package/tests/hitl.test.ts +71 -71
- package/tests/i18n.test.ts +41 -41
- package/tests/mcp.test.ts +54 -54
- package/tests/oad.test.ts +68 -68
- package/tests/performance.test.ts +115 -115
- package/tests/plugin.test.ts +74 -74
- package/tests/room.test.ts +106 -106
- package/tests/runtime.test.ts +42 -42
- package/tests/sandbox.test.ts +46 -46
- package/tests/security.test.ts +60 -60
- package/tests/streaming.test.ts +109 -0
- package/tests/templates.test.ts +77 -77
- package/tests/v070.test.ts +76 -76
- package/tests/versioning.test.ts +75 -75
- package/tests/voice.test.ts +61 -61
- package/tests/webhook.test.ts +29 -29
- package/tests/workflow.test.ts +143 -143
- package/tsconfig.json +19 -19
- package/vitest.config.ts +9 -9
package/tests/runtime.test.ts
CHANGED
|
@@ -1,42 +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
|
-
});
|
|
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/tests/sandbox.test.ts
CHANGED
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { Sandbox } from '../src/core/sandbox';
|
|
3
|
-
|
|
4
|
-
describe('Security Sandbox', () => {
|
|
5
|
-
it('should create sandbox with trust level', () => {
|
|
6
|
-
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
7
|
-
expect(sb.trustLevel).toBe('sandbox');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('should restrict shell in sandbox mode', () => {
|
|
11
|
-
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
12
|
-
expect(sb.checkShellAccess()).toBe(false);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should allow shell in certified mode', () => {
|
|
16
|
-
const sb = new Sandbox({ trustLevel: 'certified', agentDir: '/tmp/agent' });
|
|
17
|
-
expect(sb.checkShellAccess()).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should restrict network in sandbox mode', () => {
|
|
21
|
-
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
22
|
-
expect(sb.checkNetworkAccess('https://api.openai.com')).toBe(false);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should allow network with allowlist', () => {
|
|
26
|
-
const sb = new Sandbox({
|
|
27
|
-
trustLevel: 'sandbox',
|
|
28
|
-
agentDir: '/tmp/agent',
|
|
29
|
-
networkAllowlist: ['api.openai.com'],
|
|
30
|
-
});
|
|
31
|
-
expect(sb.checkNetworkAccess('https://api.openai.com/v1/chat')).toBe(true);
|
|
32
|
-
expect(sb.checkNetworkAccess('https://evil.com')).toBe(false);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('should allow wildcard network in listed mode', () => {
|
|
36
|
-
const sb = new Sandbox({ trustLevel: 'listed', agentDir: '/tmp/agent' });
|
|
37
|
-
expect(sb.checkNetworkAccess('https://anything.com')).toBe(true);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('should return restrictions snapshot', () => {
|
|
41
|
-
const sb = new Sandbox({ trustLevel: 'verified', agentDir: '/tmp/agent' });
|
|
42
|
-
const r = sb.getRestrictions();
|
|
43
|
-
expect(r.shell).toBe(false);
|
|
44
|
-
expect(r.network.allowed.length).toBeGreaterThan(0);
|
|
45
|
-
});
|
|
46
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { Sandbox } from '../src/core/sandbox';
|
|
3
|
+
|
|
4
|
+
describe('Security Sandbox', () => {
|
|
5
|
+
it('should create sandbox with trust level', () => {
|
|
6
|
+
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
7
|
+
expect(sb.trustLevel).toBe('sandbox');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should restrict shell in sandbox mode', () => {
|
|
11
|
+
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
12
|
+
expect(sb.checkShellAccess()).toBe(false);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should allow shell in certified mode', () => {
|
|
16
|
+
const sb = new Sandbox({ trustLevel: 'certified', agentDir: '/tmp/agent' });
|
|
17
|
+
expect(sb.checkShellAccess()).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should restrict network in sandbox mode', () => {
|
|
21
|
+
const sb = new Sandbox({ trustLevel: 'sandbox', agentDir: '/tmp/agent' });
|
|
22
|
+
expect(sb.checkNetworkAccess('https://api.openai.com')).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should allow network with allowlist', () => {
|
|
26
|
+
const sb = new Sandbox({
|
|
27
|
+
trustLevel: 'sandbox',
|
|
28
|
+
agentDir: '/tmp/agent',
|
|
29
|
+
networkAllowlist: ['api.openai.com'],
|
|
30
|
+
});
|
|
31
|
+
expect(sb.checkNetworkAccess('https://api.openai.com/v1/chat')).toBe(true);
|
|
32
|
+
expect(sb.checkNetworkAccess('https://evil.com')).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should allow wildcard network in listed mode', () => {
|
|
36
|
+
const sb = new Sandbox({ trustLevel: 'listed', agentDir: '/tmp/agent' });
|
|
37
|
+
expect(sb.checkNetworkAccess('https://anything.com')).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return restrictions snapshot', () => {
|
|
41
|
+
const sb = new Sandbox({ trustLevel: 'verified', agentDir: '/tmp/agent' });
|
|
42
|
+
const r = sb.getRestrictions();
|
|
43
|
+
expect(r.shell).toBe(false);
|
|
44
|
+
expect(r.network.allowed.length).toBeGreaterThan(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
package/tests/security.test.ts
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { sanitizeInput, detectInjection, APIKeyManager } from '../src/core/security';
|
|
3
|
-
|
|
4
|
-
describe('Security', () => {
|
|
5
|
-
describe('sanitizeInput', () => {
|
|
6
|
-
it('strips script tags', () => {
|
|
7
|
-
expect(sanitizeInput('<script>alert(1)</script>hello')).not.toContain('<script');
|
|
8
|
-
});
|
|
9
|
-
it('encodes HTML entities', () => {
|
|
10
|
-
const result = sanitizeInput('a < b & c > d');
|
|
11
|
-
expect(result).toContain('<');
|
|
12
|
-
expect(result).toContain('&');
|
|
13
|
-
expect(result).toContain('>');
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
describe('detectInjection', () => {
|
|
18
|
-
it('detects XSS', () => {
|
|
19
|
-
const r = detectInjection('<script>alert(1)</script>');
|
|
20
|
-
expect(r.safe).toBe(false);
|
|
21
|
-
expect(r.threats).toContain('xss');
|
|
22
|
-
});
|
|
23
|
-
it('passes clean input', () => {
|
|
24
|
-
expect(detectInjection('Hello world')).toEqual({ safe: true, threats: [] });
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('APIKeyManager', () => {
|
|
29
|
-
it('add, validate, revoke', () => {
|
|
30
|
-
const mgr = new APIKeyManager();
|
|
31
|
-
mgr.addKey('key1', { label: 'test' });
|
|
32
|
-
expect(mgr.isValid('key1')).toBe(true);
|
|
33
|
-
expect(mgr.isValid('key2')).toBe(false);
|
|
34
|
-
mgr.revokeKey('key1');
|
|
35
|
-
expect(mgr.isValid('key1')).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('rotate key', () => {
|
|
39
|
-
const mgr = new APIKeyManager();
|
|
40
|
-
mgr.addKey('old');
|
|
41
|
-
expect(mgr.rotateKey('old', 'new')).toBe(true);
|
|
42
|
-
expect(mgr.isValid('old')).toBe(false);
|
|
43
|
-
expect(mgr.isValid('new')).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('expires keys', () => {
|
|
47
|
-
const mgr = new APIKeyManager();
|
|
48
|
-
mgr.addKey('expired', { expiresAt: Date.now() - 1000 });
|
|
49
|
-
expect(mgr.isValid('expired')).toBe(false);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('listActive filters', () => {
|
|
53
|
-
const mgr = new APIKeyManager();
|
|
54
|
-
mgr.addKey('a');
|
|
55
|
-
mgr.addKey('b');
|
|
56
|
-
mgr.revokeKey('b');
|
|
57
|
-
expect(mgr.listActive().length).toBe(1);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { sanitizeInput, detectInjection, APIKeyManager } from '../src/core/security';
|
|
3
|
+
|
|
4
|
+
describe('Security', () => {
|
|
5
|
+
describe('sanitizeInput', () => {
|
|
6
|
+
it('strips script tags', () => {
|
|
7
|
+
expect(sanitizeInput('<script>alert(1)</script>hello')).not.toContain('<script');
|
|
8
|
+
});
|
|
9
|
+
it('encodes HTML entities', () => {
|
|
10
|
+
const result = sanitizeInput('a < b & c > d');
|
|
11
|
+
expect(result).toContain('<');
|
|
12
|
+
expect(result).toContain('&');
|
|
13
|
+
expect(result).toContain('>');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('detectInjection', () => {
|
|
18
|
+
it('detects XSS', () => {
|
|
19
|
+
const r = detectInjection('<script>alert(1)</script>');
|
|
20
|
+
expect(r.safe).toBe(false);
|
|
21
|
+
expect(r.threats).toContain('xss');
|
|
22
|
+
});
|
|
23
|
+
it('passes clean input', () => {
|
|
24
|
+
expect(detectInjection('Hello world')).toEqual({ safe: true, threats: [] });
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('APIKeyManager', () => {
|
|
29
|
+
it('add, validate, revoke', () => {
|
|
30
|
+
const mgr = new APIKeyManager();
|
|
31
|
+
mgr.addKey('key1', { label: 'test' });
|
|
32
|
+
expect(mgr.isValid('key1')).toBe(true);
|
|
33
|
+
expect(mgr.isValid('key2')).toBe(false);
|
|
34
|
+
mgr.revokeKey('key1');
|
|
35
|
+
expect(mgr.isValid('key1')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('rotate key', () => {
|
|
39
|
+
const mgr = new APIKeyManager();
|
|
40
|
+
mgr.addKey('old');
|
|
41
|
+
expect(mgr.rotateKey('old', 'new')).toBe(true);
|
|
42
|
+
expect(mgr.isValid('old')).toBe(false);
|
|
43
|
+
expect(mgr.isValid('new')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('expires keys', () => {
|
|
47
|
+
const mgr = new APIKeyManager();
|
|
48
|
+
mgr.addKey('expired', { expiresAt: Date.now() - 1000 });
|
|
49
|
+
expect(mgr.isValid('expired')).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('listActive filters', () => {
|
|
53
|
+
const mgr = new APIKeyManager();
|
|
54
|
+
mgr.addKey('a');
|
|
55
|
+
mgr.addKey('b');
|
|
56
|
+
mgr.revokeKey('b');
|
|
57
|
+
expect(mgr.listActive().length).toBe(1);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { StreamingManager, StreamableResponse } from '../src/core/streaming';
|
|
3
|
+
import type { StreamChunk } from '../src/core/streaming';
|
|
4
|
+
|
|
5
|
+
describe('StreamableResponse', () => {
|
|
6
|
+
it('should collect chunks and build text', () => {
|
|
7
|
+
const stream = new StreamableResponse('test-1');
|
|
8
|
+
stream.push({ id: '0', type: 'text', data: 'Hello ', timestamp: Date.now() });
|
|
9
|
+
stream.push({ id: '1', type: 'text', data: 'World', timestamp: Date.now() });
|
|
10
|
+
expect(stream.getText()).toBe('Hello World');
|
|
11
|
+
expect(stream.length).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should emit chunk events', () => {
|
|
15
|
+
const stream = new StreamableResponse('test-2');
|
|
16
|
+
const received: StreamChunk[] = [];
|
|
17
|
+
stream.on('chunk', (c: StreamChunk) => received.push(c));
|
|
18
|
+
stream.push({ id: '0', type: 'text', data: 'hi', timestamp: Date.now() });
|
|
19
|
+
expect(received.length).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should emit end event', () => {
|
|
23
|
+
const stream = new StreamableResponse('test-3');
|
|
24
|
+
let ended = false;
|
|
25
|
+
stream.on('end', () => { ended = true; });
|
|
26
|
+
stream.end();
|
|
27
|
+
expect(ended).toBe(true);
|
|
28
|
+
expect(stream.isEnded).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should apply backpressure at highWaterMark', () => {
|
|
32
|
+
const stream = new StreamableResponse('test-4', { highWaterMark: 2 });
|
|
33
|
+
const chunk = (): StreamChunk => ({ id: 'c', type: 'text', data: 'x', timestamp: Date.now() });
|
|
34
|
+
stream.push(chunk()); // 1 — ok
|
|
35
|
+
const ok = stream.push(chunk()); // 2 — triggers backpressure
|
|
36
|
+
expect(ok).toBe(false);
|
|
37
|
+
expect(stream.isPaused).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should flush buffer on resume', () => {
|
|
41
|
+
const stream = new StreamableResponse('test-5', { highWaterMark: 1 });
|
|
42
|
+
const received: StreamChunk[] = [];
|
|
43
|
+
stream.on('chunk', (c: StreamChunk) => received.push(c));
|
|
44
|
+
stream.push({ id: '0', type: 'text', data: 'a', timestamp: Date.now() });
|
|
45
|
+
// Now paused — next chunk goes to buffer
|
|
46
|
+
stream.push({ id: '1', type: 'text', data: 'b', timestamp: Date.now() });
|
|
47
|
+
expect(received.length).toBe(1);
|
|
48
|
+
stream.resume();
|
|
49
|
+
expect(received.length).toBe(2);
|
|
50
|
+
expect(stream.isPaused).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should reject pushes after end', () => {
|
|
54
|
+
const stream = new StreamableResponse('test-6');
|
|
55
|
+
stream.end();
|
|
56
|
+
const ok = stream.push({ id: '0', type: 'text', data: 'late', timestamp: Date.now() });
|
|
57
|
+
expect(ok).toBe(false);
|
|
58
|
+
expect(stream.length).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('StreamingManager', () => {
|
|
63
|
+
it('should create and manage streams', () => {
|
|
64
|
+
const mgr = new StreamingManager();
|
|
65
|
+
const stream = mgr.createStream();
|
|
66
|
+
expect(stream.id).toMatch(/^stream_/);
|
|
67
|
+
expect(mgr.activeCount).toBe(1);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should write chunks and end stream', () => {
|
|
71
|
+
const mgr = new StreamingManager();
|
|
72
|
+
const stream = mgr.createStream();
|
|
73
|
+
mgr.writeChunk(stream.id, 'hello');
|
|
74
|
+
mgr.writeChunk(stream.id, ' world');
|
|
75
|
+
mgr.endStream(stream.id);
|
|
76
|
+
expect(stream.getText()).toBe('hello world');
|
|
77
|
+
expect(stream.isEnded).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should return false for unknown stream writes', () => {
|
|
81
|
+
const mgr = new StreamingManager();
|
|
82
|
+
expect(mgr.writeChunk('nonexistent', 'data')).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should format SSE correctly', () => {
|
|
86
|
+
const chunk: StreamChunk = { id: 'c1', type: 'text', data: 'hi', timestamp: 123 };
|
|
87
|
+
const sse = StreamingManager.formatSSE(chunk);
|
|
88
|
+
expect(sse).toContain('event: text');
|
|
89
|
+
expect(sse).toContain('id: c1');
|
|
90
|
+
expect(sse).toContain('"data":"hi"');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should pipe to SSE response', () => {
|
|
94
|
+
const mgr = new StreamingManager();
|
|
95
|
+
const stream = mgr.createStream();
|
|
96
|
+
const written: string[] = [];
|
|
97
|
+
const mockRes = {
|
|
98
|
+
write: (d: string) => { written.push(d); return true; },
|
|
99
|
+
end: vi.fn(),
|
|
100
|
+
setHeader: vi.fn(),
|
|
101
|
+
};
|
|
102
|
+
StreamingManager.pipeSSE(stream, mockRes, { heartbeatInterval: 100_000 });
|
|
103
|
+
mgr.writeChunk(stream.id, 'data');
|
|
104
|
+
mgr.endStream(stream.id);
|
|
105
|
+
expect(written.length).toBeGreaterThan(0);
|
|
106
|
+
expect(mockRes.end).toHaveBeenCalled();
|
|
107
|
+
expect(mockRes.setHeader).toHaveBeenCalledWith('Content-Type', 'text/event-stream');
|
|
108
|
+
});
|
|
109
|
+
});
|
package/tests/templates.test.ts
CHANGED
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { createLegalAssistantConfig, ContractReviewSkill, ComplianceCheckSkill } from '../src/templates/legal-assistant';
|
|
3
|
-
import { createFinancialAdvisorConfig, BudgetAnalysisSkill, FinancialPlanningSkill } from '../src/templates/financial-advisor';
|
|
4
|
-
import { createExecutiveAssistantConfig, CalendarSkill, EmailDraftSkill, MeetingPrepSkill } from '../src/templates/executive-assistant';
|
|
5
|
-
import type { AgentContext, Message, MemoryStore } from '../src/core/types';
|
|
6
|
-
|
|
7
|
-
const mockMemory: MemoryStore = {
|
|
8
|
-
get: async () => null, set: async () => {}, getConversation: async () => [],
|
|
9
|
-
addMessage: async () => {}, clear: async () => {},
|
|
10
|
-
};
|
|
11
|
-
const ctx: AgentContext = { agentName: 'test', sessionId: 's1', messages: [], memory: mockMemory, metadata: {} };
|
|
12
|
-
const msg = (content: string): Message => ({ id: '1', role: 'user', content, timestamp: Date.now() });
|
|
13
|
-
|
|
14
|
-
describe('Legal Assistant Template', () => {
|
|
15
|
-
it('should create valid config', () => {
|
|
16
|
-
const config = createLegalAssistantConfig();
|
|
17
|
-
expect(config.metadata.name).toBe('legal-assistant');
|
|
18
|
-
expect(config.spec.skills).toHaveLength(2);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('ContractReviewSkill matches contract terms', async () => {
|
|
22
|
-
const skill = new ContractReviewSkill();
|
|
23
|
-
const r = await skill.execute(ctx, msg('What is force majeure?'));
|
|
24
|
-
expect(r.handled).toBe(true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('ComplianceCheckSkill matches GDPR', async () => {
|
|
28
|
-
const skill = new ComplianceCheckSkill();
|
|
29
|
-
const r = await skill.execute(ctx, msg('GDPR requirements'));
|
|
30
|
-
expect(r.handled).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('Financial Advisor Template', () => {
|
|
35
|
-
it('should create valid config', () => {
|
|
36
|
-
const config = createFinancialAdvisorConfig();
|
|
37
|
-
expect(config.metadata.name).toBe('financial-advisor');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('BudgetAnalysisSkill matches budget queries', async () => {
|
|
41
|
-
const skill = new BudgetAnalysisSkill();
|
|
42
|
-
const r = await skill.execute(ctx, msg('Help with my budget'));
|
|
43
|
-
expect(r.handled).toBe(true);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('FinancialPlanningSkill matches investment queries', async () => {
|
|
47
|
-
const skill = new FinancialPlanningSkill();
|
|
48
|
-
const r = await skill.execute(ctx, msg('How to invest'));
|
|
49
|
-
expect(r.handled).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('Executive Assistant Template', () => {
|
|
54
|
-
it('should create valid config', () => {
|
|
55
|
-
const config = createExecutiveAssistantConfig();
|
|
56
|
-
expect(config.metadata.name).toBe('executive-assistant');
|
|
57
|
-
expect(config.spec.skills).toHaveLength(3);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('CalendarSkill matches scheduling', async () => {
|
|
61
|
-
const skill = new CalendarSkill();
|
|
62
|
-
const r = await skill.execute(ctx, msg('Schedule a meeting'));
|
|
63
|
-
expect(r.handled).toBe(true);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('EmailDraftSkill matches email queries', async () => {
|
|
67
|
-
const skill = new EmailDraftSkill();
|
|
68
|
-
const r = await skill.execute(ctx, msg('Draft an email'));
|
|
69
|
-
expect(r.handled).toBe(true);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('MeetingPrepSkill matches prep queries', async () => {
|
|
73
|
-
const skill = new MeetingPrepSkill();
|
|
74
|
-
const r = await skill.execute(ctx, msg('Prepare the agenda'));
|
|
75
|
-
expect(r.handled).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { createLegalAssistantConfig, ContractReviewSkill, ComplianceCheckSkill } from '../src/templates/legal-assistant';
|
|
3
|
+
import { createFinancialAdvisorConfig, BudgetAnalysisSkill, FinancialPlanningSkill } from '../src/templates/financial-advisor';
|
|
4
|
+
import { createExecutiveAssistantConfig, CalendarSkill, EmailDraftSkill, MeetingPrepSkill } from '../src/templates/executive-assistant';
|
|
5
|
+
import type { AgentContext, Message, MemoryStore } from '../src/core/types';
|
|
6
|
+
|
|
7
|
+
const mockMemory: MemoryStore = {
|
|
8
|
+
get: async () => null, set: async () => {}, getConversation: async () => [],
|
|
9
|
+
addMessage: async () => {}, clear: async () => {},
|
|
10
|
+
};
|
|
11
|
+
const ctx: AgentContext = { agentName: 'test', sessionId: 's1', messages: [], memory: mockMemory, metadata: {} };
|
|
12
|
+
const msg = (content: string): Message => ({ id: '1', role: 'user', content, timestamp: Date.now() });
|
|
13
|
+
|
|
14
|
+
describe('Legal Assistant Template', () => {
|
|
15
|
+
it('should create valid config', () => {
|
|
16
|
+
const config = createLegalAssistantConfig();
|
|
17
|
+
expect(config.metadata.name).toBe('legal-assistant');
|
|
18
|
+
expect(config.spec.skills).toHaveLength(2);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('ContractReviewSkill matches contract terms', async () => {
|
|
22
|
+
const skill = new ContractReviewSkill();
|
|
23
|
+
const r = await skill.execute(ctx, msg('What is force majeure?'));
|
|
24
|
+
expect(r.handled).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('ComplianceCheckSkill matches GDPR', async () => {
|
|
28
|
+
const skill = new ComplianceCheckSkill();
|
|
29
|
+
const r = await skill.execute(ctx, msg('GDPR requirements'));
|
|
30
|
+
expect(r.handled).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('Financial Advisor Template', () => {
|
|
35
|
+
it('should create valid config', () => {
|
|
36
|
+
const config = createFinancialAdvisorConfig();
|
|
37
|
+
expect(config.metadata.name).toBe('financial-advisor');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('BudgetAnalysisSkill matches budget queries', async () => {
|
|
41
|
+
const skill = new BudgetAnalysisSkill();
|
|
42
|
+
const r = await skill.execute(ctx, msg('Help with my budget'));
|
|
43
|
+
expect(r.handled).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('FinancialPlanningSkill matches investment queries', async () => {
|
|
47
|
+
const skill = new FinancialPlanningSkill();
|
|
48
|
+
const r = await skill.execute(ctx, msg('How to invest'));
|
|
49
|
+
expect(r.handled).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Executive Assistant Template', () => {
|
|
54
|
+
it('should create valid config', () => {
|
|
55
|
+
const config = createExecutiveAssistantConfig();
|
|
56
|
+
expect(config.metadata.name).toBe('executive-assistant');
|
|
57
|
+
expect(config.spec.skills).toHaveLength(3);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('CalendarSkill matches scheduling', async () => {
|
|
61
|
+
const skill = new CalendarSkill();
|
|
62
|
+
const r = await skill.execute(ctx, msg('Schedule a meeting'));
|
|
63
|
+
expect(r.handled).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('EmailDraftSkill matches email queries', async () => {
|
|
67
|
+
const skill = new EmailDraftSkill();
|
|
68
|
+
const r = await skill.execute(ctx, msg('Draft an email'));
|
|
69
|
+
expect(r.handled).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('MeetingPrepSkill matches prep queries', async () => {
|
|
73
|
+
const skill = new MeetingPrepSkill();
|
|
74
|
+
const r = await skill.execute(ctx, msg('Prepare the agenda'));
|
|
75
|
+
expect(r.handled).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
});
|