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,200 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { ServerlessAdapter } from '../src/ServerlessAdapter.js';
|
|
3
|
+
import { AgentBase } from '../src/AgentBase.js';
|
|
4
|
+
import { SwaigFunctionResult } from '../src/SwaigFunctionResult.js';
|
|
5
|
+
import { suppressAllLogs } from '../src/Logger.js';
|
|
6
|
+
|
|
7
|
+
describe('ServerlessAdapter', () => {
|
|
8
|
+
beforeEach(() => suppressAllLogs(true));
|
|
9
|
+
afterEach(() => suppressAllLogs(false));
|
|
10
|
+
|
|
11
|
+
it('detectPlatform returns lambda for AWS env', () => {
|
|
12
|
+
const saved = process.env['AWS_LAMBDA_FUNCTION_NAME'];
|
|
13
|
+
process.env['AWS_LAMBDA_FUNCTION_NAME'] = 'my-func';
|
|
14
|
+
try {
|
|
15
|
+
const adapter = new ServerlessAdapter('auto');
|
|
16
|
+
expect(adapter.getPlatform()).toBe('lambda');
|
|
17
|
+
} finally {
|
|
18
|
+
if (saved) process.env['AWS_LAMBDA_FUNCTION_NAME'] = saved;
|
|
19
|
+
else delete process.env['AWS_LAMBDA_FUNCTION_NAME'];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('detectPlatform returns gcf for Google env', () => {
|
|
24
|
+
const saved = process.env['FUNCTION_TARGET'];
|
|
25
|
+
process.env['FUNCTION_TARGET'] = 'handler';
|
|
26
|
+
try {
|
|
27
|
+
const adapter = new ServerlessAdapter('auto');
|
|
28
|
+
expect(adapter.getPlatform()).toBe('gcf');
|
|
29
|
+
} finally {
|
|
30
|
+
if (saved) process.env['FUNCTION_TARGET'] = saved;
|
|
31
|
+
else delete process.env['FUNCTION_TARGET'];
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('explicit platform overrides detection', () => {
|
|
36
|
+
const adapter = new ServerlessAdapter('azure');
|
|
37
|
+
expect(adapter.getPlatform()).toBe('azure');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('handleRequest routes through Hono app', async () => {
|
|
41
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
42
|
+
agent.setPromptText('hello');
|
|
43
|
+
const app = agent.getApp();
|
|
44
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
45
|
+
|
|
46
|
+
const response = await adapter.handleRequest(app, {
|
|
47
|
+
httpMethod: 'GET',
|
|
48
|
+
path: '/health',
|
|
49
|
+
headers: { host: 'localhost' },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
expect(response.statusCode).toBe(200);
|
|
53
|
+
const body = JSON.parse(response.body);
|
|
54
|
+
expect(body.status).toBe('ok');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('handleRequest passes POST body', async () => {
|
|
58
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
59
|
+
agent.setPromptText('hello');
|
|
60
|
+
agent.defineTool({
|
|
61
|
+
name: 'echo',
|
|
62
|
+
description: 'Echo back',
|
|
63
|
+
parameters: { msg: { type: 'string' } },
|
|
64
|
+
handler: (args) => new SwaigFunctionResult(`Echo: ${args['msg']}`),
|
|
65
|
+
});
|
|
66
|
+
const app = agent.getApp();
|
|
67
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
68
|
+
|
|
69
|
+
const response = await adapter.handleRequest(app, {
|
|
70
|
+
httpMethod: 'POST',
|
|
71
|
+
path: '/swaig',
|
|
72
|
+
headers: {
|
|
73
|
+
host: 'localhost',
|
|
74
|
+
'content-type': 'application/json',
|
|
75
|
+
authorization: 'Basic ' + Buffer.from('u:p').toString('base64'),
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({ function: 'echo', argument: { msg: 'hi' } }),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(response.statusCode).toBe(200);
|
|
81
|
+
const body = JSON.parse(response.body);
|
|
82
|
+
expect(body.response).toContain('Echo: hi');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handleRequest handles query parameters', async () => {
|
|
86
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
87
|
+
agent.setPromptText('hello');
|
|
88
|
+
const app = agent.getApp();
|
|
89
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
90
|
+
|
|
91
|
+
const response = await adapter.handleRequest(app, {
|
|
92
|
+
httpMethod: 'GET',
|
|
93
|
+
path: '/health',
|
|
94
|
+
headers: { host: 'localhost' },
|
|
95
|
+
queryStringParameters: { foo: 'bar' },
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
expect(response.statusCode).toBe(200);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('generateUrl produces Lambda URL', () => {
|
|
102
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
103
|
+
const url = adapter.generateUrl({ region: 'us-west-2', apiId: 'abc123', stage: 'dev' });
|
|
104
|
+
expect(url).toContain('us-west-2');
|
|
105
|
+
expect(url).toContain('abc123');
|
|
106
|
+
expect(url).toContain('dev');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('generateUrl produces GCF URL', () => {
|
|
110
|
+
const adapter = new ServerlessAdapter('gcf');
|
|
111
|
+
const url = adapter.generateUrl({ projectId: 'my-proj', region: 'us-central1', functionName: 'agent' });
|
|
112
|
+
expect(url).toContain('my-proj');
|
|
113
|
+
expect(url).toContain('us-central1');
|
|
114
|
+
expect(url).toContain('agent');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('generateUrl produces Azure URL', () => {
|
|
118
|
+
const adapter = new ServerlessAdapter('azure');
|
|
119
|
+
const url = adapter.generateUrl({ functionName: 'my-agent' });
|
|
120
|
+
expect(url).toContain('my-agent');
|
|
121
|
+
expect(url).toContain('azurewebsites.net');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('generateUrl produces CGI URL', () => {
|
|
125
|
+
const adapter = new ServerlessAdapter('cgi');
|
|
126
|
+
const url = adapter.generateUrl({ functionName: 'agent' });
|
|
127
|
+
expect(url).toContain('cgi-bin');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('createLambdaHandler returns a function', () => {
|
|
131
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
132
|
+
const handler = ServerlessAdapter.createLambdaHandler(agent.getApp());
|
|
133
|
+
expect(typeof handler).toBe('function');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('Lambda handler processes requests', async () => {
|
|
137
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
138
|
+
agent.setPromptText('hello');
|
|
139
|
+
const handler = ServerlessAdapter.createLambdaHandler(agent.getApp());
|
|
140
|
+
|
|
141
|
+
const response = await handler({
|
|
142
|
+
httpMethod: 'GET',
|
|
143
|
+
path: '/health',
|
|
144
|
+
headers: { host: 'localhost' },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(response.statusCode).toBe(200);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('handleRequest uses rawPath if available', async () => {
|
|
151
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
152
|
+
agent.setPromptText('hello');
|
|
153
|
+
const app = agent.getApp();
|
|
154
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
155
|
+
|
|
156
|
+
const response = await adapter.handleRequest(app, {
|
|
157
|
+
httpMethod: 'GET',
|
|
158
|
+
rawPath: '/health',
|
|
159
|
+
headers: { host: 'localhost' },
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(response.statusCode).toBe(200);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('handleRequest defaults to POST method', async () => {
|
|
166
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
167
|
+
agent.setPromptText('hello');
|
|
168
|
+
const app = agent.getApp();
|
|
169
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
170
|
+
|
|
171
|
+
// No httpMethod specified
|
|
172
|
+
const response = await adapter.handleRequest(app, {
|
|
173
|
+
path: '/',
|
|
174
|
+
headers: {
|
|
175
|
+
host: 'localhost',
|
|
176
|
+
authorization: 'Basic ' + Buffer.from('u:p').toString('base64'),
|
|
177
|
+
'content-type': 'application/json',
|
|
178
|
+
},
|
|
179
|
+
body: '{}',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(response.statusCode).toBe(200);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('response includes headers', async () => {
|
|
186
|
+
const agent = new AgentBase({ name: 'test', route: '/', basicAuth: ['u', 'p'] });
|
|
187
|
+
agent.setPromptText('hello');
|
|
188
|
+
const app = agent.getApp();
|
|
189
|
+
const adapter = new ServerlessAdapter('lambda');
|
|
190
|
+
|
|
191
|
+
const response = await adapter.handleRequest(app, {
|
|
192
|
+
httpMethod: 'GET',
|
|
193
|
+
path: '/health',
|
|
194
|
+
headers: { host: 'localhost' },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(response.headers).toBeDefined();
|
|
198
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { SessionManager } from '../src/SessionManager.js';
|
|
3
|
+
|
|
4
|
+
describe('SessionManager', () => {
|
|
5
|
+
it('createSession generates an ID when none provided', () => {
|
|
6
|
+
const sm = new SessionManager();
|
|
7
|
+
const id = sm.createSession();
|
|
8
|
+
expect(id).toBeTruthy();
|
|
9
|
+
expect(typeof id).toBe('string');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('createSession returns provided ID', () => {
|
|
13
|
+
const sm = new SessionManager();
|
|
14
|
+
expect(sm.createSession('my-call')).toBe('my-call');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('generateToken produces a base64url string', () => {
|
|
18
|
+
const sm = new SessionManager();
|
|
19
|
+
const token = sm.generateToken('get_time', 'call-1');
|
|
20
|
+
expect(typeof token).toBe('string');
|
|
21
|
+
// Should be valid base64url
|
|
22
|
+
expect(() => Buffer.from(token, 'base64url').toString()).not.toThrow();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('validateToken accepts a valid token', () => {
|
|
26
|
+
const sm = new SessionManager();
|
|
27
|
+
const callId = 'call-1';
|
|
28
|
+
const fn = 'get_time';
|
|
29
|
+
const token = sm.generateToken(fn, callId);
|
|
30
|
+
expect(sm.validateToken(callId, fn, token)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('validateToken rejects wrong function name', () => {
|
|
34
|
+
const sm = new SessionManager();
|
|
35
|
+
const token = sm.generateToken('fn_a', 'call-1');
|
|
36
|
+
expect(sm.validateToken('call-1', 'fn_b', token)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('validateToken rejects wrong call id', () => {
|
|
40
|
+
const sm = new SessionManager();
|
|
41
|
+
const token = sm.generateToken('fn', 'call-1');
|
|
42
|
+
expect(sm.validateToken('call-2', 'fn', token)).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('validateToken rejects expired tokens', () => {
|
|
46
|
+
const sm = new SessionManager(0); // 0 second expiry
|
|
47
|
+
const token = sm.generateToken('fn', 'call-1');
|
|
48
|
+
// Token is immediately expired (or within ms)
|
|
49
|
+
// Wait a tiny bit to ensure expiry
|
|
50
|
+
const result = sm.validateToken('call-1', 'fn', token);
|
|
51
|
+
// With 0 expiry the token expires at creation time, so it should be invalid
|
|
52
|
+
expect(result).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('validateToken rejects tampered tokens', () => {
|
|
56
|
+
const sm = new SessionManager();
|
|
57
|
+
const token = sm.generateToken('fn', 'call-1');
|
|
58
|
+
// Tamper with the token
|
|
59
|
+
const tampered = token.slice(0, -2) + 'XX';
|
|
60
|
+
expect(sm.validateToken('call-1', 'fn', tampered)).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('validateToken rejects tokens from different secret', () => {
|
|
64
|
+
const sm1 = new SessionManager(3600, 'secret-1');
|
|
65
|
+
const sm2 = new SessionManager(3600, 'secret-2');
|
|
66
|
+
const token = sm1.generateToken('fn', 'call-1');
|
|
67
|
+
expect(sm2.validateToken('call-1', 'fn', token)).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('aliases work', () => {
|
|
71
|
+
const sm = new SessionManager();
|
|
72
|
+
const callId = 'call-1';
|
|
73
|
+
const fn = 'fn';
|
|
74
|
+
const token = sm.createToolToken(fn, callId);
|
|
75
|
+
expect(sm.validateToolToken(fn, token, callId)).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('validates token when callId is empty (uses token callId)', () => {
|
|
79
|
+
const sm = new SessionManager();
|
|
80
|
+
const token = sm.generateToken('fn', 'call-1');
|
|
81
|
+
// Empty callId should use token's embedded callId
|
|
82
|
+
expect(sm.validateToken('', 'fn', token)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// ── Pass 1: debugToken + session metadata ────────────────────────
|
|
86
|
+
|
|
87
|
+
it('debugToken decodes token components', () => {
|
|
88
|
+
const sm = new SessionManager();
|
|
89
|
+
const token = sm.generateToken('get_time', 'call-42');
|
|
90
|
+
const info = sm.debugToken(token);
|
|
91
|
+
expect(info).not.toBeNull();
|
|
92
|
+
expect(info!.callId).toBe('call-42');
|
|
93
|
+
expect(info!.functionName).toBe('get_time');
|
|
94
|
+
expect(typeof info!.expiry).toBe('number');
|
|
95
|
+
expect(typeof info!.nonce).toBe('string');
|
|
96
|
+
expect(typeof info!.signature).toBe('string');
|
|
97
|
+
expect(info!.expired).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('debugToken returns null for invalid token', () => {
|
|
101
|
+
const sm = new SessionManager();
|
|
102
|
+
expect(sm.debugToken('not-valid')).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('debugToken shows expired status for expired tokens', () => {
|
|
106
|
+
const sm = new SessionManager(0); // 0 second expiry
|
|
107
|
+
const token = sm.generateToken('fn', 'call-1');
|
|
108
|
+
const info = sm.debugToken(token);
|
|
109
|
+
expect(info).not.toBeNull();
|
|
110
|
+
expect(info!.expired).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('setSessionMetadata stores and retrieves metadata', () => {
|
|
114
|
+
const sm = new SessionManager();
|
|
115
|
+
sm.setSessionMetadata('session-1', { caller: 'John', topic: 'billing' });
|
|
116
|
+
const meta = sm.getSessionMetadata('session-1');
|
|
117
|
+
expect(meta).toEqual({ caller: 'John', topic: 'billing' });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('setSessionMetadata merges with existing metadata', () => {
|
|
121
|
+
const sm = new SessionManager();
|
|
122
|
+
sm.setSessionMetadata('session-1', { caller: 'John' });
|
|
123
|
+
sm.setSessionMetadata('session-1', { topic: 'billing' });
|
|
124
|
+
const meta = sm.getSessionMetadata('session-1');
|
|
125
|
+
expect(meta).toEqual({ caller: 'John', topic: 'billing' });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('getSessionMetadata returns undefined for unknown session', () => {
|
|
129
|
+
const sm = new SessionManager();
|
|
130
|
+
expect(sm.getSessionMetadata('nope')).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('deleteSessionMetadata removes metadata', () => {
|
|
134
|
+
const sm = new SessionManager();
|
|
135
|
+
sm.setSessionMetadata('session-1', { key: 'val' });
|
|
136
|
+
expect(sm.deleteSessionMetadata('session-1')).toBe(true);
|
|
137
|
+
expect(sm.getSessionMetadata('session-1')).toBeUndefined();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { SslConfig } from '../src/SslConfig.js';
|
|
3
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
|
|
6
|
+
const TEST_DIR = join(process.cwd(), '__ssl_test_tmp__');
|
|
7
|
+
|
|
8
|
+
describe('SslConfig', () => {
|
|
9
|
+
beforeAll(() => {
|
|
10
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
11
|
+
writeFileSync(join(TEST_DIR, 'cert.pem'), '-----BEGIN CERTIFICATE-----\ntest\n-----END CERTIFICATE-----');
|
|
12
|
+
writeFileSync(join(TEST_DIR, 'key.pem'), '-----BEGIN PRIVATE KEY-----\ntest\n-----END PRIVATE KEY-----');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterAll(() => {
|
|
16
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('defaults to disabled', () => {
|
|
20
|
+
const ssl = new SslConfig();
|
|
21
|
+
expect(ssl.enabled).toBe(false);
|
|
22
|
+
expect(ssl.isConfigured()).toBe(false);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('can be enabled via options', () => {
|
|
26
|
+
const ssl = new SslConfig({
|
|
27
|
+
enabled: true,
|
|
28
|
+
certPath: join(TEST_DIR, 'cert.pem'),
|
|
29
|
+
keyPath: join(TEST_DIR, 'key.pem'),
|
|
30
|
+
});
|
|
31
|
+
expect(ssl.enabled).toBe(true);
|
|
32
|
+
expect(ssl.isConfigured()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('isConfigured returns false when files missing', () => {
|
|
36
|
+
const ssl = new SslConfig({
|
|
37
|
+
enabled: true,
|
|
38
|
+
certPath: '/nonexistent/cert.pem',
|
|
39
|
+
keyPath: '/nonexistent/key.pem',
|
|
40
|
+
});
|
|
41
|
+
expect(ssl.isConfigured()).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('getCert reads certificate file', () => {
|
|
45
|
+
const ssl = new SslConfig({
|
|
46
|
+
enabled: true,
|
|
47
|
+
certPath: join(TEST_DIR, 'cert.pem'),
|
|
48
|
+
keyPath: join(TEST_DIR, 'key.pem'),
|
|
49
|
+
});
|
|
50
|
+
const cert = ssl.getCert();
|
|
51
|
+
expect(cert).toContain('BEGIN CERTIFICATE');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('getKey reads key file', () => {
|
|
55
|
+
const ssl = new SslConfig({
|
|
56
|
+
enabled: true,
|
|
57
|
+
certPath: join(TEST_DIR, 'cert.pem'),
|
|
58
|
+
keyPath: join(TEST_DIR, 'key.pem'),
|
|
59
|
+
});
|
|
60
|
+
const key = ssl.getKey();
|
|
61
|
+
expect(key).toContain('BEGIN PRIVATE KEY');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('getCert returns null for missing file', () => {
|
|
65
|
+
const ssl = new SslConfig({ enabled: true, certPath: '/missing' });
|
|
66
|
+
expect(ssl.getCert()).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('getHstsHeader returns HSTS value when enabled', () => {
|
|
70
|
+
const ssl = new SslConfig({ enabled: true, hsts: true });
|
|
71
|
+
const header = ssl.getHstsHeader();
|
|
72
|
+
expect(header).toContain('max-age=31536000');
|
|
73
|
+
expect(header).toContain('includeSubDomains');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('getHstsHeader returns null when disabled', () => {
|
|
77
|
+
const ssl = new SslConfig({ enabled: false });
|
|
78
|
+
expect(ssl.getHstsHeader()).toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('getServerOptions returns cert and key', () => {
|
|
82
|
+
const ssl = new SslConfig({
|
|
83
|
+
enabled: true,
|
|
84
|
+
certPath: join(TEST_DIR, 'cert.pem'),
|
|
85
|
+
keyPath: join(TEST_DIR, 'key.pem'),
|
|
86
|
+
});
|
|
87
|
+
const opts = ssl.getServerOptions();
|
|
88
|
+
expect(opts).not.toBeNull();
|
|
89
|
+
expect(opts!.cert).toContain('CERTIFICATE');
|
|
90
|
+
expect(opts!.key).toContain('PRIVATE KEY');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('getServerOptions returns null when files missing', () => {
|
|
94
|
+
const ssl = new SslConfig({ enabled: true });
|
|
95
|
+
expect(ssl.getServerOptions()).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
});
|