vellum 0.2.8 → 0.2.9
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/bun.lock +2 -2
- package/package.json +3 -2
- package/src/__tests__/config-schema.test.ts +0 -6
- package/src/__tests__/forbidden-legacy-symbols.test.ts +69 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +91 -11
- package/src/__tests__/ingress-url-consistency.test.ts +214 -0
- package/src/__tests__/ipc-snapshot.test.ts +17 -16
- package/src/__tests__/oauth2-gateway-transport.test.ts +7 -1
- package/src/__tests__/public-ingress-urls.test.ts +50 -34
- package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
- package/src/__tests__/runtime-events-sse.test.ts +162 -0
- package/src/__tests__/twilio-provider.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +4 -4
- package/src/__tests__/twitter-auth-handler.test.ts +87 -2
- package/src/calls/call-domain.ts +8 -6
- package/src/calls/twilio-config.ts +2 -3
- package/src/config/bundled-skills/tasks/TOOLS.json +25 -0
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
- package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
- package/src/config/defaults.ts +1 -2
- package/src/config/schema.ts +2 -6
- package/src/config/vellum-skills/google-oauth-setup/SKILL.md +5 -4
- package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -2
- package/src/config/vellum-skills/telegram-setup/SKILL.md +3 -3
- package/src/daemon/handlers/config.ts +33 -50
- package/src/daemon/handlers/shared.ts +1 -0
- package/src/daemon/handlers/subagents.ts +85 -2
- package/src/daemon/handlers/twitter-auth.ts +31 -2
- package/src/daemon/ipc-contract-inventory.json +4 -4
- package/src/daemon/ipc-contract.ts +25 -21
- package/src/daemon/lifecycle.ts +9 -4
- package/src/daemon/server.ts +7 -0
- package/src/daemon/session-tool-setup.ts +1 -1
- package/src/inbound/public-ingress-urls.ts +36 -30
- package/src/memory/db.ts +132 -5
- package/src/memory/llm-usage-store.ts +0 -1
- package/src/memory/runs-store.ts +51 -3
- package/src/memory/schema.ts +2 -2
- package/src/runtime/gateway-client.ts +7 -1
- package/src/runtime/http-server.ts +95 -10
- package/src/runtime/routes/channel-routes.ts +7 -2
- package/src/runtime/routes/events-routes.ts +79 -0
- package/src/runtime/routes/run-routes.ts +43 -0
- package/src/runtime/run-orchestrator.ts +64 -7
- package/src/security/oauth-callback-registry.ts +10 -0
- package/src/security/oauth2.ts +41 -7
- package/src/subagent/manager.ts +3 -1
- package/src/tools/tasks/work-item-run.ts +78 -0
- package/src/util/platform.ts +1 -1
- package/src/work-items/work-item-runner.ts +171 -0
- package/src/__tests__/handlers-twilio-config.test.ts +0 -221
- package/src/calls/__tests__/twilio-webhook-urls.test.ts +0 -162
- package/src/calls/twilio-webhook-urls.ts +0 -47
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, mock, beforeEach } from 'bun:test';
|
|
2
|
-
import { mkdtempSync } from 'node:fs';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import * as net from 'node:net';
|
|
6
|
-
|
|
7
|
-
const testDir = mkdtempSync(join(tmpdir(), 'handlers-twilio-cfg-test-'));
|
|
8
|
-
|
|
9
|
-
let rawConfigStore: Record<string, unknown> = {};
|
|
10
|
-
const saveRawConfigCalls: Record<string, unknown>[] = [];
|
|
11
|
-
|
|
12
|
-
mock.module('../config/loader.js', () => ({
|
|
13
|
-
getConfig: () => ({}),
|
|
14
|
-
loadConfig: () => ({}),
|
|
15
|
-
loadRawConfig: () => ({ ...rawConfigStore }),
|
|
16
|
-
saveRawConfig: (cfg: Record<string, unknown>) => {
|
|
17
|
-
saveRawConfigCalls.push(cfg);
|
|
18
|
-
rawConfigStore = { ...cfg };
|
|
19
|
-
},
|
|
20
|
-
saveConfig: () => {},
|
|
21
|
-
invalidateConfigCache: () => {},
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
|
-
mock.module('../util/platform.js', () => ({
|
|
25
|
-
getRootDir: () => testDir,
|
|
26
|
-
getDataDir: () => testDir,
|
|
27
|
-
getIpcBlobDir: () => join(testDir, 'ipc-blobs'),
|
|
28
|
-
isMacOS: () => process.platform === 'darwin',
|
|
29
|
-
isLinux: () => process.platform === 'linux',
|
|
30
|
-
isWindows: () => process.platform === 'win32',
|
|
31
|
-
getSocketPath: () => join(testDir, 'test.sock'),
|
|
32
|
-
getPidPath: () => join(testDir, 'test.pid'),
|
|
33
|
-
getDbPath: () => join(testDir, 'test.db'),
|
|
34
|
-
getLogPath: () => join(testDir, 'test.log'),
|
|
35
|
-
ensureDataDir: () => {},
|
|
36
|
-
}));
|
|
37
|
-
|
|
38
|
-
mock.module('../util/logger.js', () => ({
|
|
39
|
-
getLogger: () => ({
|
|
40
|
-
info: () => {},
|
|
41
|
-
warn: () => {},
|
|
42
|
-
error: () => {},
|
|
43
|
-
debug: () => {},
|
|
44
|
-
trace: () => {},
|
|
45
|
-
fatal: () => {},
|
|
46
|
-
child: () => ({
|
|
47
|
-
info: () => {},
|
|
48
|
-
warn: () => {},
|
|
49
|
-
error: () => {},
|
|
50
|
-
debug: () => {},
|
|
51
|
-
}),
|
|
52
|
-
}),
|
|
53
|
-
}));
|
|
54
|
-
|
|
55
|
-
mock.module('../memory/app-store.js', () => ({
|
|
56
|
-
queryAppRecords: () => [],
|
|
57
|
-
createAppRecord: () => {},
|
|
58
|
-
updateAppRecord: () => {},
|
|
59
|
-
deleteAppRecord: () => {},
|
|
60
|
-
listApps: () => [],
|
|
61
|
-
getApp: () => undefined,
|
|
62
|
-
createApp: () => {},
|
|
63
|
-
updateApp: () => {},
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
mock.module('../slack/slack-webhook.js', () => ({
|
|
67
|
-
postToSlackWebhook: async () => {},
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
import { handleMessage, type HandlerContext } from '../daemon/handlers.js';
|
|
71
|
-
import type {
|
|
72
|
-
TwilioWebhookConfigRequest,
|
|
73
|
-
ServerMessage,
|
|
74
|
-
} from '../daemon/ipc-contract.js';
|
|
75
|
-
|
|
76
|
-
function createTestContext(): { ctx: HandlerContext; sent: ServerMessage[] } {
|
|
77
|
-
const sent: ServerMessage[] = [];
|
|
78
|
-
const ctx: HandlerContext = {
|
|
79
|
-
sessions: new Map(),
|
|
80
|
-
socketToSession: new Map(),
|
|
81
|
-
cuSessions: new Map(),
|
|
82
|
-
socketToCuSession: new Map(),
|
|
83
|
-
cuObservationParseSequence: new Map(),
|
|
84
|
-
socketSandboxOverride: new Map(),
|
|
85
|
-
sharedRequestTimestamps: [],
|
|
86
|
-
debounceTimers: new Map(),
|
|
87
|
-
suppressConfigReload: false,
|
|
88
|
-
setSuppressConfigReload: () => {},
|
|
89
|
-
updateConfigFingerprint: () => {},
|
|
90
|
-
send: (_socket, msg) => { sent.push(msg); },
|
|
91
|
-
broadcast: () => {},
|
|
92
|
-
clearAllSessions: () => 0,
|
|
93
|
-
getOrCreateSession: () => { throw new Error('not implemented'); },
|
|
94
|
-
touchSession: () => {},
|
|
95
|
-
};
|
|
96
|
-
return { ctx, sent };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
describe('Twilio webhook config handler', () => {
|
|
100
|
-
beforeEach(() => {
|
|
101
|
-
rawConfigStore = {};
|
|
102
|
-
saveRawConfigCalls.length = 0;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('get returns empty string when no config set', () => {
|
|
106
|
-
rawConfigStore = {};
|
|
107
|
-
|
|
108
|
-
const msg: TwilioWebhookConfigRequest = {
|
|
109
|
-
type: 'twilio_webhook_config',
|
|
110
|
-
action: 'get',
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const { ctx, sent } = createTestContext();
|
|
114
|
-
handleMessage(msg, {} as net.Socket, ctx);
|
|
115
|
-
|
|
116
|
-
expect(sent).toHaveLength(1);
|
|
117
|
-
const res = sent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
118
|
-
expect(res.type).toBe('twilio_webhook_config_response');
|
|
119
|
-
expect(res.success).toBe(true);
|
|
120
|
-
expect(res.webhookBaseUrl).toBe('');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('set persists value and returns it', () => {
|
|
124
|
-
rawConfigStore = {};
|
|
125
|
-
|
|
126
|
-
const msg: TwilioWebhookConfigRequest = {
|
|
127
|
-
type: 'twilio_webhook_config',
|
|
128
|
-
action: 'set',
|
|
129
|
-
webhookBaseUrl: 'https://example.com/twilio',
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const { ctx, sent } = createTestContext();
|
|
133
|
-
handleMessage(msg, {} as net.Socket, ctx);
|
|
134
|
-
|
|
135
|
-
expect(sent).toHaveLength(1);
|
|
136
|
-
const res = sent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
137
|
-
expect(res.type).toBe('twilio_webhook_config_response');
|
|
138
|
-
expect(res.success).toBe(true);
|
|
139
|
-
expect(res.webhookBaseUrl).toBe('https://example.com/twilio');
|
|
140
|
-
|
|
141
|
-
expect(saveRawConfigCalls).toHaveLength(1);
|
|
142
|
-
const saved = saveRawConfigCalls[0] as { calls?: { webhookBaseUrl?: string } };
|
|
143
|
-
expect(saved.calls?.webhookBaseUrl).toBe('https://example.com/twilio');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('set normalizes trailing slashes', () => {
|
|
147
|
-
rawConfigStore = {};
|
|
148
|
-
|
|
149
|
-
const msg: TwilioWebhookConfigRequest = {
|
|
150
|
-
type: 'twilio_webhook_config',
|
|
151
|
-
action: 'set',
|
|
152
|
-
webhookBaseUrl: 'https://example.com/twilio///',
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const { ctx, sent } = createTestContext();
|
|
156
|
-
handleMessage(msg, {} as net.Socket, ctx);
|
|
157
|
-
|
|
158
|
-
expect(sent).toHaveLength(1);
|
|
159
|
-
const res = sent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
160
|
-
expect(res.webhookBaseUrl).toBe('https://example.com/twilio');
|
|
161
|
-
|
|
162
|
-
const saved = saveRawConfigCalls[0] as { calls?: { webhookBaseUrl?: string } };
|
|
163
|
-
expect(saved.calls?.webhookBaseUrl).toBe('https://example.com/twilio');
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test('set treats empty string as unset', () => {
|
|
167
|
-
rawConfigStore = { calls: { webhookBaseUrl: 'https://example.com/twilio' } };
|
|
168
|
-
saveRawConfigCalls.length = 0;
|
|
169
|
-
|
|
170
|
-
const msg: TwilioWebhookConfigRequest = {
|
|
171
|
-
type: 'twilio_webhook_config',
|
|
172
|
-
action: 'set',
|
|
173
|
-
webhookBaseUrl: '',
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const { ctx, sent } = createTestContext();
|
|
177
|
-
handleMessage(msg, {} as net.Socket, ctx);
|
|
178
|
-
|
|
179
|
-
expect(sent).toHaveLength(1);
|
|
180
|
-
const res = sent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
181
|
-
expect(res.success).toBe(true);
|
|
182
|
-
expect(res.webhookBaseUrl).toBe('');
|
|
183
|
-
|
|
184
|
-
const saved = saveRawConfigCalls[0] as { calls?: { webhookBaseUrl?: string } };
|
|
185
|
-
expect(saved.calls?.webhookBaseUrl).toBeUndefined();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('get after set roundtrip works', () => {
|
|
189
|
-
rawConfigStore = {};
|
|
190
|
-
|
|
191
|
-
// Set
|
|
192
|
-
const setMsg: TwilioWebhookConfigRequest = {
|
|
193
|
-
type: 'twilio_webhook_config',
|
|
194
|
-
action: 'set',
|
|
195
|
-
webhookBaseUrl: 'https://my-server.ngrok.io',
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const { ctx: setCtx, sent: setSent } = createTestContext();
|
|
199
|
-
handleMessage(setMsg, {} as net.Socket, setCtx);
|
|
200
|
-
|
|
201
|
-
expect(setSent).toHaveLength(1);
|
|
202
|
-
const setRes = setSent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
203
|
-
expect(setRes.success).toBe(true);
|
|
204
|
-
expect(setRes.webhookBaseUrl).toBe('https://my-server.ngrok.io');
|
|
205
|
-
|
|
206
|
-
// Get (rawConfigStore was updated by the mock saveRawConfig)
|
|
207
|
-
const getMsg: TwilioWebhookConfigRequest = {
|
|
208
|
-
type: 'twilio_webhook_config',
|
|
209
|
-
action: 'get',
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const { ctx: getCtx, sent: getSent } = createTestContext();
|
|
213
|
-
handleMessage(getMsg, {} as net.Socket, getCtx);
|
|
214
|
-
|
|
215
|
-
expect(getSent).toHaveLength(1);
|
|
216
|
-
const getRes = getSent[0] as { type: string; webhookBaseUrl: string; success: boolean };
|
|
217
|
-
expect(getRes.type).toBe('twilio_webhook_config_response');
|
|
218
|
-
expect(getRes.success).toBe(true);
|
|
219
|
-
expect(getRes.webhookBaseUrl).toBe('https://my-server.ngrok.io');
|
|
220
|
-
});
|
|
221
|
-
});
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test';
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Mocks — silence logger output during tests
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
function makeLoggerStub(): Record<string, unknown> {
|
|
8
|
-
const stub: Record<string, unknown> = {};
|
|
9
|
-
for (const m of ['info', 'warn', 'error', 'debug', 'trace', 'fatal', 'silent', 'child']) {
|
|
10
|
-
stub[m] = m === 'child' ? () => makeLoggerStub() : () => {};
|
|
11
|
-
}
|
|
12
|
-
return stub;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
mock.module('../../util/logger.js', () => ({
|
|
16
|
-
getLogger: () => makeLoggerStub(),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
normalizeBaseUrl,
|
|
21
|
-
buildTwilioVoiceWebhookUrl,
|
|
22
|
-
buildTwilioStatusCallbackUrl,
|
|
23
|
-
getWebhookBaseUrl,
|
|
24
|
-
} from '../twilio-webhook-urls.js';
|
|
25
|
-
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// normalizeBaseUrl
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
describe('normalizeBaseUrl', () => {
|
|
31
|
-
test('returns already-clean URL unchanged', () => {
|
|
32
|
-
expect(normalizeBaseUrl('https://example.com')).toBe('https://example.com');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test('strips trailing slash', () => {
|
|
36
|
-
expect(normalizeBaseUrl('https://example.com/')).toBe('https://example.com');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('strips multiple trailing slashes', () => {
|
|
40
|
-
expect(normalizeBaseUrl('https://example.com///')).toBe('https://example.com');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('trims leading and trailing whitespace', () => {
|
|
44
|
-
expect(normalizeBaseUrl(' https://example.com ')).toBe('https://example.com');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('trims whitespace and strips trailing slash together', () => {
|
|
48
|
-
expect(normalizeBaseUrl(' https://example.com/ ')).toBe('https://example.com');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// buildTwilioVoiceWebhookUrl
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
describe('buildTwilioVoiceWebhookUrl', () => {
|
|
57
|
-
test('returns correct URL with callSessionId', () => {
|
|
58
|
-
const url = buildTwilioVoiceWebhookUrl('https://example.com', 'session-123');
|
|
59
|
-
expect(url).toBe('https://example.com/webhooks/twilio/voice?callSessionId=session-123');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('normalizes base URL before composing', () => {
|
|
63
|
-
const url = buildTwilioVoiceWebhookUrl('https://example.com/', 'abc');
|
|
64
|
-
expect(url).toBe('https://example.com/webhooks/twilio/voice?callSessionId=abc');
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// buildTwilioStatusCallbackUrl
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
describe('buildTwilioStatusCallbackUrl', () => {
|
|
73
|
-
test('returns correct URL', () => {
|
|
74
|
-
const url = buildTwilioStatusCallbackUrl('https://example.com');
|
|
75
|
-
expect(url).toBe('https://example.com/webhooks/twilio/status');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test('normalizes base URL before composing', () => {
|
|
79
|
-
const url = buildTwilioStatusCallbackUrl('https://example.com/');
|
|
80
|
-
expect(url).toBe('https://example.com/webhooks/twilio/status');
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// getWebhookBaseUrl
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
describe('getWebhookBaseUrl', () => {
|
|
89
|
-
let savedEnv: string | undefined;
|
|
90
|
-
|
|
91
|
-
beforeEach(() => {
|
|
92
|
-
savedEnv = process.env.TWILIO_WEBHOOK_BASE_URL;
|
|
93
|
-
delete process.env.TWILIO_WEBHOOK_BASE_URL;
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
afterEach(() => {
|
|
97
|
-
if (savedEnv !== undefined) {
|
|
98
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = savedEnv;
|
|
99
|
-
} else {
|
|
100
|
-
delete process.env.TWILIO_WEBHOOK_BASE_URL;
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
test('uses config value when set', () => {
|
|
105
|
-
const result = getWebhookBaseUrl({ calls: { webhookBaseUrl: 'https://config.example.com/' } });
|
|
106
|
-
expect(result).toBe('https://config.example.com');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('falls back to env var when config value is empty', () => {
|
|
110
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = 'https://env.example.com/';
|
|
111
|
-
const result = getWebhookBaseUrl({ calls: { webhookBaseUrl: '' } });
|
|
112
|
-
expect(result).toBe('https://env.example.com');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('falls back to env var when config value is undefined', () => {
|
|
116
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = 'https://env.example.com';
|
|
117
|
-
const result = getWebhookBaseUrl({ calls: {} });
|
|
118
|
-
expect(result).toBe('https://env.example.com');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('throws when neither config nor env var is set', () => {
|
|
122
|
-
expect(() => getWebhookBaseUrl({ calls: { webhookBaseUrl: '' } })).toThrow(
|
|
123
|
-
/No webhook base URL configured/,
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('throws when config is undefined and env var is unset', () => {
|
|
128
|
-
expect(() => getWebhookBaseUrl({ calls: {} })).toThrow(
|
|
129
|
-
/No webhook base URL configured/,
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('normalizes the returned URL', () => {
|
|
134
|
-
const result = getWebhookBaseUrl({ calls: { webhookBaseUrl: ' https://example.com/ ' } });
|
|
135
|
-
expect(result).toBe('https://example.com');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test('falls through when config value is whitespace-only', () => {
|
|
139
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = 'https://env.example.com';
|
|
140
|
-
const result = getWebhookBaseUrl({ calls: { webhookBaseUrl: ' ' } });
|
|
141
|
-
expect(result).toBe('https://env.example.com');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test('falls through when config value is slash-only', () => {
|
|
145
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = 'https://env.example.com';
|
|
146
|
-
const result = getWebhookBaseUrl({ calls: { webhookBaseUrl: '///' } });
|
|
147
|
-
expect(result).toBe('https://env.example.com');
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test('throws when config is whitespace-only and env var is unset', () => {
|
|
151
|
-
expect(() => getWebhookBaseUrl({ calls: { webhookBaseUrl: ' ' } })).toThrow(
|
|
152
|
-
/No webhook base URL configured/,
|
|
153
|
-
);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('throws when env var is whitespace-only and config is empty', () => {
|
|
157
|
-
process.env.TWILIO_WEBHOOK_BASE_URL = ' ';
|
|
158
|
-
expect(() => getWebhookBaseUrl({ calls: {} })).toThrow(
|
|
159
|
-
/No webhook base URL configured/,
|
|
160
|
-
);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Twilio webhook URL helpers.
|
|
3
|
-
*
|
|
4
|
-
* This module is a thin backward-compat wrapper that delegates to the
|
|
5
|
-
* centralized URL builders in inbound/public-ingress-urls.ts.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
getPublicBaseUrl,
|
|
10
|
-
type IngressConfig,
|
|
11
|
-
} from '../inbound/public-ingress-urls.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Resolve the webhook base URL from config, falling back to the
|
|
15
|
-
* TWILIO_WEBHOOK_BASE_URL environment variable with a deprecation warning.
|
|
16
|
-
* Throws if neither source provides a value.
|
|
17
|
-
*
|
|
18
|
-
* @deprecated Use `getPublicBaseUrl` from `inbound/public-ingress-urls.ts` instead.
|
|
19
|
-
*/
|
|
20
|
-
export function getWebhookBaseUrl(config: { calls: { webhookBaseUrl?: string }; ingress?: { publicBaseUrl?: string } }): string {
|
|
21
|
-
return getPublicBaseUrl(config as IngressConfig);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Trim whitespace and strip trailing slash from a URL string.
|
|
26
|
-
*/
|
|
27
|
-
export function normalizeBaseUrl(url: string): string {
|
|
28
|
-
return url.trim().replace(/\/+$/, '');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Build the Twilio voice webhook URL for a given call session.
|
|
33
|
-
*
|
|
34
|
-
* @deprecated Use `getTwilioVoiceWebhookUrl` from `inbound/public-ingress-urls.ts` instead.
|
|
35
|
-
*/
|
|
36
|
-
export function buildTwilioVoiceWebhookUrl(baseUrl: string, callSessionId: string): string {
|
|
37
|
-
return `${normalizeBaseUrl(baseUrl)}/webhooks/twilio/voice?callSessionId=${callSessionId}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Build the Twilio status callback URL.
|
|
42
|
-
*
|
|
43
|
-
* @deprecated Use `getTwilioStatusCallbackUrl` from `inbound/public-ingress-urls.ts` instead.
|
|
44
|
-
*/
|
|
45
|
-
export function buildTwilioStatusCallbackUrl(baseUrl: string): string {
|
|
46
|
-
return `${normalizeBaseUrl(baseUrl)}/webhooks/twilio/status`;
|
|
47
|
-
}
|