opc-agent 4.0.44 → 4.1.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/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- package/CHANGELOG.md +48 -48
- package/CONTRIBUTING.md +36 -36
- package/README.zh-CN.md +497 -497
- package/dist/channels/wechat.js +6 -6
- package/dist/cli.js +2 -2
- package/dist/core/runtime.js +18 -0
- package/dist/deploy/index.js +56 -56
- package/dist/providers/index.js +39 -13
- package/dist/studio/server.js +211 -20
- package/dist/studio-ui/index.html +279 -24
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/fix-sidebar.mjs +188 -188
- package/install.ps1 +154 -154
- package/install.sh +164 -164
- package/package.json +1 -1
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/serve-studio.js +13 -13
- package/serve-test.js +25 -25
- package/src/channels/dingtalk.ts +46 -46
- package/src/channels/email.ts +351 -351
- package/src/channels/feishu.ts +349 -349
- package/src/channels/googlechat.ts +42 -42
- package/src/channels/imessage.ts +31 -31
- package/src/channels/irc.ts +82 -82
- package/src/channels/line.ts +32 -32
- package/src/channels/matrix.ts +33 -33
- package/src/channels/mattermost.ts +57 -57
- package/src/channels/msteams.ts +32 -32
- package/src/channels/nostr.ts +32 -32
- package/src/channels/qq.ts +33 -33
- package/src/channels/signal.ts +32 -32
- package/src/channels/sms.ts +33 -33
- package/src/channels/telegram.ts +616 -616
- package/src/channels/twitch.ts +65 -65
- package/src/channels/voice-call.ts +100 -100
- package/src/channels/websocket.ts +399 -399
- package/src/channels/wechat.ts +329 -329
- package/src/channels/whatsapp.ts +32 -32
- package/src/cli/chat.ts +99 -99
- package/src/cli/setup.ts +314 -314
- package/src/cli.ts +2 -2
- package/src/core/agent.ts +476 -476
- package/src/core/api-server.ts +277 -277
- package/src/core/audio.ts +98 -98
- package/src/core/collaboration.ts +275 -275
- package/src/core/context-discovery.ts +85 -85
- package/src/core/context-refs.ts +140 -140
- package/src/core/gateway.ts +106 -106
- package/src/core/heartbeat.ts +51 -51
- package/src/core/hooks.ts +105 -105
- package/src/core/ide-bridge.ts +133 -133
- package/src/core/node-network.ts +86 -86
- package/src/core/profiles.ts +122 -122
- package/src/core/runtime.ts +18 -0
- package/src/core/scheduler.ts +187 -187
- package/src/core/session-manager.ts +137 -137
- package/src/core/subagent.ts +98 -98
- package/src/core/vision.ts +180 -180
- package/src/core/workflow-graph.ts +365 -365
- package/src/daemon.ts +96 -96
- package/src/deploy/index.ts +255 -255
- package/src/doctor.ts +156 -156
- package/src/eval/index.ts +211 -211
- package/src/eval/suites/basic.json +16 -16
- package/src/eval/suites/memory.json +12 -12
- package/src/eval/suites/safety.json +14 -14
- package/src/hub/brain-seed.ts +54 -54
- package/src/hub/client.ts +60 -60
- package/src/mcp/servers/calculator-mcp.ts +65 -65
- package/src/mcp/servers/crypto-mcp.ts +73 -73
- package/src/mcp/servers/database-mcp.ts +72 -72
- package/src/mcp/servers/datetime-mcp.ts +69 -69
- package/src/mcp/servers/filesystem.ts +66 -66
- package/src/mcp/servers/github-mcp.ts +58 -58
- package/src/mcp/servers/index.ts +63 -63
- package/src/mcp/servers/json-mcp.ts +102 -102
- package/src/mcp/servers/memory-mcp.ts +56 -56
- package/src/mcp/servers/regex-mcp.ts +53 -53
- package/src/mcp/servers/web-mcp.ts +49 -49
- package/src/memory/context-compressor.ts +189 -189
- package/src/memory/seed-loader.ts +212 -212
- package/src/memory/user-profiler.ts +215 -215
- package/src/plugins/content-filter.ts +23 -23
- package/src/plugins/logger.ts +18 -18
- package/src/plugins/rate-limiter.ts +38 -38
- package/src/protocols/a2a/client.ts +132 -132
- package/src/protocols/a2a/index.ts +8 -8
- package/src/protocols/a2a/server.ts +333 -333
- package/src/protocols/a2a/types.ts +88 -88
- package/src/protocols/a2a/utils.ts +50 -50
- package/src/protocols/agui/client.ts +83 -83
- package/src/protocols/agui/index.ts +4 -4
- package/src/protocols/agui/server.ts +218 -218
- package/src/protocols/agui/types.ts +153 -153
- package/src/protocols/index.ts +2 -2
- package/src/protocols/mcp/agent-tools.ts +134 -134
- package/src/protocols/mcp/index.ts +8 -8
- package/src/protocols/mcp/server.ts +262 -262
- package/src/protocols/mcp/types.ts +69 -69
- package/src/providers/index.ts +632 -608
- package/src/publish/index.ts +376 -376
- package/src/scheduler/cron-engine.ts +191 -191
- package/src/scheduler/index.ts +2 -2
- package/src/schema/oad.ts +217 -217
- package/src/security/approval.ts +131 -131
- package/src/security/approvals.ts +143 -143
- package/src/security/elevated.ts +105 -105
- package/src/security/guardrails.ts +248 -248
- package/src/security/index.ts +9 -9
- package/src/security/keys.ts +87 -87
- package/src/security/secrets.ts +129 -129
- package/src/skills/builtin/index.ts +408 -408
- package/src/skills/marketplace.ts +113 -113
- package/src/skills/types.ts +42 -42
- package/src/studio/server.ts +209 -22
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +279 -24
- package/src/telemetry/index.ts +324 -324
- package/src/tools/builtin/browser.ts +299 -299
- package/src/tools/builtin/datetime.ts +41 -41
- package/src/tools/builtin/file.ts +107 -107
- package/src/tools/builtin/home-assistant.ts +116 -116
- package/src/tools/builtin/rl-tools.ts +243 -243
- package/src/tools/builtin/shell.ts +43 -43
- package/src/tools/builtin/vision.ts +64 -64
- package/src/tools/builtin/web-search.ts +126 -126
- package/src/tools/builtin/web.ts +35 -35
- package/src/tools/document-processor.ts +213 -213
- package/src/tools/image-generator.ts +150 -150
- package/src/tools/integrations/calendar.ts +73 -73
- package/src/tools/integrations/code-exec.ts +39 -39
- package/src/tools/integrations/csv-analyzer.ts +92 -92
- package/src/tools/integrations/database.ts +44 -44
- package/src/tools/integrations/email-send.ts +76 -76
- package/src/tools/integrations/git-tool.ts +42 -42
- package/src/tools/integrations/github-tool.ts +76 -76
- package/src/tools/integrations/image-gen.ts +56 -56
- package/src/tools/integrations/index.ts +92 -92
- package/src/tools/integrations/jira.ts +83 -83
- package/src/tools/integrations/notion.ts +71 -71
- package/src/tools/integrations/npm-tool.ts +48 -48
- package/src/tools/integrations/pdf-reader.ts +58 -58
- package/src/tools/integrations/slack.ts +65 -65
- package/src/tools/integrations/summarizer.ts +49 -49
- package/src/tools/integrations/translator.ts +48 -48
- package/src/tools/integrations/trello.ts +60 -60
- package/src/tools/integrations/vector-search.ts +42 -42
- package/src/tools/integrations/web-scraper.ts +47 -47
- package/src/tools/integrations/web-search.ts +58 -58
- package/src/tools/integrations/webhook.ts +38 -38
- package/src/tools/mcp-client.ts +131 -131
- package/src/tools/web-scraper.ts +179 -179
- package/src/tools/web-search.ts +180 -180
- package/src/ui/components.ts +127 -127
- package/srv-out.txt +1 -1
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -9
- package/test-agent/README.md +50 -50
- package/test-agent/agent.yaml +23 -23
- package/test-agent/docker-compose.yml +11 -11
- package/test-agent/oad.yaml +31 -31
- package/test-agent/package-lock.json +1492 -1492
- package/test-agent/package.json +17 -17
- package/test-agent/src/index.ts +24 -24
- package/test-agent/src/skills/echo.ts +15 -15
- package/test-agent/tsconfig.json +24 -24
- package/test-full.js +43 -43
- package/test-sidebar.js +22 -22
- package/test-studio3.js +75 -75
- package/test-studio4.js +41 -41
- package/tests/a2a-protocol.test.ts +285 -285
- package/tests/agui-protocol.test.ts +246 -246
- package/tests/api-server.test.ts +148 -148
- package/tests/approvals.test.ts +89 -89
- package/tests/audio.test.ts +40 -40
- package/tests/brain-seed-extended.test.ts +490 -490
- package/tests/brain-seed.test.ts +239 -239
- package/tests/browser.test.ts +179 -179
- package/tests/channels/discord.test.ts +79 -79
- package/tests/channels/email.test.ts +148 -148
- package/tests/channels/feishu.test.ts +123 -123
- package/tests/channels/telegram.test.ts +129 -129
- package/tests/channels/websocket.test.ts +53 -53
- package/tests/channels/wechat.test.ts +170 -170
- package/tests/channels-extra.test.ts +45 -45
- package/tests/chat-cli.test.ts +160 -160
- package/tests/cli.test.ts +46 -46
- package/tests/context-compressor.test.ts +172 -172
- package/tests/context-refs.test.ts +121 -121
- package/tests/cron-engine.test.ts +101 -101
- package/tests/daemon.test.ts +135 -135
- package/tests/deepbrain-wire.test.ts +234 -234
- package/tests/deploy-and-dag.test.ts +196 -196
- package/tests/doctor.test.ts +38 -38
- package/tests/document-processor.test.ts +69 -69
- package/tests/e2e-nocode.test.ts +442 -442
- package/tests/elevated.test.ts +69 -69
- package/tests/eval.test.ts +173 -173
- package/tests/gateway.test.ts +63 -63
- package/tests/guardrails.test.ts +177 -177
- package/tests/home-assistant.test.ts +40 -40
- package/tests/hooks.test.ts +79 -79
- package/tests/ide-bridge.test.ts +38 -38
- package/tests/image-generator.test.ts +84 -84
- package/tests/init-role.test.ts +124 -124
- package/tests/integrations.test.ts +249 -249
- package/tests/mcp-client.test.ts +92 -92
- package/tests/mcp-server.test.ts +178 -178
- package/tests/mcp-servers.test.ts +260 -260
- package/tests/node-network.test.ts +74 -74
- package/tests/plugin-a2a-enhanced.test.ts +230 -230
- package/tests/profiles.test.ts +61 -61
- package/tests/publish.test.ts +231 -231
- package/tests/rl-tools.test.ts +93 -93
- package/tests/sandbox-manager.test.ts +46 -46
- package/tests/scheduler.test.ts +200 -200
- package/tests/secrets.test.ts +107 -107
- package/tests/security-enhanced.test.ts +233 -233
- package/tests/settings-api.test.ts +148 -148
- package/tests/setup.test.ts +73 -73
- package/tests/subagent.test.ts +193 -193
- package/tests/telegram-discord.test.ts +60 -60
- package/tests/telemetry.test.ts +186 -186
- package/tests/user-profiler.test.ts +169 -169
- package/tests/v090-features.test.ts +254 -254
- package/tests/vision.test.ts +61 -61
- package/tests/voice-call.test.ts +47 -47
- package/tests/voice-enhanced.test.ts +169 -169
- package/tests/voice-interaction.test.ts +38 -38
- package/tests/web-search.test.ts +155 -155
- package/tests/workflow-graph.test.ts +279 -279
- package/tutorial/customer-service-agent/README.md +612 -612
- package/tutorial/customer-service-agent/SOUL.md +26 -26
- package/tutorial/customer-service-agent/agent.yaml +63 -63
- package/tutorial/customer-service-agent/package.json +19 -19
- package/tutorial/customer-service-agent/src/index.ts +69 -69
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
- package/tutorial/customer-service-agent/tsconfig.json +14 -14
package/tests/voice-call.test.ts
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { VoiceCallManager } from '../src/channels/voice-call';
|
|
3
|
-
|
|
4
|
-
describe('VoiceCallManager', () => {
|
|
5
|
-
it('should create with config', () => {
|
|
6
|
-
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x', token: 'y' } });
|
|
7
|
-
expect(mgr).toBeInstanceOf(VoiceCallManager);
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('should throw without credentials on startCall', async () => {
|
|
11
|
-
const mgr = new VoiceCallManager({ provider: 'twilio' });
|
|
12
|
-
await expect(mgr.startCall('+1234')).rejects.toThrow('credentials');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('should start and end a call', async () => {
|
|
16
|
-
const mgr = new VoiceCallManager({ provider: 'webrtc', credentials: { key: 'val' } });
|
|
17
|
-
const callId = await mgr.startCall('+1234');
|
|
18
|
-
expect(callId).toBeTruthy();
|
|
19
|
-
expect(mgr.getCallStatus(callId)).toBe('ringing');
|
|
20
|
-
await mgr.endCall(callId);
|
|
21
|
-
expect(mgr.getCallStatus(callId)).toBe('ended');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should handle incoming calls', async () => {
|
|
25
|
-
const mgr = new VoiceCallManager({ provider: 'sip', credentials: { key: 'val' } });
|
|
26
|
-
const result = await new Promise<{ callId: string; from: string }>((resolve) => {
|
|
27
|
-
mgr.onIncoming((callId, from) => resolve({ callId, from }));
|
|
28
|
-
mgr.simulateIncoming('+5678');
|
|
29
|
-
});
|
|
30
|
-
expect(result.callId).toBeTruthy();
|
|
31
|
-
expect(result.from).toBe('+5678');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should list active calls', async () => {
|
|
35
|
-
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x', token: 'y' } });
|
|
36
|
-
const id1 = await mgr.startCall('+111');
|
|
37
|
-
const id2 = await mgr.startCall('+222');
|
|
38
|
-
expect(mgr.listActiveCalls().length).toBe(2);
|
|
39
|
-
await mgr.endCall(id1);
|
|
40
|
-
expect(mgr.listActiveCalls().length).toBe(1);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should throw on unknown callId', () => {
|
|
44
|
-
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x' } });
|
|
45
|
-
expect(() => mgr.getCallStatus('unknown')).toThrow('not found');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { VoiceCallManager } from '../src/channels/voice-call';
|
|
3
|
+
|
|
4
|
+
describe('VoiceCallManager', () => {
|
|
5
|
+
it('should create with config', () => {
|
|
6
|
+
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x', token: 'y' } });
|
|
7
|
+
expect(mgr).toBeInstanceOf(VoiceCallManager);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should throw without credentials on startCall', async () => {
|
|
11
|
+
const mgr = new VoiceCallManager({ provider: 'twilio' });
|
|
12
|
+
await expect(mgr.startCall('+1234')).rejects.toThrow('credentials');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should start and end a call', async () => {
|
|
16
|
+
const mgr = new VoiceCallManager({ provider: 'webrtc', credentials: { key: 'val' } });
|
|
17
|
+
const callId = await mgr.startCall('+1234');
|
|
18
|
+
expect(callId).toBeTruthy();
|
|
19
|
+
expect(mgr.getCallStatus(callId)).toBe('ringing');
|
|
20
|
+
await mgr.endCall(callId);
|
|
21
|
+
expect(mgr.getCallStatus(callId)).toBe('ended');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should handle incoming calls', async () => {
|
|
25
|
+
const mgr = new VoiceCallManager({ provider: 'sip', credentials: { key: 'val' } });
|
|
26
|
+
const result = await new Promise<{ callId: string; from: string }>((resolve) => {
|
|
27
|
+
mgr.onIncoming((callId, from) => resolve({ callId, from }));
|
|
28
|
+
mgr.simulateIncoming('+5678');
|
|
29
|
+
});
|
|
30
|
+
expect(result.callId).toBeTruthy();
|
|
31
|
+
expect(result.from).toBe('+5678');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should list active calls', async () => {
|
|
35
|
+
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x', token: 'y' } });
|
|
36
|
+
const id1 = await mgr.startCall('+111');
|
|
37
|
+
const id2 = await mgr.startCall('+222');
|
|
38
|
+
expect(mgr.listActiveCalls().length).toBe(2);
|
|
39
|
+
await mgr.endCall(id1);
|
|
40
|
+
expect(mgr.listActiveCalls().length).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should throw on unknown callId', () => {
|
|
44
|
+
const mgr = new VoiceCallManager({ provider: 'twilio', credentials: { sid: 'x' } });
|
|
45
|
+
expect(() => mgr.getCallStatus('unknown')).toThrow('not found');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -1,169 +1,169 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { VoiceChannel, createVoiceProviders, EdgeTTSProvider, WhisperSTTProvider, OpenAITTSProvider } from '../src/channels/voice';
|
|
3
|
-
import type { STTProvider, TTSProvider, VoiceConfig } from '../src/channels/voice';
|
|
4
|
-
|
|
5
|
-
describe('VoiceChannel Enhanced', () => {
|
|
6
|
-
// ── VoiceConfig Parsing ───────────────────────────────────
|
|
7
|
-
|
|
8
|
-
it('should parse VoiceConfig with edge-tts', () => {
|
|
9
|
-
const config: VoiceConfig = {
|
|
10
|
-
sttProvider: 'whisper',
|
|
11
|
-
ttsProvider: 'edge-tts',
|
|
12
|
-
voice: 'en-US-GuyNeural',
|
|
13
|
-
language: 'en',
|
|
14
|
-
};
|
|
15
|
-
const { stt, tts } = createVoiceProviders(config);
|
|
16
|
-
expect(stt).toBeUndefined(); // no API key
|
|
17
|
-
expect(tts).toBeDefined();
|
|
18
|
-
expect(tts!.name).toBe('edge-tts');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('should parse VoiceConfig with whisper + openai-tts', () => {
|
|
22
|
-
const config: VoiceConfig = {
|
|
23
|
-
sttProvider: 'whisper',
|
|
24
|
-
ttsProvider: 'openai-tts',
|
|
25
|
-
sttApiKey: 'sk-test',
|
|
26
|
-
ttsApiKey: 'sk-test',
|
|
27
|
-
voice: 'nova',
|
|
28
|
-
};
|
|
29
|
-
const { stt, tts } = createVoiceProviders(config);
|
|
30
|
-
expect(stt).toBeDefined();
|
|
31
|
-
expect(stt!.name).toBe('whisper');
|
|
32
|
-
expect(tts).toBeDefined();
|
|
33
|
-
expect(tts!.name).toBe('openai-tts');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should return undefined for web-speech (node-only)', () => {
|
|
37
|
-
const { stt } = createVoiceProviders({
|
|
38
|
-
sttProvider: 'web-speech',
|
|
39
|
-
ttsProvider: 'edge-tts',
|
|
40
|
-
});
|
|
41
|
-
expect(stt).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
// ── Transcribe (mock) ─────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
it('should transcribe audio with mock STT provider', async () => {
|
|
47
|
-
const mockSTT: STTProvider = {
|
|
48
|
-
name: 'mock-stt',
|
|
49
|
-
transcribe: vi.fn().mockResolvedValue('Hello world'),
|
|
50
|
-
};
|
|
51
|
-
const channel = new VoiceChannel({ sttProvider: mockSTT });
|
|
52
|
-
const text = await channel.transcribe(Buffer.from('audio-data'));
|
|
53
|
-
expect(text).toBe('Hello world');
|
|
54
|
-
expect(mockSTT.transcribe).toHaveBeenCalled();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it('should throw if no STT provider for transcribe', async () => {
|
|
58
|
-
const channel = new VoiceChannel();
|
|
59
|
-
await expect(channel.transcribe(Buffer.from('audio'))).rejects.toThrow('No STT provider');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// ── Synthesize (mock) ─────────────────────────────────────
|
|
63
|
-
|
|
64
|
-
it('should synthesize text with mock TTS provider', async () => {
|
|
65
|
-
const mockTTS: TTSProvider = {
|
|
66
|
-
name: 'mock-tts',
|
|
67
|
-
synthesize: vi.fn().mockResolvedValue(Buffer.from('audio-output')),
|
|
68
|
-
};
|
|
69
|
-
const channel = new VoiceChannel({ ttsProvider: mockTTS });
|
|
70
|
-
const audio = await channel.synthesize('Hello world');
|
|
71
|
-
expect(audio).toEqual(Buffer.from('audio-output'));
|
|
72
|
-
expect(mockTTS.synthesize).toHaveBeenCalledWith('Hello world', { voice: undefined });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should throw if no TTS provider for synthesize', async () => {
|
|
76
|
-
const channel = new VoiceChannel();
|
|
77
|
-
await expect(channel.synthesize('Hello')).rejects.toThrow('No TTS provider');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// ── Conversation Mode ─────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
it('should start and stop conversation mode', async () => {
|
|
83
|
-
const channel = new VoiceChannel();
|
|
84
|
-
await channel.startConversation(async (text) => `Echo: ${text}`);
|
|
85
|
-
expect(channel.isConversationActive()).toBe(true);
|
|
86
|
-
channel.stopConversation();
|
|
87
|
-
expect(channel.isConversationActive()).toBe(false);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// ── processConversationTurn ───────────────────────────────
|
|
91
|
-
|
|
92
|
-
it('should process a conversation turn', async () => {
|
|
93
|
-
const mockSTT: STTProvider = {
|
|
94
|
-
name: 'mock-stt',
|
|
95
|
-
transcribe: vi.fn().mockResolvedValue('What time is it?'),
|
|
96
|
-
};
|
|
97
|
-
const mockTTS: TTSProvider = {
|
|
98
|
-
name: 'mock-tts',
|
|
99
|
-
synthesize: vi.fn().mockResolvedValue(Buffer.from('audio-response')),
|
|
100
|
-
};
|
|
101
|
-
const channel = new VoiceChannel({ sttProvider: mockSTT, ttsProvider: mockTTS });
|
|
102
|
-
|
|
103
|
-
const result = await channel.processConversationTurn(
|
|
104
|
-
Buffer.from('audio'),
|
|
105
|
-
async (text) => `It is 3pm, you said: ${text}`,
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
expect(result.text).toBe('What time is it?');
|
|
109
|
-
expect(result.response).toContain('3pm');
|
|
110
|
-
expect(result.audioResponse).toBeDefined();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// ── processAudio with handler ─────────────────────────────
|
|
114
|
-
|
|
115
|
-
it('should process audio end-to-end', async () => {
|
|
116
|
-
const mockSTT: STTProvider = {
|
|
117
|
-
name: 'mock-stt',
|
|
118
|
-
transcribe: vi.fn().mockResolvedValue('Hello agent'),
|
|
119
|
-
};
|
|
120
|
-
const mockTTS: TTSProvider = {
|
|
121
|
-
name: 'mock-tts',
|
|
122
|
-
synthesize: vi.fn().mockResolvedValue(Buffer.from('tts-audio')),
|
|
123
|
-
};
|
|
124
|
-
const channel = new VoiceChannel({ sttProvider: mockSTT, ttsProvider: mockTTS });
|
|
125
|
-
channel.onMessage(async (msg) => ({
|
|
126
|
-
id: 'r1',
|
|
127
|
-
role: 'assistant' as const,
|
|
128
|
-
content: `Reply to: ${msg.content}`,
|
|
129
|
-
timestamp: Date.now(),
|
|
130
|
-
}));
|
|
131
|
-
|
|
132
|
-
const result = await channel.processAudio(Buffer.from('raw-audio'));
|
|
133
|
-
expect(result.text).toBe('Hello agent');
|
|
134
|
-
expect(result.response).toBe('Reply to: Hello agent');
|
|
135
|
-
expect(result.audioResponse).toBeDefined();
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ── Provider instances ────────────────────────────────────
|
|
139
|
-
|
|
140
|
-
it('should create EdgeTTSProvider with default voice', () => {
|
|
141
|
-
const provider = new EdgeTTSProvider();
|
|
142
|
-
expect(provider.name).toBe('edge-tts');
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should create EdgeTTSProvider with custom voice', () => {
|
|
146
|
-
const provider = new EdgeTTSProvider('zh-CN-XiaoxiaoNeural');
|
|
147
|
-
expect(provider.name).toBe('edge-tts');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// ── Channel lifecycle ─────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
it('should start and stop channel', async () => {
|
|
153
|
-
const channel = new VoiceChannel();
|
|
154
|
-
expect(channel.isRunning()).toBe(false);
|
|
155
|
-
await channel.start();
|
|
156
|
-
expect(channel.isRunning()).toBe(true);
|
|
157
|
-
await channel.stop();
|
|
158
|
-
expect(channel.isRunning()).toBe(false);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it('should set STT/TTS providers dynamically', () => {
|
|
162
|
-
const channel = new VoiceChannel();
|
|
163
|
-
const mockSTT: STTProvider = { name: 'test-stt', transcribe: vi.fn() };
|
|
164
|
-
const mockTTS: TTSProvider = { name: 'test-tts', synthesize: vi.fn() };
|
|
165
|
-
channel.setSTTProvider(mockSTT);
|
|
166
|
-
channel.setTTSProvider(mockTTS);
|
|
167
|
-
// No error means success
|
|
168
|
-
});
|
|
169
|
-
});
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { VoiceChannel, createVoiceProviders, EdgeTTSProvider, WhisperSTTProvider, OpenAITTSProvider } from '../src/channels/voice';
|
|
3
|
+
import type { STTProvider, TTSProvider, VoiceConfig } from '../src/channels/voice';
|
|
4
|
+
|
|
5
|
+
describe('VoiceChannel Enhanced', () => {
|
|
6
|
+
// ── VoiceConfig Parsing ───────────────────────────────────
|
|
7
|
+
|
|
8
|
+
it('should parse VoiceConfig with edge-tts', () => {
|
|
9
|
+
const config: VoiceConfig = {
|
|
10
|
+
sttProvider: 'whisper',
|
|
11
|
+
ttsProvider: 'edge-tts',
|
|
12
|
+
voice: 'en-US-GuyNeural',
|
|
13
|
+
language: 'en',
|
|
14
|
+
};
|
|
15
|
+
const { stt, tts } = createVoiceProviders(config);
|
|
16
|
+
expect(stt).toBeUndefined(); // no API key
|
|
17
|
+
expect(tts).toBeDefined();
|
|
18
|
+
expect(tts!.name).toBe('edge-tts');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should parse VoiceConfig with whisper + openai-tts', () => {
|
|
22
|
+
const config: VoiceConfig = {
|
|
23
|
+
sttProvider: 'whisper',
|
|
24
|
+
ttsProvider: 'openai-tts',
|
|
25
|
+
sttApiKey: 'sk-test',
|
|
26
|
+
ttsApiKey: 'sk-test',
|
|
27
|
+
voice: 'nova',
|
|
28
|
+
};
|
|
29
|
+
const { stt, tts } = createVoiceProviders(config);
|
|
30
|
+
expect(stt).toBeDefined();
|
|
31
|
+
expect(stt!.name).toBe('whisper');
|
|
32
|
+
expect(tts).toBeDefined();
|
|
33
|
+
expect(tts!.name).toBe('openai-tts');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return undefined for web-speech (node-only)', () => {
|
|
37
|
+
const { stt } = createVoiceProviders({
|
|
38
|
+
sttProvider: 'web-speech',
|
|
39
|
+
ttsProvider: 'edge-tts',
|
|
40
|
+
});
|
|
41
|
+
expect(stt).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ── Transcribe (mock) ─────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
it('should transcribe audio with mock STT provider', async () => {
|
|
47
|
+
const mockSTT: STTProvider = {
|
|
48
|
+
name: 'mock-stt',
|
|
49
|
+
transcribe: vi.fn().mockResolvedValue('Hello world'),
|
|
50
|
+
};
|
|
51
|
+
const channel = new VoiceChannel({ sttProvider: mockSTT });
|
|
52
|
+
const text = await channel.transcribe(Buffer.from('audio-data'));
|
|
53
|
+
expect(text).toBe('Hello world');
|
|
54
|
+
expect(mockSTT.transcribe).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should throw if no STT provider for transcribe', async () => {
|
|
58
|
+
const channel = new VoiceChannel();
|
|
59
|
+
await expect(channel.transcribe(Buffer.from('audio'))).rejects.toThrow('No STT provider');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ── Synthesize (mock) ─────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
it('should synthesize text with mock TTS provider', async () => {
|
|
65
|
+
const mockTTS: TTSProvider = {
|
|
66
|
+
name: 'mock-tts',
|
|
67
|
+
synthesize: vi.fn().mockResolvedValue(Buffer.from('audio-output')),
|
|
68
|
+
};
|
|
69
|
+
const channel = new VoiceChannel({ ttsProvider: mockTTS });
|
|
70
|
+
const audio = await channel.synthesize('Hello world');
|
|
71
|
+
expect(audio).toEqual(Buffer.from('audio-output'));
|
|
72
|
+
expect(mockTTS.synthesize).toHaveBeenCalledWith('Hello world', { voice: undefined });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should throw if no TTS provider for synthesize', async () => {
|
|
76
|
+
const channel = new VoiceChannel();
|
|
77
|
+
await expect(channel.synthesize('Hello')).rejects.toThrow('No TTS provider');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// ── Conversation Mode ─────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
it('should start and stop conversation mode', async () => {
|
|
83
|
+
const channel = new VoiceChannel();
|
|
84
|
+
await channel.startConversation(async (text) => `Echo: ${text}`);
|
|
85
|
+
expect(channel.isConversationActive()).toBe(true);
|
|
86
|
+
channel.stopConversation();
|
|
87
|
+
expect(channel.isConversationActive()).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// ── processConversationTurn ───────────────────────────────
|
|
91
|
+
|
|
92
|
+
it('should process a conversation turn', async () => {
|
|
93
|
+
const mockSTT: STTProvider = {
|
|
94
|
+
name: 'mock-stt',
|
|
95
|
+
transcribe: vi.fn().mockResolvedValue('What time is it?'),
|
|
96
|
+
};
|
|
97
|
+
const mockTTS: TTSProvider = {
|
|
98
|
+
name: 'mock-tts',
|
|
99
|
+
synthesize: vi.fn().mockResolvedValue(Buffer.from('audio-response')),
|
|
100
|
+
};
|
|
101
|
+
const channel = new VoiceChannel({ sttProvider: mockSTT, ttsProvider: mockTTS });
|
|
102
|
+
|
|
103
|
+
const result = await channel.processConversationTurn(
|
|
104
|
+
Buffer.from('audio'),
|
|
105
|
+
async (text) => `It is 3pm, you said: ${text}`,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result.text).toBe('What time is it?');
|
|
109
|
+
expect(result.response).toContain('3pm');
|
|
110
|
+
expect(result.audioResponse).toBeDefined();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── processAudio with handler ─────────────────────────────
|
|
114
|
+
|
|
115
|
+
it('should process audio end-to-end', async () => {
|
|
116
|
+
const mockSTT: STTProvider = {
|
|
117
|
+
name: 'mock-stt',
|
|
118
|
+
transcribe: vi.fn().mockResolvedValue('Hello agent'),
|
|
119
|
+
};
|
|
120
|
+
const mockTTS: TTSProvider = {
|
|
121
|
+
name: 'mock-tts',
|
|
122
|
+
synthesize: vi.fn().mockResolvedValue(Buffer.from('tts-audio')),
|
|
123
|
+
};
|
|
124
|
+
const channel = new VoiceChannel({ sttProvider: mockSTT, ttsProvider: mockTTS });
|
|
125
|
+
channel.onMessage(async (msg) => ({
|
|
126
|
+
id: 'r1',
|
|
127
|
+
role: 'assistant' as const,
|
|
128
|
+
content: `Reply to: ${msg.content}`,
|
|
129
|
+
timestamp: Date.now(),
|
|
130
|
+
}));
|
|
131
|
+
|
|
132
|
+
const result = await channel.processAudio(Buffer.from('raw-audio'));
|
|
133
|
+
expect(result.text).toBe('Hello agent');
|
|
134
|
+
expect(result.response).toBe('Reply to: Hello agent');
|
|
135
|
+
expect(result.audioResponse).toBeDefined();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ── Provider instances ────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
it('should create EdgeTTSProvider with default voice', () => {
|
|
141
|
+
const provider = new EdgeTTSProvider();
|
|
142
|
+
expect(provider.name).toBe('edge-tts');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should create EdgeTTSProvider with custom voice', () => {
|
|
146
|
+
const provider = new EdgeTTSProvider('zh-CN-XiaoxiaoNeural');
|
|
147
|
+
expect(provider.name).toBe('edge-tts');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// ── Channel lifecycle ─────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
it('should start and stop channel', async () => {
|
|
153
|
+
const channel = new VoiceChannel();
|
|
154
|
+
expect(channel.isRunning()).toBe(false);
|
|
155
|
+
await channel.start();
|
|
156
|
+
expect(channel.isRunning()).toBe(true);
|
|
157
|
+
await channel.stop();
|
|
158
|
+
expect(channel.isRunning()).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should set STT/TTS providers dynamically', () => {
|
|
162
|
+
const channel = new VoiceChannel();
|
|
163
|
+
const mockSTT: STTProvider = { name: 'test-stt', transcribe: vi.fn() };
|
|
164
|
+
const mockTTS: TTSProvider = { name: 'test-tts', synthesize: vi.fn() };
|
|
165
|
+
channel.setSTTProvider(mockSTT);
|
|
166
|
+
channel.setTTSProvider(mockTTS);
|
|
167
|
+
// No error means success
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Voice interaction tests — browser-only APIs (Web Speech API).
|
|
5
|
-
* These tests verify the integration logic rather than the browser APIs themselves.
|
|
6
|
-
*/
|
|
7
|
-
describe('Voice Interaction', () => {
|
|
8
|
-
it('should define voice feature flags', () => {
|
|
9
|
-
// In a browser context, these would be available
|
|
10
|
-
// Here we just verify our integration assumptions
|
|
11
|
-
expect(typeof globalThis).toBe('object');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should handle missing SpeechRecognition gracefully', () => {
|
|
15
|
-
// Simulate no SpeechRecognition API
|
|
16
|
-
const SpeechRecognition = (globalThis as any).SpeechRecognition || (globalThis as any).webkitSpeechRecognition;
|
|
17
|
-
expect(SpeechRecognition).toBeUndefined(); // Not available in Node
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should handle missing speechSynthesis gracefully', () => {
|
|
21
|
-
const synth = (globalThis as any).speechSynthesis;
|
|
22
|
-
expect(synth).toBeUndefined(); // Not available in Node
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('Voice TTS button injection', () => {
|
|
27
|
-
it('should create a button element with correct attributes', () => {
|
|
28
|
-
// Simulate DOM creation logic
|
|
29
|
-
const btn = {
|
|
30
|
-
className: 'tts-btn',
|
|
31
|
-
textContent: '🔊',
|
|
32
|
-
title: 'Read aloud',
|
|
33
|
-
};
|
|
34
|
-
expect(btn.className).toBe('tts-btn');
|
|
35
|
-
expect(btn.textContent).toBe('🔊');
|
|
36
|
-
expect(btn.title).toBe('Read aloud');
|
|
37
|
-
});
|
|
38
|
-
});
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Voice interaction tests — browser-only APIs (Web Speech API).
|
|
5
|
+
* These tests verify the integration logic rather than the browser APIs themselves.
|
|
6
|
+
*/
|
|
7
|
+
describe('Voice Interaction', () => {
|
|
8
|
+
it('should define voice feature flags', () => {
|
|
9
|
+
// In a browser context, these would be available
|
|
10
|
+
// Here we just verify our integration assumptions
|
|
11
|
+
expect(typeof globalThis).toBe('object');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('should handle missing SpeechRecognition gracefully', () => {
|
|
15
|
+
// Simulate no SpeechRecognition API
|
|
16
|
+
const SpeechRecognition = (globalThis as any).SpeechRecognition || (globalThis as any).webkitSpeechRecognition;
|
|
17
|
+
expect(SpeechRecognition).toBeUndefined(); // Not available in Node
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should handle missing speechSynthesis gracefully', () => {
|
|
21
|
+
const synth = (globalThis as any).speechSynthesis;
|
|
22
|
+
expect(synth).toBeUndefined(); // Not available in Node
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('Voice TTS button injection', () => {
|
|
27
|
+
it('should create a button element with correct attributes', () => {
|
|
28
|
+
// Simulate DOM creation logic
|
|
29
|
+
const btn = {
|
|
30
|
+
className: 'tts-btn',
|
|
31
|
+
textContent: '🔊',
|
|
32
|
+
title: 'Read aloud',
|
|
33
|
+
};
|
|
34
|
+
expect(btn.className).toBe('tts-btn');
|
|
35
|
+
expect(btn.textContent).toBe('🔊');
|
|
36
|
+
expect(btn.title).toBe('Read aloud');
|
|
37
|
+
});
|
|
38
|
+
});
|