signalwire-agents 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/CLAUDE.md +112 -0
- package/README.md +417 -0
- package/dist/AgentBase.d.ts +481 -0
- package/dist/AgentBase.js +1155 -0
- package/dist/AgentBase.js.map +1 -0
- package/dist/AgentServer.d.ts +60 -0
- package/dist/AgentServer.js +128 -0
- package/dist/AgentServer.js.map +1 -0
- package/dist/AuthHandler.d.ts +56 -0
- package/dist/AuthHandler.js +119 -0
- package/dist/AuthHandler.js.map +1 -0
- package/dist/ConfigLoader.d.ts +68 -0
- package/dist/ConfigLoader.js +150 -0
- package/dist/ConfigLoader.js.map +1 -0
- package/dist/ContextBuilder.d.ts +431 -0
- package/dist/ContextBuilder.js +751 -0
- package/dist/ContextBuilder.js.map +1 -0
- package/dist/DataMap.d.ts +179 -0
- package/dist/DataMap.js +321 -0
- package/dist/DataMap.js.map +1 -0
- package/dist/Logger.d.ts +91 -0
- package/dist/Logger.js +191 -0
- package/dist/Logger.js.map +1 -0
- package/dist/PomBuilder.d.ts +144 -0
- package/dist/PomBuilder.js +249 -0
- package/dist/PomBuilder.js.map +1 -0
- package/dist/PromptManager.d.ts +85 -0
- package/dist/PromptManager.js +108 -0
- package/dist/PromptManager.js.map +1 -0
- package/dist/SchemaUtils.d.ts +42 -0
- package/dist/SchemaUtils.js +148 -0
- package/dist/SchemaUtils.js.map +1 -0
- package/dist/ServerlessAdapter.d.ts +100 -0
- package/dist/ServerlessAdapter.js +166 -0
- package/dist/ServerlessAdapter.js.map +1 -0
- package/dist/SessionManager.d.ts +86 -0
- package/dist/SessionManager.js +155 -0
- package/dist/SessionManager.js.map +1 -0
- package/dist/SslConfig.d.ts +77 -0
- package/dist/SslConfig.js +100 -0
- package/dist/SslConfig.js.map +1 -0
- package/dist/SwaigFunction.d.ts +88 -0
- package/dist/SwaigFunction.js +125 -0
- package/dist/SwaigFunction.js.map +1 -0
- package/dist/SwaigFunctionResult.d.ts +442 -0
- package/dist/SwaigFunctionResult.js +735 -0
- package/dist/SwaigFunctionResult.js.map +1 -0
- package/dist/SwmlBuilder.d.ts +37 -0
- package/dist/SwmlBuilder.js +55 -0
- package/dist/SwmlBuilder.js.map +1 -0
- package/dist/cli/agent-loader.d.ts +22 -0
- package/dist/cli/agent-loader.js +106 -0
- package/dist/cli/agent-loader.js.map +1 -0
- package/dist/cli/mock-data.d.ts +37 -0
- package/dist/cli/mock-data.js +79 -0
- package/dist/cli/mock-data.js.map +1 -0
- package/dist/cli/swaig-test.d.ts +32 -0
- package/dist/cli/swaig-test.js +405 -0
- package/dist/cli/swaig-test.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/prefabs/ConciergeAgent.d.ts +64 -0
- package/dist/prefabs/ConciergeAgent.js +188 -0
- package/dist/prefabs/ConciergeAgent.js.map +1 -0
- package/dist/prefabs/FAQBotAgent.d.ts +68 -0
- package/dist/prefabs/FAQBotAgent.js +206 -0
- package/dist/prefabs/FAQBotAgent.js.map +1 -0
- package/dist/prefabs/InfoGathererAgent.d.ts +73 -0
- package/dist/prefabs/InfoGathererAgent.js +202 -0
- package/dist/prefabs/InfoGathererAgent.js.map +1 -0
- package/dist/prefabs/ReceptionistAgent.d.ts +65 -0
- package/dist/prefabs/ReceptionistAgent.js +205 -0
- package/dist/prefabs/ReceptionistAgent.js.map +1 -0
- package/dist/prefabs/SurveyAgent.d.ts +84 -0
- package/dist/prefabs/SurveyAgent.js +324 -0
- package/dist/prefabs/SurveyAgent.js.map +1 -0
- package/dist/prefabs/index.d.ts +13 -0
- package/dist/prefabs/index.js +14 -0
- package/dist/prefabs/index.js.map +1 -0
- package/dist/skills/SkillBase.d.ts +134 -0
- package/dist/skills/SkillBase.js +105 -0
- package/dist/skills/SkillBase.js.map +1 -0
- package/dist/skills/SkillManager.d.ts +83 -0
- package/dist/skills/SkillManager.js +159 -0
- package/dist/skills/SkillManager.js.map +1 -0
- package/dist/skills/SkillRegistry.d.ts +105 -0
- package/dist/skills/SkillRegistry.js +195 -0
- package/dist/skills/SkillRegistry.js.map +1 -0
- package/dist/skills/builtin/api_ninjas_trivia.d.ts +35 -0
- package/dist/skills/builtin/api_ninjas_trivia.js +161 -0
- package/dist/skills/builtin/api_ninjas_trivia.js.map +1 -0
- package/dist/skills/builtin/claude_skills.d.ts +34 -0
- package/dist/skills/builtin/claude_skills.js +156 -0
- package/dist/skills/builtin/claude_skills.js.map +1 -0
- package/dist/skills/builtin/custom_skills.d.ts +59 -0
- package/dist/skills/builtin/custom_skills.js +231 -0
- package/dist/skills/builtin/custom_skills.js.map +1 -0
- package/dist/skills/builtin/datasphere.d.ts +34 -0
- package/dist/skills/builtin/datasphere.js +163 -0
- package/dist/skills/builtin/datasphere.js.map +1 -0
- package/dist/skills/builtin/datasphere_serverless.d.ts +54 -0
- package/dist/skills/builtin/datasphere_serverless.js +155 -0
- package/dist/skills/builtin/datasphere_serverless.js.map +1 -0
- package/dist/skills/builtin/datetime.d.ts +31 -0
- package/dist/skills/builtin/datetime.js +95 -0
- package/dist/skills/builtin/datetime.js.map +1 -0
- package/dist/skills/builtin/google_maps.d.ts +33 -0
- package/dist/skills/builtin/google_maps.js +233 -0
- package/dist/skills/builtin/google_maps.js.map +1 -0
- package/dist/skills/builtin/index.d.ts +28 -0
- package/dist/skills/builtin/index.js +75 -0
- package/dist/skills/builtin/index.js.map +1 -0
- package/dist/skills/builtin/info_gatherer.d.ts +52 -0
- package/dist/skills/builtin/info_gatherer.js +237 -0
- package/dist/skills/builtin/info_gatherer.js.map +1 -0
- package/dist/skills/builtin/joke.d.ts +32 -0
- package/dist/skills/builtin/joke.js +142 -0
- package/dist/skills/builtin/joke.js.map +1 -0
- package/dist/skills/builtin/math.d.ts +33 -0
- package/dist/skills/builtin/math.js +120 -0
- package/dist/skills/builtin/math.js.map +1 -0
- package/dist/skills/builtin/mcp_gateway.d.ts +35 -0
- package/dist/skills/builtin/mcp_gateway.js +85 -0
- package/dist/skills/builtin/mcp_gateway.js.map +1 -0
- package/dist/skills/builtin/native_vector_search.d.ts +44 -0
- package/dist/skills/builtin/native_vector_search.js +248 -0
- package/dist/skills/builtin/native_vector_search.js.map +1 -0
- package/dist/skills/builtin/play_background_file.d.ts +34 -0
- package/dist/skills/builtin/play_background_file.js +135 -0
- package/dist/skills/builtin/play_background_file.js.map +1 -0
- package/dist/skills/builtin/spider.d.ts +34 -0
- package/dist/skills/builtin/spider.js +164 -0
- package/dist/skills/builtin/spider.js.map +1 -0
- package/dist/skills/builtin/swml_transfer.d.ts +44 -0
- package/dist/skills/builtin/swml_transfer.js +204 -0
- package/dist/skills/builtin/swml_transfer.js.map +1 -0
- package/dist/skills/builtin/weather_api.d.ts +34 -0
- package/dist/skills/builtin/weather_api.js +136 -0
- package/dist/skills/builtin/weather_api.js.map +1 -0
- package/dist/skills/builtin/web_search.d.ts +34 -0
- package/dist/skills/builtin/web_search.js +141 -0
- package/dist/skills/builtin/web_search.js.map +1 -0
- package/dist/skills/builtin/wikipedia_search.d.ts +34 -0
- package/dist/skills/builtin/wikipedia_search.js +166 -0
- package/dist/skills/builtin/wikipedia_search.js.map +1 -0
- package/dist/skills/index.d.ts +11 -0
- package/dist/skills/index.js +10 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/types.d.ts +85 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/docs/agent-guide.md +869 -0
- package/docs/api-reference.md +2419 -0
- package/docs/architecture.md +843 -0
- package/docs/cli-guide.md +579 -0
- package/docs/configuration.md +422 -0
- package/docs/contexts-guide.md +787 -0
- package/docs/datamap-guide.md +1141 -0
- package/docs/prefabs-guide.md +830 -0
- package/docs/security.md +436 -0
- package/docs/serverless-guide.md +513 -0
- package/docs/skills-guide.md +845 -0
- package/docs/swaig-reference.md +1627 -0
- package/examples/call-flow.ts +60 -0
- package/examples/contexts-steps.ts +62 -0
- package/examples/datamap-tools.ts +52 -0
- package/examples/dynamic-config.ts +51 -0
- package/examples/multi-agent.ts +67 -0
- package/examples/pom-prompt.ts +68 -0
- package/examples/simple-agent.ts +33 -0
- package/package.json +35 -0
- package/src/AgentBase.ts +1279 -0
- package/src/AgentServer.ts +149 -0
- package/src/AuthHandler.ts +141 -0
- package/src/ConfigLoader.ts +165 -0
- package/src/ContextBuilder.ts +796 -0
- package/src/DataMap.ts +365 -0
- package/src/Logger.ts +197 -0
- package/src/PomBuilder.ts +298 -0
- package/src/PromptManager.ts +133 -0
- package/src/SchemaUtils.ts +170 -0
- package/src/ServerlessAdapter.ts +218 -0
- package/src/SessionManager.ts +161 -0
- package/src/SslConfig.ts +118 -0
- package/src/SwaigFunction.ts +168 -0
- package/src/SwaigFunctionResult.ts +864 -0
- package/src/SwmlBuilder.ts +62 -0
- package/src/cli/agent-loader.ts +122 -0
- package/src/cli/mock-data.ts +110 -0
- package/src/cli/swaig-test.ts +440 -0
- package/src/index.ts +99 -0
- package/src/prefabs/ConciergeAgent.ts +251 -0
- package/src/prefabs/FAQBotAgent.ts +263 -0
- package/src/prefabs/InfoGathererAgent.ts +276 -0
- package/src/prefabs/ReceptionistAgent.ts +277 -0
- package/src/prefabs/SurveyAgent.ts +413 -0
- package/src/prefabs/index.ts +23 -0
- package/src/skills/SkillBase.ts +184 -0
- package/src/skills/SkillManager.ts +177 -0
- package/src/skills/SkillRegistry.ts +231 -0
- package/src/skills/builtin/api_ninjas_trivia.ts +230 -0
- package/src/skills/builtin/claude_skills.ts +239 -0
- package/src/skills/builtin/custom_skills.ts +326 -0
- package/src/skills/builtin/datasphere.ts +225 -0
- package/src/skills/builtin/datasphere_serverless.ts +194 -0
- package/src/skills/builtin/datetime.ts +115 -0
- package/src/skills/builtin/google_maps.ts +347 -0
- package/src/skills/builtin/index.ts +79 -0
- package/src/skills/builtin/info_gatherer.ts +331 -0
- package/src/skills/builtin/joke.ts +180 -0
- package/src/skills/builtin/math.ts +150 -0
- package/src/skills/builtin/mcp_gateway.ts +99 -0
- package/src/skills/builtin/native_vector_search.ts +327 -0
- package/src/skills/builtin/play_background_file.ts +189 -0
- package/src/skills/builtin/spider.ts +232 -0
- package/src/skills/builtin/swml_transfer.ts +285 -0
- package/src/skills/builtin/weather_api.ts +193 -0
- package/src/skills/builtin/web_search.ts +212 -0
- package/src/skills/builtin/wikipedia_search.ts +248 -0
- package/src/skills/index.ts +12 -0
- package/src/types.ts +99 -0
- package/tests/AgentBase.test.ts +693 -0
- package/tests/AgentServer.test.ts +102 -0
- package/tests/AuthHandler.test.ts +121 -0
- package/tests/ConfigLoader.test.ts +121 -0
- package/tests/DataMap.test.ts +286 -0
- package/tests/Logger.test.ts +218 -0
- package/tests/PomBuilder.test.ts +164 -0
- package/tests/ProxyDetection.test.ts +185 -0
- package/tests/SchemaUtils.test.ts +106 -0
- package/tests/ServerlessAdapter.test.ts +200 -0
- package/tests/SessionManager.test.ts +139 -0
- package/tests/SslConfig.test.ts +97 -0
- package/tests/SwaigFunctionResult.test.ts +298 -0
- package/tests/cli/mock-data.test.ts +94 -0
- package/tests/cli/swaig-test.test.ts +96 -0
- package/tests/prefabs/prefabs.test.ts +854 -0
- package/tests/setup.ts +6 -0
- package/tests/skills/SkillBase.test.ts +118 -0
- package/tests/skills/SkillManager.test.ts +164 -0
- package/tests/skills/SkillRegistry.test.ts +124 -0
- package/tests/skills/builtin.test.ts +924 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { AgentServer } from '../src/AgentServer.js';
|
|
3
|
+
import { AgentBase } from '../src/AgentBase.js';
|
|
4
|
+
|
|
5
|
+
describe('AgentServer', () => {
|
|
6
|
+
it('registers agents', () => {
|
|
7
|
+
const server = new AgentServer();
|
|
8
|
+
const agent1 = new AgentBase({ name: 'support', route: '/support' });
|
|
9
|
+
const agent2 = new AgentBase({ name: 'sales', route: '/sales' });
|
|
10
|
+
|
|
11
|
+
server.register(agent1, '/support');
|
|
12
|
+
server.register(agent2, '/sales');
|
|
13
|
+
|
|
14
|
+
expect(server.getAgents().size).toBe(2);
|
|
15
|
+
expect(server.getAgent('/support')?.name).toBe('support');
|
|
16
|
+
expect(server.getAgent('/sales')?.name).toBe('sales');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('throws on duplicate route', () => {
|
|
20
|
+
const server = new AgentServer();
|
|
21
|
+
const agent1 = new AgentBase({ name: 'a1', route: '/test' });
|
|
22
|
+
const agent2 = new AgentBase({ name: 'a2', route: '/test' });
|
|
23
|
+
|
|
24
|
+
server.register(agent1, '/test');
|
|
25
|
+
expect(() => server.register(agent2, '/test')).toThrow("Route '/test' is already in use");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('unregisters agents', () => {
|
|
29
|
+
const server = new AgentServer();
|
|
30
|
+
const agent = new AgentBase({ name: 'test', route: '/test' });
|
|
31
|
+
server.register(agent, '/test');
|
|
32
|
+
server.unregister('/test');
|
|
33
|
+
expect(server.getAgents().size).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('uses agent route if none provided', () => {
|
|
37
|
+
const server = new AgentServer();
|
|
38
|
+
const agent = new AgentBase({ name: 'myagent', route: '/myroute' });
|
|
39
|
+
server.register(agent);
|
|
40
|
+
expect(server.getAgent('/myroute')).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('getApp returns Hono instance', () => {
|
|
44
|
+
const server = new AgentServer();
|
|
45
|
+
const app = server.getApp();
|
|
46
|
+
expect(app).toBeDefined();
|
|
47
|
+
expect(typeof app.fetch).toBe('function');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('health endpoint responds', async () => {
|
|
51
|
+
const server = new AgentServer();
|
|
52
|
+
const app = server.getApp();
|
|
53
|
+
const res = await app.request('/health');
|
|
54
|
+
expect(res.status).toBe(200);
|
|
55
|
+
const body = await res.json();
|
|
56
|
+
expect(body.status).toBe('ok');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('ready endpoint responds', async () => {
|
|
60
|
+
const server = new AgentServer();
|
|
61
|
+
const app = server.getApp();
|
|
62
|
+
const res = await app.request('/ready');
|
|
63
|
+
expect(res.status).toBe(200);
|
|
64
|
+
const body = await res.json();
|
|
65
|
+
expect(body.status).toBe('ready');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('root listing when no agent at /', async () => {
|
|
69
|
+
const server = new AgentServer();
|
|
70
|
+
const agent = new AgentBase({ name: 'test', route: '/test' });
|
|
71
|
+
server.register(agent);
|
|
72
|
+
const app = server.getApp();
|
|
73
|
+
const res = await app.request('/');
|
|
74
|
+
expect(res.status).toBe(200);
|
|
75
|
+
const body = await res.json();
|
|
76
|
+
expect(body.service).toBe('SignalWire AI Agents');
|
|
77
|
+
expect(body.agents.length).toBe(1);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('includes security headers in responses', async () => {
|
|
81
|
+
const server = new AgentServer();
|
|
82
|
+
const app = server.getApp();
|
|
83
|
+
const res = await app.request('/health');
|
|
84
|
+
expect(res.headers.get('X-Content-Type-Options')).toBe('nosniff');
|
|
85
|
+
expect(res.headers.get('X-Frame-Options')).toBe('DENY');
|
|
86
|
+
expect(res.headers.get('X-XSS-Protection')).toBe('1; mode=block');
|
|
87
|
+
expect(res.headers.get('Referrer-Policy')).toBe('strict-origin-when-cross-origin');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('responds to CORS preflight', async () => {
|
|
91
|
+
const server = new AgentServer();
|
|
92
|
+
const app = server.getApp();
|
|
93
|
+
const res = await app.request('/health', {
|
|
94
|
+
method: 'OPTIONS',
|
|
95
|
+
headers: {
|
|
96
|
+
'Origin': 'https://example.com',
|
|
97
|
+
'Access-Control-Request-Method': 'POST',
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { AuthHandler } from '../src/AuthHandler.js';
|
|
3
|
+
|
|
4
|
+
describe('AuthHandler', () => {
|
|
5
|
+
it('validates Bearer token', async () => {
|
|
6
|
+
const auth = new AuthHandler({ bearerToken: 'my-secret-token' });
|
|
7
|
+
const valid = await auth.validate({ authorization: 'Bearer my-secret-token' });
|
|
8
|
+
expect(valid).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('rejects invalid Bearer token', async () => {
|
|
12
|
+
const auth = new AuthHandler({ bearerToken: 'my-secret-token' });
|
|
13
|
+
const valid = await auth.validate({ authorization: 'Bearer wrong-token' });
|
|
14
|
+
expect(valid).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('validates API key', async () => {
|
|
18
|
+
const auth = new AuthHandler({ apiKey: 'my-api-key' });
|
|
19
|
+
const valid = await auth.validate({ 'x-api-key': 'my-api-key' });
|
|
20
|
+
expect(valid).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('rejects invalid API key', async () => {
|
|
24
|
+
const auth = new AuthHandler({ apiKey: 'my-api-key' });
|
|
25
|
+
const valid = await auth.validate({ 'x-api-key': 'wrong-key' });
|
|
26
|
+
expect(valid).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('validates Basic auth', async () => {
|
|
30
|
+
const auth = new AuthHandler({ basicAuth: ['admin', 'pass123'] });
|
|
31
|
+
const encoded = Buffer.from('admin:pass123').toString('base64');
|
|
32
|
+
const valid = await auth.validate({ authorization: `Basic ${encoded}` });
|
|
33
|
+
expect(valid).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('rejects invalid Basic auth', async () => {
|
|
37
|
+
const auth = new AuthHandler({ basicAuth: ['admin', 'pass123'] });
|
|
38
|
+
const encoded = Buffer.from('admin:wrong').toString('base64');
|
|
39
|
+
const valid = await auth.validate({ authorization: `Basic ${encoded}` });
|
|
40
|
+
expect(valid).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('validates custom validator', async () => {
|
|
44
|
+
const auth = new AuthHandler({
|
|
45
|
+
customValidator: (req) => req.headers['x-custom'] === 'valid',
|
|
46
|
+
});
|
|
47
|
+
const valid = await auth.validate({ 'x-custom': 'valid' });
|
|
48
|
+
expect(valid).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('rejects with custom validator', async () => {
|
|
52
|
+
const auth = new AuthHandler({
|
|
53
|
+
customValidator: (req) => req.headers['x-custom'] === 'valid',
|
|
54
|
+
});
|
|
55
|
+
const valid = await auth.validate({ 'x-custom': 'invalid' });
|
|
56
|
+
expect(valid).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('checks methods in priority order: Bearer > API Key > Basic', async () => {
|
|
60
|
+
const auth = new AuthHandler({
|
|
61
|
+
bearerToken: 'token',
|
|
62
|
+
apiKey: 'key',
|
|
63
|
+
basicAuth: ['u', 'p'],
|
|
64
|
+
});
|
|
65
|
+
// Bearer should pass even without API key or Basic
|
|
66
|
+
const valid = await auth.validate({ authorization: 'Bearer token' });
|
|
67
|
+
expect(valid).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('allows all when no auth configured', async () => {
|
|
71
|
+
const auth = new AuthHandler({});
|
|
72
|
+
const valid = await auth.validate({});
|
|
73
|
+
expect(valid).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('hasBearerAuth returns correct state', () => {
|
|
77
|
+
expect(new AuthHandler({ bearerToken: 'x' }).hasBearerAuth()).toBe(true);
|
|
78
|
+
expect(new AuthHandler({}).hasBearerAuth()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('hasApiKeyAuth returns correct state', () => {
|
|
82
|
+
expect(new AuthHandler({ apiKey: 'x' }).hasApiKeyAuth()).toBe(true);
|
|
83
|
+
expect(new AuthHandler({}).hasApiKeyAuth()).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('hasBasicAuth returns correct state', () => {
|
|
87
|
+
expect(new AuthHandler({ basicAuth: ['u', 'p'] }).hasBasicAuth()).toBe(true);
|
|
88
|
+
expect(new AuthHandler({}).hasBasicAuth()).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('async custom validator works', async () => {
|
|
92
|
+
const auth = new AuthHandler({
|
|
93
|
+
customValidator: async (req) => {
|
|
94
|
+
await new Promise((r) => setTimeout(r, 1));
|
|
95
|
+
return req.headers['x-token'] === 'async-valid';
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
expect(await auth.validate({ 'x-token': 'async-valid' })).toBe(true);
|
|
99
|
+
expect(await auth.validate({ 'x-token': 'nope' })).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('middleware returns 401 for unauthorized request', async () => {
|
|
103
|
+
const auth = new AuthHandler({ bearerToken: 'secret' });
|
|
104
|
+
const mw = auth.middleware();
|
|
105
|
+
let nextCalled = false;
|
|
106
|
+
|
|
107
|
+
// Mock Hono context
|
|
108
|
+
const mockCtx = {
|
|
109
|
+
req: {
|
|
110
|
+
raw: {
|
|
111
|
+
headers: new Map([['authorization', 'Bearer wrong']]),
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
json: (body: unknown, status: number) => ({ body, status }),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const result = await mw(mockCtx, async () => { nextCalled = true; });
|
|
118
|
+
expect(nextCalled).toBe(false);
|
|
119
|
+
expect((result as any).status).toBe(401);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { ConfigLoader } from '../src/ConfigLoader.js';
|
|
3
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const TEST_DIR = join(process.cwd(), '__config_test_tmp__');
|
|
7
|
+
|
|
8
|
+
describe('ConfigLoader', () => {
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
11
|
+
writeFileSync(join(TEST_DIR, 'test.json'), JSON.stringify({
|
|
12
|
+
server: { host: '0.0.0.0', port: 3000 },
|
|
13
|
+
name: 'test-agent',
|
|
14
|
+
nested: { deep: { value: 42 } },
|
|
15
|
+
}));
|
|
16
|
+
writeFileSync(join(TEST_DIR, 'env-test.json'), JSON.stringify({
|
|
17
|
+
host: '${TEST_CONFIG_HOST|localhost}',
|
|
18
|
+
port: '${TEST_CONFIG_PORT|8080}',
|
|
19
|
+
secret: '${TEST_CONFIG_SECRET}',
|
|
20
|
+
}));
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('loads a JSON config file', () => {
|
|
28
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
29
|
+
expect(config.get('name')).toBe('test-agent');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('dot-notation access for nested values', () => {
|
|
33
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
34
|
+
expect(config.get('server.host')).toBe('0.0.0.0');
|
|
35
|
+
expect(config.get('server.port')).toBe(3000);
|
|
36
|
+
expect(config.get('nested.deep.value')).toBe(42);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('returns default value for missing path', () => {
|
|
40
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
41
|
+
expect(config.get('missing', 'fallback')).toBe('fallback');
|
|
42
|
+
expect(config.get('server.missing', 99)).toBe(99);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('has() checks path existence', () => {
|
|
46
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
47
|
+
expect(config.has('name')).toBe(true);
|
|
48
|
+
expect(config.has('server.host')).toBe(true);
|
|
49
|
+
expect(config.has('nope')).toBe(false);
|
|
50
|
+
expect(config.has('server.nope')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('set() sets values with dot-notation', () => {
|
|
54
|
+
const config = new ConfigLoader();
|
|
55
|
+
config.loadFromObject({});
|
|
56
|
+
config.set('server.port', 5000);
|
|
57
|
+
config.set('name', 'new-agent');
|
|
58
|
+
expect(config.get('server.port')).toBe(5000);
|
|
59
|
+
expect(config.get('name')).toBe('new-agent');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('getAll() returns all config data', () => {
|
|
63
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
64
|
+
const all = config.getAll();
|
|
65
|
+
expect(all.name).toBe('test-agent');
|
|
66
|
+
expect(all.server).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('interpolates env vars with defaults', () => {
|
|
70
|
+
const config = new ConfigLoader(join(TEST_DIR, 'env-test.json'));
|
|
71
|
+
expect(config.get('host')).toBe('localhost');
|
|
72
|
+
expect(config.get('port')).toBe('8080');
|
|
73
|
+
// secret has no default and no env var, so empty string
|
|
74
|
+
expect(config.get('secret')).toBe('');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('interpolates env vars from environment', () => {
|
|
78
|
+
process.env['TEST_CONFIG_HOST'] = 'myhost.com';
|
|
79
|
+
try {
|
|
80
|
+
const config = new ConfigLoader(join(TEST_DIR, 'env-test.json'));
|
|
81
|
+
expect(config.get('host')).toBe('myhost.com');
|
|
82
|
+
} finally {
|
|
83
|
+
delete process.env['TEST_CONFIG_HOST'];
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('throws for missing file', () => {
|
|
88
|
+
expect(() => new ConfigLoader('/nonexistent/path.json')).toThrow('Config file not found');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('loadFromObject works', () => {
|
|
92
|
+
const config = new ConfigLoader();
|
|
93
|
+
config.loadFromObject({ key: 'value', nested: { a: 1 } });
|
|
94
|
+
expect(config.get('key')).toBe('value');
|
|
95
|
+
expect(config.get('nested.a')).toBe(1);
|
|
96
|
+
expect(config.getFilePath()).toBeNull();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('getFilePath returns the loaded file path', () => {
|
|
100
|
+
const config = new ConfigLoader(join(TEST_DIR, 'test.json'));
|
|
101
|
+
expect(config.getFilePath()).toContain('test.json');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('load returns this for chaining', () => {
|
|
105
|
+
const config = new ConfigLoader();
|
|
106
|
+
const result = config.load(join(TEST_DIR, 'test.json'));
|
|
107
|
+
expect(result).toBe(config);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('set creates intermediate objects', () => {
|
|
111
|
+
const config = new ConfigLoader();
|
|
112
|
+
config.loadFromObject({});
|
|
113
|
+
config.set('a.b.c', 'deep');
|
|
114
|
+
expect(config.get('a.b.c')).toBe('deep');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('search returns null when file not found', () => {
|
|
118
|
+
const result = ConfigLoader.search('nonexistent-file-12345.json');
|
|
119
|
+
expect(result).toBeNull();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DataMap, createSimpleApiTool, createExpressionTool } from '../src/DataMap.js';
|
|
3
|
+
import { SwaigFunctionResult } from '../src/SwaigFunctionResult.js';
|
|
4
|
+
|
|
5
|
+
describe('DataMap', () => {
|
|
6
|
+
it('basic builder produces valid swaig function', () => {
|
|
7
|
+
const dm = new DataMap('get_weather')
|
|
8
|
+
.purpose('Get weather')
|
|
9
|
+
.parameter('location', 'string', 'City name', { required: true })
|
|
10
|
+
.webhook('GET', 'https://api.weather.com?q=${location}')
|
|
11
|
+
.output(new SwaigFunctionResult('Weather: ${response.temp}'));
|
|
12
|
+
|
|
13
|
+
const fn = dm.toSwaigFunction();
|
|
14
|
+
expect(fn['function']).toBe('get_weather');
|
|
15
|
+
expect(fn['description']).toBe('Get weather');
|
|
16
|
+
expect((fn['parameters'] as Record<string, unknown>)['required']).toEqual(['location']);
|
|
17
|
+
const dataMap = fn['data_map'] as Record<string, unknown>;
|
|
18
|
+
expect(dataMap['webhooks']).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('description is alias for purpose', () => {
|
|
22
|
+
const dm = new DataMap('fn').description('desc');
|
|
23
|
+
expect(dm.toSwaigFunction()['description']).toBe('desc');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('default description when purpose not set', () => {
|
|
27
|
+
const dm = new DataMap('my_fn');
|
|
28
|
+
dm.webhook('GET', 'https://example.com').output(new SwaigFunctionResult('ok'));
|
|
29
|
+
expect(dm.toSwaigFunction()['description']).toBe('Execute my_fn');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('parameter with enum', () => {
|
|
33
|
+
const dm = new DataMap('fn')
|
|
34
|
+
.parameter('color', 'string', 'A color', { enum: ['red', 'blue'] });
|
|
35
|
+
const params = dm.toSwaigFunction()['parameters'] as Record<string, unknown>;
|
|
36
|
+
const props = params['properties'] as Record<string, Record<string, unknown>>;
|
|
37
|
+
expect(props['color']['enum']).toEqual(['red', 'blue']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('expression-based tool', () => {
|
|
41
|
+
const dm = new DataMap('ctrl')
|
|
42
|
+
.purpose('Control playback')
|
|
43
|
+
.parameter('cmd', 'string', 'Command')
|
|
44
|
+
.expression('${args.cmd}', /start.*/, new SwaigFunctionResult('Starting'))
|
|
45
|
+
.expression('${args.cmd}', 'stop', new SwaigFunctionResult('Stopping'));
|
|
46
|
+
|
|
47
|
+
const fn = dm.toSwaigFunction();
|
|
48
|
+
const dataMap = fn['data_map'] as Record<string, unknown>;
|
|
49
|
+
const exprs = dataMap['expressions'] as Record<string, unknown>[];
|
|
50
|
+
expect(exprs.length).toBe(2);
|
|
51
|
+
expect(exprs[0]['pattern']).toBe('start.*');
|
|
52
|
+
expect(exprs[1]['pattern']).toBe('stop');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('expression with nomatch output', () => {
|
|
56
|
+
const dm = new DataMap('fn')
|
|
57
|
+
.expression(
|
|
58
|
+
'${args.x}',
|
|
59
|
+
'yes',
|
|
60
|
+
new SwaigFunctionResult('matched'),
|
|
61
|
+
new SwaigFunctionResult('no match'),
|
|
62
|
+
);
|
|
63
|
+
const fn = dm.toSwaigFunction();
|
|
64
|
+
const exprs = (fn['data_map'] as Record<string, unknown>)['expressions'] as Record<string, unknown>[];
|
|
65
|
+
expect(exprs[0]['nomatch-output']).toEqual({ response: 'no match' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('webhook with body and headers', () => {
|
|
69
|
+
const dm = new DataMap('search')
|
|
70
|
+
.purpose('Search docs')
|
|
71
|
+
.webhook('POST', 'https://api.docs.com/search', {
|
|
72
|
+
headers: { Authorization: 'Bearer TOKEN' },
|
|
73
|
+
})
|
|
74
|
+
.body({ query: '${query}', limit: 3 })
|
|
75
|
+
.output(new SwaigFunctionResult('Found: ${response.title}'));
|
|
76
|
+
|
|
77
|
+
const fn = dm.toSwaigFunction();
|
|
78
|
+
const webhooks = (fn['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
79
|
+
expect(webhooks[0]['method']).toBe('POST');
|
|
80
|
+
expect(webhooks[0]['headers']).toEqual({ Authorization: 'Bearer TOKEN' });
|
|
81
|
+
expect(webhooks[0]['body']).toEqual({ query: '${query}', limit: 3 });
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('webhook with params', () => {
|
|
85
|
+
const dm = new DataMap('fn')
|
|
86
|
+
.webhook('GET', 'https://example.com')
|
|
87
|
+
.params({ key: 'val' })
|
|
88
|
+
.output(new SwaigFunctionResult('ok'));
|
|
89
|
+
const webhooks = (dm.toSwaigFunction()['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
90
|
+
expect(webhooks[0]['params']).toEqual({ key: 'val' });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('foreach', () => {
|
|
94
|
+
const dm = new DataMap('fn')
|
|
95
|
+
.webhook('GET', 'https://example.com')
|
|
96
|
+
.foreach({ input_key: 'results', output_key: 'out', append: '${this.title}\n' })
|
|
97
|
+
.output(new SwaigFunctionResult('${out}'));
|
|
98
|
+
const webhooks = (dm.toSwaigFunction()['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
99
|
+
expect(webhooks[0]['foreach']).toBeDefined();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('multiple webhooks with fallback', () => {
|
|
103
|
+
const dm = new DataMap('multi')
|
|
104
|
+
.purpose('Search')
|
|
105
|
+
.webhook('GET', 'https://primary.com')
|
|
106
|
+
.output(new SwaigFunctionResult('Primary: ${response.title}'))
|
|
107
|
+
.webhook('GET', 'https://fallback.com')
|
|
108
|
+
.output(new SwaigFunctionResult('Fallback: ${response.title}'))
|
|
109
|
+
.fallbackOutput(new SwaigFunctionResult('All APIs failed'));
|
|
110
|
+
|
|
111
|
+
const fn = dm.toSwaigFunction();
|
|
112
|
+
const dataMap = fn['data_map'] as Record<string, unknown>;
|
|
113
|
+
expect((dataMap['webhooks'] as unknown[]).length).toBe(2);
|
|
114
|
+
expect(dataMap['output']).toEqual({ response: 'All APIs failed' });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('error keys on webhook', () => {
|
|
118
|
+
const dm = new DataMap('fn')
|
|
119
|
+
.webhook('GET', 'https://example.com')
|
|
120
|
+
.errorKeys(['error', 'message'])
|
|
121
|
+
.output(new SwaigFunctionResult('ok'));
|
|
122
|
+
const webhooks = (dm.toSwaigFunction()['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
123
|
+
expect(webhooks[0]['error_keys']).toEqual(['error', 'message']);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('global error keys', () => {
|
|
127
|
+
const dm = new DataMap('fn').globalErrorKeys(['err']);
|
|
128
|
+
dm.webhook('GET', 'https://example.com').output(new SwaigFunctionResult('ok'));
|
|
129
|
+
const dataMap = dm.toSwaigFunction()['data_map'] as Record<string, unknown>;
|
|
130
|
+
expect(dataMap['error_keys']).toEqual(['err']);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('throws if body/output/params/foreach called without webhook', () => {
|
|
134
|
+
const dm = new DataMap('fn');
|
|
135
|
+
expect(() => dm.body({})).toThrow('Must add webhook');
|
|
136
|
+
expect(() => dm.output(new SwaigFunctionResult('x'))).toThrow('Must add webhook');
|
|
137
|
+
expect(() => dm.params({})).toThrow('Must add webhook');
|
|
138
|
+
expect(() => dm.foreach({ input_key: 'a', output_key: 'b', append: 'c' })).toThrow('Must add webhook');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('webhookExpressions', () => {
|
|
142
|
+
const dm = new DataMap('fn')
|
|
143
|
+
.webhook('GET', 'https://example.com')
|
|
144
|
+
.webhookExpressions([{ string: '${response.status}', pattern: 'ok', output: { response: 'good' } }])
|
|
145
|
+
.output(new SwaigFunctionResult('ok'));
|
|
146
|
+
const webhooks = (dm.toSwaigFunction()['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
147
|
+
expect(webhooks[0]['expressions']).toBeDefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('webhook with advanced options', () => {
|
|
151
|
+
const dm = new DataMap('fn')
|
|
152
|
+
.webhook('POST', 'https://example.com', {
|
|
153
|
+
formParam: 'data',
|
|
154
|
+
inputArgsAsParams: true,
|
|
155
|
+
requireArgs: ['query'],
|
|
156
|
+
})
|
|
157
|
+
.output(new SwaigFunctionResult('ok'));
|
|
158
|
+
const webhooks = (dm.toSwaigFunction()['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
159
|
+
expect(webhooks[0]['form_param']).toBe('data');
|
|
160
|
+
expect(webhooks[0]['input_args_as_params']).toBe(true);
|
|
161
|
+
expect(webhooks[0]['require_args']).toEqual(['query']);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('createSimpleApiTool', () => {
|
|
166
|
+
it('creates a simple API tool', () => {
|
|
167
|
+
const dm = createSimpleApiTool({
|
|
168
|
+
name: 'weather',
|
|
169
|
+
url: 'https://api.weather.com?q=${location}',
|
|
170
|
+
responseTemplate: 'Temp: ${response.temp}',
|
|
171
|
+
parameters: { location: { type: 'string', description: 'City', required: true } },
|
|
172
|
+
});
|
|
173
|
+
const fn = dm.toSwaigFunction();
|
|
174
|
+
expect(fn['function']).toBe('weather');
|
|
175
|
+
const params = fn['parameters'] as Record<string, unknown>;
|
|
176
|
+
expect(params['required']).toEqual(['location']);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('createExpressionTool', () => {
|
|
181
|
+
it('creates expression-based tool', () => {
|
|
182
|
+
const dm = createExpressionTool({
|
|
183
|
+
name: 'ctrl',
|
|
184
|
+
patterns: {
|
|
185
|
+
'${args.cmd}': ['start', new SwaigFunctionResult('Starting')],
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
const fn = dm.toSwaigFunction();
|
|
189
|
+
const exprs = (fn['data_map'] as Record<string, unknown>)['expressions'] as Record<string, unknown>[];
|
|
190
|
+
expect(exprs.length).toBe(1);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// ── Pass 3: DataMap enhancements ──────────────────────────────────
|
|
195
|
+
|
|
196
|
+
describe('DataMap - registerWithAgent', () => {
|
|
197
|
+
it('registers DataMap with an agent-like object', () => {
|
|
198
|
+
const registered: Record<string, unknown>[] = [];
|
|
199
|
+
const mockAgent = {
|
|
200
|
+
registerSwaigFunction(fn: Record<string, unknown>) {
|
|
201
|
+
registered.push(fn);
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
const dm = new DataMap('my_tool')
|
|
205
|
+
.purpose('Test tool')
|
|
206
|
+
.webhook('GET', 'https://example.com')
|
|
207
|
+
.output(new SwaigFunctionResult('ok'));
|
|
208
|
+
|
|
209
|
+
dm.registerWithAgent(mockAgent);
|
|
210
|
+
expect(registered.length).toBe(1);
|
|
211
|
+
expect(registered[0]['function']).toBe('my_tool');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('registerWithAgent returns this for chaining', () => {
|
|
215
|
+
const mockAgent = { registerSwaigFunction() {} };
|
|
216
|
+
const dm = new DataMap('fn')
|
|
217
|
+
.webhook('GET', 'https://example.com')
|
|
218
|
+
.output(new SwaigFunctionResult('ok'));
|
|
219
|
+
expect(dm.registerWithAgent(mockAgent)).toBe(dm);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('DataMap - ENV expansion', () => {
|
|
224
|
+
it('expands ${ENV.*} variables in webhook URLs', () => {
|
|
225
|
+
const saved = process.env['TEST_API_KEY'];
|
|
226
|
+
process.env['TEST_API_KEY'] = 'secret123';
|
|
227
|
+
try {
|
|
228
|
+
const dm = new DataMap('fn')
|
|
229
|
+
.enableEnvExpansion()
|
|
230
|
+
.purpose('Test')
|
|
231
|
+
.webhook('GET', 'https://api.example.com?key=${ENV.TEST_API_KEY}')
|
|
232
|
+
.output(new SwaigFunctionResult('ok'));
|
|
233
|
+
const fn = dm.toSwaigFunction();
|
|
234
|
+
const webhooks = (fn['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
235
|
+
expect(webhooks[0]['url']).toBe('https://api.example.com?key=secret123');
|
|
236
|
+
} finally {
|
|
237
|
+
if (saved) process.env['TEST_API_KEY'] = saved;
|
|
238
|
+
else delete process.env['TEST_API_KEY'];
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('missing ENV vars expand to empty string', () => {
|
|
243
|
+
delete process.env['NONEXISTENT_VAR_12345'];
|
|
244
|
+
const dm = new DataMap('fn')
|
|
245
|
+
.enableEnvExpansion()
|
|
246
|
+
.webhook('GET', 'https://api.example.com?key=${ENV.NONEXISTENT_VAR_12345}')
|
|
247
|
+
.output(new SwaigFunctionResult('ok'));
|
|
248
|
+
const fn = dm.toSwaigFunction();
|
|
249
|
+
const webhooks = (fn['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
250
|
+
expect(webhooks[0]['url']).toBe('https://api.example.com?key=');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('no expansion when enableEnvExpansion not called', () => {
|
|
254
|
+
process.env['TEST_NO_EXPAND'] = 'should-not-appear';
|
|
255
|
+
try {
|
|
256
|
+
const dm = new DataMap('fn')
|
|
257
|
+
.webhook('GET', 'https://api.example.com?key=${ENV.TEST_NO_EXPAND}')
|
|
258
|
+
.output(new SwaigFunctionResult('ok'));
|
|
259
|
+
const fn = dm.toSwaigFunction();
|
|
260
|
+
const webhooks = (fn['data_map'] as Record<string, unknown>)['webhooks'] as Record<string, unknown>[];
|
|
261
|
+
expect(webhooks[0]['url']).toBe('https://api.example.com?key=${ENV.TEST_NO_EXPAND}');
|
|
262
|
+
} finally {
|
|
263
|
+
delete process.env['TEST_NO_EXPAND'];
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('expands ENV vars in descriptions and outputs', () => {
|
|
268
|
+
process.env['TEST_SERVICE_NAME'] = 'MyService';
|
|
269
|
+
try {
|
|
270
|
+
const dm = new DataMap('fn')
|
|
271
|
+
.enableEnvExpansion()
|
|
272
|
+
.purpose('Call ${ENV.TEST_SERVICE_NAME} API')
|
|
273
|
+
.webhook('GET', 'https://example.com')
|
|
274
|
+
.output(new SwaigFunctionResult('ok'));
|
|
275
|
+
const fn = dm.toSwaigFunction();
|
|
276
|
+
expect(fn['description']).toBe('Call MyService API');
|
|
277
|
+
} finally {
|
|
278
|
+
delete process.env['TEST_SERVICE_NAME'];
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('enableEnvExpansion returns this for chaining', () => {
|
|
283
|
+
const dm = new DataMap('fn');
|
|
284
|
+
expect(dm.enableEnvExpansion()).toBe(dm);
|
|
285
|
+
});
|
|
286
|
+
});
|