aki-pro-max 2.3.3

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.
Files changed (83) hide show
  1. package/.env.example +34 -0
  2. package/AICOWORKER-NATIVE-TOOLS.md +60 -0
  3. package/CLAUDE-LIVE-TOOLS-EVIDENCE.json +94 -0
  4. package/FULL-TRACE-EVIDENCE.json +109 -0
  5. package/ISSUE-1-REMOTE.json +1 -0
  6. package/ISSUE-2-POSTREVIEW.json +1 -0
  7. package/ISSUE-2-REMOTE.json +1 -0
  8. package/KEY-ROTATION-EVIDENCE.json +9 -0
  9. package/LICENSE +21 -0
  10. package/PMN-9ROUTER-FINAL.md +13 -0
  11. package/RAPID-BASIL-RECONCILIATION.json +10 -0
  12. package/README.md +232 -0
  13. package/RELEASE-ARCHIVE.json +15 -0
  14. package/SECURITY-RECONCILIATION.json +30 -0
  15. package/SECURITY.md +16 -0
  16. package/TEST-ISSUES12-FINAL.txt +0 -0
  17. package/TEST-ISSUES12-HARNESS.txt +0 -0
  18. package/VERIFICATION-REPORT.md +58 -0
  19. package/VERIFIER-ISSUES12-FINAL.txt +0 -0
  20. package/VERIFY-RELEASE-ISSUES12-FINAL.txt +0 -0
  21. package/VERIFY-RELEASE-ISSUES12-HARNESS.txt +0 -0
  22. package/bin/aki-pro-max.js +98 -0
  23. package/docs/ADMIN-GUI-CONTRACT.md +29 -0
  24. package/docs/ARCHITECTURE.md +109 -0
  25. package/docs/CAPABILITY-MATRIX.md +44 -0
  26. package/docs/CORRELATION-DESIGN.md +226 -0
  27. package/docs/FAIL-CLOSED-ISSUE-HARNESS.md +21 -0
  28. package/docs/WEB-SESSION-TRANSPORT-DESIGN.md +423 -0
  29. package/docs/assets/control-plane.jpg +0 -0
  30. package/gitleaks-report-all.json +1 -0
  31. package/gitleaks-report-latest.json +1 -0
  32. package/gitleaks-report.json +1 -0
  33. package/package.json +33 -0
  34. package/scripts/eventual-tool-loop.mjs +55 -0
  35. package/scripts/install-local.ps1 +35 -0
  36. package/scripts/live-eventual-multitool.mjs +18 -0
  37. package/scripts/upgrade-admin-v232.mjs +33 -0
  38. package/scripts/verify-issue-closure.mjs +81 -0
  39. package/scripts/verify-release.mjs +31 -0
  40. package/src/admin-auth.mjs +94 -0
  41. package/src/admin.mjs +133 -0
  42. package/src/canonical.mjs +23 -0
  43. package/src/config.mjs +88 -0
  44. package/src/correlation-store.mjs +120 -0
  45. package/src/errors.mjs +18 -0
  46. package/src/index.mjs +4 -0
  47. package/src/openai-response.mjs +72 -0
  48. package/src/openai.mjs +104 -0
  49. package/src/postman-events.mjs +43 -0
  50. package/src/postman-request.mjs +49 -0
  51. package/src/schema.mjs +35 -0
  52. package/src/server.mjs +73 -0
  53. package/src/session-store.mjs +48 -0
  54. package/src/sse.mjs +13 -0
  55. package/src/transport.mjs +90 -0
  56. package/src/web-session-events.mjs +358 -0
  57. package/src/web-session-request.mjs +280 -0
  58. package/test/9router-executor.integration.test.mjs +207 -0
  59. package/test/admin-auth.test.mjs +47 -0
  60. package/test/admin.test.mjs +68 -0
  61. package/test/config.test.mjs +14 -0
  62. package/test/contract.test.mjs +14 -0
  63. package/test/correlation-store.test.mjs +19 -0
  64. package/test/correlation.integration.test.mjs +48 -0
  65. package/test/eventual-tool-loop.test.mjs +42 -0
  66. package/test/fixtures/text.json +8 -0
  67. package/test/fixtures/tool.json +7 -0
  68. package/test/fixtures/web-session-observed-done.json +12 -0
  69. package/test/fixtures/web-session-tool-fragments.json +14 -0
  70. package/test/full-ingress/alias-loader.mjs +22 -0
  71. package/test/full-ingress/run-full-ingress.mjs +207 -0
  72. package/test/full-ingress/seed-9router.mjs +36 -0
  73. package/test/helpers.mjs +9 -0
  74. package/test/issue-closure-harness.test.mjs +49 -0
  75. package/test/model-thinking.test.mjs +20 -0
  76. package/test/protocol.test.mjs +16 -0
  77. package/test/request.test.mjs +10 -0
  78. package/test/session-store.test.mjs +20 -0
  79. package/test/web-session-builder.test.mjs +124 -0
  80. package/test/web-session-events.test.mjs +241 -0
  81. package/test/web-session-integration.test.mjs +103 -0
  82. package/test/web-session-tools.test.mjs +52 -0
  83. package/version.json +8 -0
@@ -0,0 +1,207 @@
1
+ import http from 'node:http';
2
+ import net from 'node:net';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs/promises';
5
+ import { spawn, spawnSync } from 'node:child_process';
6
+ import { once } from 'node:events';
7
+ import assert from 'node:assert/strict';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { createProviderServer } from '../../src/server.mjs';
10
+ import { DatabaseSync } from 'node:sqlite';
11
+
12
+ const here = path.dirname(fileURLToPath(import.meta.url));
13
+ const serviceRoot = path.resolve(here, '../..');
14
+ const workspaceRoot = path.resolve(serviceRoot, '../..');
15
+ const routerRoot = process.env.NINEROUTER_ROOT || path.join(workspaceRoot, 'tmp/9router-pinned-17c4cc7');
16
+ const expectReplayFix = process.env.EXPECT_REPLAY_FIX === '1';
17
+ const runRoot = path.join(serviceRoot, '.full-ingress-run');
18
+ const dataDir = path.join(runRoot, '9router-data');
19
+ const nextLogPath = path.join(runRoot, 'next.log');
20
+ const resultPath = path.join(runRoot, 'result.json');
21
+ const providerId = 'openai-compatible-chat-full-ingress';
22
+ const prefix = 'pmnfull';
23
+ const model = 'fixture-model';
24
+ const clientKey = 'sk-client-full-ingress-offline';
25
+ const facadeKey = 'sk-facade-full-ingress-offline';
26
+ const upstreamToken = 'pstmn-offline-sentinel';
27
+ const startedPids = [];
28
+ let nextChild;
29
+
30
+ await fs.rm(runRoot, { recursive: true, force: true });
31
+ await fs.mkdir(dataDir, { recursive: true });
32
+
33
+ const freePort = () => new Promise((resolve, reject) => {
34
+ const s = net.createServer(); s.unref(); s.on('error', reject);
35
+ s.listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => resolve(p)); });
36
+ });
37
+ const sse = (res, frames, delayDone = 0) => {
38
+ res.writeHead(200, { 'content-type': 'text/event-stream', connection: 'close' });
39
+ for (const frame of frames) res.write(`event: message\ndata: ${JSON.stringify(frame)}\n\n`);
40
+ const done = () => res.end('data: [DONE]\n\n');
41
+ delayDone ? setTimeout(done, delayDone) : done();
42
+ };
43
+
44
+ const captures = [];
45
+ let toolSequence = 0;
46
+ let cancelObserved = false;
47
+ const rawMock = http.createServer(async (req, res) => {
48
+ const chunks = []; for await (const chunk of req) chunks.push(chunk);
49
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8'));
50
+ captures.push({ url: req.url, method: req.method, headers: req.headers, body });
51
+ assert.equal(req.socket.remoteAddress === '127.0.0.1' || req.socket.remoteAddress === '::ffff:127.0.0.1', true);
52
+ assert.equal(req.headers['x-access-token'], upstreamToken);
53
+ const query = String(body?.input?.query || '');
54
+ if (query.includes('cancel-fixture')) {
55
+ req.on('close', () => { cancelObserved = true; });
56
+ res.on('close', () => { if (!res.writableEnded) cancelObserved = true; });
57
+ sse(res, [{ eventType: 'conversation', data: { id: 'conv_cancel' } }], 10000);
58
+ return;
59
+ }
60
+ if (body?.input?.chatType === 'TOOL_RESPONSE') {
61
+ sse(res, [{ eventType: 'conversation', data: { id: 'conv_tool' } }, { eventType: 'textChunk', data: { textContent: 'native final' } }]);
62
+ return;
63
+ }
64
+ if (query.includes('tool-fixture')) {
65
+ toolSequence++;
66
+ sse(res, [
67
+ { eventType: 'conversation', data: { id: 'conv_tool' } },
68
+ { eventType: 'toolCallChunk', data: { id: 'call_raw', index: 0, name: 'openai__lookup', toolCallGroupId: 'group_raw' } },
69
+ { eventType: 'toolCallChunk', data: { id: 'call_raw', index: 0, arguments: '{"q":' } },
70
+ { eventType: 'toolCallChunk', data: { id: 'call_raw', index: 0, arguments: '"x"}' } },
71
+ ]);
72
+ return;
73
+ }
74
+ const text = query.includes('sse-fixture') ? 'hello sse' : 'hello json';
75
+ sse(res, [{ eventType: 'conversation', data: { id: 'conv_text' } }, { eventType: 'textChunk', data: { textContent: text } }]);
76
+ });
77
+ rawMock.listen(0, '127.0.0.1'); await once(rawMock, 'listening');
78
+ const rawBase = `http://127.0.0.1:${rawMock.address().port}`;
79
+
80
+ const facadeConfig = {
81
+ host: '127.0.0.1', port: 0, apiKey: facadeKey, upstreamToken, workspaceId: 'ws_full_ingress',
82
+ upstreamOrigin: 'https://offline.invalid', chatPath: '/chat', appVersion: 'full-ingress/0.1', models: [model],
83
+ timeoutMs: 15000, maxBodyBytes: 1024 * 1024, maxEventBytes: 1024 * 1024, maxOutputBytes: 1024 * 1024,
84
+ maxToolArgumentBytes: 1024 * 1024, maxTools: 64, maxConcurrent: 8, exposeHealthDetails: false,
85
+ correlationEnabled: true, principalKey: 'full-ingress-principal', credentialSlotId: 'full-ingress-slot',
86
+ maxToolResultBytes: 1024 * 1024, correlationPendingTtlMs: 30000, correlationTerminalTtlMs: 30000,
87
+ correlationInflightTtlMs: 30000, correlationMaxGroups: 100, correlationMaxCalls: 100, correlationMaxBytes: 1024 * 1024,
88
+ };
89
+ const facade = createProviderServer(facadeConfig, {
90
+ logger: { error(line) { process.stderr.write(`[facade] ${line}\n`); } },
91
+ fetchImpl: (_url, init) => fetch(`${rawBase}/chat`, init),
92
+ });
93
+ facade.listen(0, '127.0.0.1'); await once(facade, 'listening');
94
+ const facadeBase = `http://127.0.0.1:${facade.address().port}`;
95
+
96
+ const commonEnv = {
97
+ ...process.env, DATA_DIR: dataDir, NINEROUTER_ROOT: routerRoot, NEXT_TELEMETRY_DISABLED: '1',
98
+ FULL_INGRESS_PROVIDER_ID: providerId, FULL_INGRESS_PREFIX: prefix, FULL_INGRESS_FACADE_BASE: facadeBase,
99
+ FULL_INGRESS_PROVIDER_API_KEY: facadeKey, FULL_INGRESS_CLIENT_API_KEY: clientKey,
100
+ HTTP_PROXY: 'http://127.0.0.1:9', HTTPS_PROXY: 'http://127.0.0.1:9', ALL_PROXY: 'http://127.0.0.1:9',
101
+ NO_PROXY: '127.0.0.1,localhost,::1', NODE_NO_WARNINGS: '1',
102
+ };
103
+ const seed = spawnSync(process.execPath, ['--loader', pathToFileURL(path.join(here, 'alias-loader.mjs')).href, path.join(here, 'seed-9router.mjs')], {
104
+ cwd: routerRoot, env: commonEnv, encoding: 'utf8', timeout: 30000,
105
+ });
106
+ assert.equal(seed.status, 0, `seed failed\n${seed.stdout}\n${seed.stderr}`);
107
+
108
+ const nextPort = await freePort();
109
+ const nextBin = path.join(routerRoot, 'node_modules/next/dist/bin/next');
110
+ const out = await fs.open(nextLogPath, 'w');
111
+ nextChild = spawn(process.execPath, [nextBin, 'dev', '--webpack', '--hostname', '127.0.0.1', '--port', String(nextPort)], {
112
+ cwd: routerRoot, env: commonEnv, stdio: ['ignore', out.fd, out.fd], windowsHide: true,
113
+ });
114
+ startedPids.push(nextChild.pid);
115
+ const nextBase = `http://127.0.0.1:${nextPort}`;
116
+
117
+ async function waitReady() {
118
+ const deadline = Date.now() + 90000; let last = '';
119
+ while (Date.now() < deadline) {
120
+ if (nextChild.exitCode !== null) throw new Error(`Next exited ${nextChild.exitCode}`);
121
+ try { const r = await fetch(`${nextBase}/api/health`); last = `${r.status} ${await r.text()}`; if (r.status < 500) return; } catch (e) { last = e.message; }
122
+ await new Promise(r => setTimeout(r, 500));
123
+ }
124
+ throw new Error(`Next readiness timeout: ${last}`);
125
+ }
126
+
127
+ const request = (body, options = {}) => fetch(`${nextBase}/v1/chat/completions`, {
128
+ method: 'POST', headers: { 'content-type': 'application/json', authorization: `Bearer ${clientKey}`, ...(options.headers || {}) },
129
+ body: JSON.stringify(body), signal: options.signal,
130
+ });
131
+
132
+ const observations = {};
133
+ try {
134
+ await waitReady();
135
+ let before = captures.length;
136
+ const jsonRes = await request({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'json-fixture' }], stream: false });
137
+ assert.equal(jsonRes.status, 200); const jsonBody = await jsonRes.json();
138
+ assert.equal(jsonBody.choices[0].message.content, 'hello json'); assert.equal(captures.length, before + 1);
139
+ assert.equal(captures.at(-1).body.devModeOptions.selectedModel, model);
140
+ observations.json = { status: jsonRes.status, content: jsonBody.choices[0].message.content, selectedModel: captures.at(-1).body.devModeOptions.selectedModel };
141
+
142
+ before = captures.length;
143
+ const sseRes = await request({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'sse-fixture' }], stream: true });
144
+ assert.equal(sseRes.status, 200); const wire = await sseRes.text();
145
+ assert.match(wire, /hello sse/); assert.match(wire, /"finish_reason":"stop"/); assert.match(wire, /data: \[DONE\]/); assert.equal(captures.length, before + 1);
146
+ observations.sse = { status: sseRes.status, hasText: true, hasStop: true, hasDone: true };
147
+
148
+ const tool = { type: 'function', function: { name: 'lookup', description: 'lookup', parameters: { type: 'object', properties: { q: { type: 'string' } }, required: ['q'], additionalProperties: false } } };
149
+ before = captures.length;
150
+ const firstRes = await request({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'tool-fixture' }], tools: [tool], stream: false });
151
+ assert.equal(firstRes.status, 200); const firstBody = await firstRes.json(); const assistant = firstBody.choices[0].message; const call = assistant.tool_calls[0];
152
+ assert.match(call.id, /^call_pmn_/); assert.notEqual(call.id, 'call_raw');
153
+ const continuation = { model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'tool-fixture' }, assistant, { role: 'tool', tool_call_id: call.id, content: 'lookup-result' }], tools: [tool], stream: false };
154
+ const secondRes = await request(continuation); assert.equal(secondRes.status, 200); assert.equal((await secondRes.json()).choices[0].message.content, 'native final');
155
+ assert.equal(captures.length, before + 2); const toolCapture = captures.at(-1); assert.equal(toolCapture.body.input.chatType, 'TOOL_RESPONSE'); assert.equal(toolCapture.body.input.toolCallGroupId, 'group_raw'); assert.equal(toolCapture.body.input.toolResponses[0].toolCallId, 'call_raw');
156
+ observations.tool = { first: firstRes.status, continuation: secondRes.status, syntheticCallId: call.id, rawRequests: 2 };
157
+
158
+ before = captures.length;
159
+ const missing = await fetch(`${nextBase}/v1/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'x' }] }) });
160
+ const invalid = await fetch(`${nextBase}/v1/chat/completions`, { method: 'POST', headers: { 'content-type': 'application/json', authorization: 'Bearer wrong' }, body: JSON.stringify({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'x' }] }) });
161
+ const badModel = await request({ model: 'unconfigured-prefix/nope', messages: [{ role: 'user', content: 'x' }], stream: false });
162
+ assert.equal(missing.status, 401); assert.equal(invalid.status, 401); assert.notEqual(badModel.status, 200); assert.equal(captures.length, before);
163
+ observations.failures = { missingAuth: missing.status, invalidAuth: invalid.status, badModel: badModel.status, rawRequests: 0 };
164
+
165
+ before = captures.length; const controller = new AbortController();
166
+ const cancelResponse = await request({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'cancel-fixture' }], stream: true }, { signal: controller.signal });
167
+ const pendingBody = cancelResponse.text();
168
+ setTimeout(() => controller.abort(new DOMException('full ingress cancellation', 'AbortError')), 500);
169
+ await assert.rejects(pendingBody, e => e?.name === 'AbortError');
170
+ await new Promise(r => setTimeout(r, 800));
171
+ assert.equal(captures.length, before + 1);
172
+ observations.cancel = { clientAbort: true, rawRequestObserved: true, rawDisconnectObserved: cancelObserved };
173
+
174
+ before = captures.length;
175
+ const replayRes = await request(continuation); const replayBody = await replayRes.json();
176
+ if (expectReplayFix) {
177
+ assert.equal(replayRes.status, 409); assert.equal(replayBody?.error?.code, 'tool_call_already_consumed'); assert.equal(captures.length, before);
178
+ const replayAgainRes = await request(continuation); const replayAgainBody = await replayAgainRes.json();
179
+ assert.equal(replayAgainRes.status, 409); assert.equal(replayAgainBody?.error?.code, 'tool_call_already_consumed'); assert.equal(captures.length, before);
180
+ const db = new DatabaseSync(path.join(dataDir, 'db/data.sqlite'), { readOnly: true });
181
+ const row = db.prepare('SELECT data FROM providerConnections WHERE provider = ?').get(providerId); db.close();
182
+ const connectionState = JSON.parse(row.data);
183
+ assert.ok(connectionState.testStatus == null || connectionState.testStatus === 'active'); assert.equal(connectionState.lastError ?? null, null); assert.equal(connectionState.errorCode ?? null, null); assert.ok(connectionState.backoffLevel == null || connectionState.backoffLevel === 0);
184
+ assert.equal(Object.keys(connectionState).some(key => key.startsWith('modelLock_') && connectionState[key]), false);
185
+ const freshBefore = captures.length;
186
+ const freshRes = await request({ model: `${prefix}/${model}`, messages: [{ role: 'user', content: 'fresh-after-replay' }], stream: false });
187
+ assert.equal(freshRes.status, 200); assert.equal(captures.length, freshBefore + 1);
188
+ observations.replayFix = { facadeStatus: 409, replayStatuses: [replayRes.status, replayAgainRes.status], embeddedCodes: [replayBody.error.code, replayAgainBody.error.code], extraRawRequests: 0, connectionState, freshStatus: freshRes.status, freshRawRequests: 1 };
189
+ } else {
190
+ assert.equal(replayRes.status, 503); assert.equal(captures.length, before);
191
+ assert.match(replayBody?.error?.message || JSON.stringify(replayBody), /tool_call_already_consumed/);
192
+ observations.replayGap = { facadeStatus: 409, nextIngressStatus: replayRes.status, embeddedCode: 'tool_call_already_consumed', extraRawRequests: 0, classification: 'pinned 9router account-fallback converts deterministic facade 409 into 503 and locks the selected account' };
193
+ }
194
+
195
+ const result = { ok: true, routerCommit: '17c4cc76877bd1755030a8414f8d0083f48dcccf', routerRoot, expectReplayFix, dataDir, nextBase, facadeBase, rawBase, startedPids, captures: captures.length, toolSequence, observations, seedStdout: seed.stdout.trim() };
196
+ await fs.writeFile(resultPath, JSON.stringify(result, null, 2));
197
+ console.log(JSON.stringify(result, null, 2));
198
+ } finally {
199
+ if (nextChild && nextChild.exitCode === null) {
200
+ if (process.platform === 'win32') spawnSync('taskkill', ['/PID', String(nextChild.pid), '/T', '/F'], { encoding: 'utf8' });
201
+ else nextChild.kill('SIGTERM');
202
+ await Promise.race([once(nextChild, 'exit'), new Promise(r => setTimeout(r, 5000))]).catch(() => {});
203
+ }
204
+ await out.close().catch(() => {});
205
+ await new Promise(r => facade.close(r));
206
+ await new Promise(r => rawMock.close(r));
207
+ }
@@ -0,0 +1,36 @@
1
+ import path from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+
4
+ const root = process.env.NINEROUTER_ROOT;
5
+ if (!root || !process.env.DATA_DIR) throw new Error('NINEROUTER_ROOT and DATA_DIR are required');
6
+ const localDb = await import(pathToFileURL(path.join(root, 'src/lib/localDb.js')).href);
7
+
8
+ const nodeId = process.env.FULL_INGRESS_PROVIDER_ID || 'openai-compatible-chat-full-ingress';
9
+ const prefix = process.env.FULL_INGRESS_PREFIX || 'pmnfull';
10
+ const facadeBase = process.env.FULL_INGRESS_FACADE_BASE;
11
+ const providerApiKey = process.env.FULL_INGRESS_PROVIDER_API_KEY;
12
+ const clientApiKey = process.env.FULL_INGRESS_CLIENT_API_KEY;
13
+ if (!facadeBase || !providerApiKey || !clientApiKey) throw new Error('Missing full ingress seed variables');
14
+
15
+ await localDb.updateSettings({
16
+ requireApiKey: true, requireLogin: false, enableObservability: false,
17
+ outboundProxyEnabled: false, rtkEnabled: false, headroomEnabled: false,
18
+ cavemanEnabled: false, ponytailEnabled: false, pxpipeEnabled: false,
19
+ capacityAdapter: {
20
+ vision: { enabled: false, roundRobin: false, models: [] },
21
+ pdf: { enabled: false, roundRobin: false, models: [] },
22
+ audioInput: { enabled: false, roundRobin: false, models: [] },
23
+ videoInput: { enabled: false, roundRobin: false, models: [] },
24
+ },
25
+ });
26
+ await localDb.createProviderNode({ id: nodeId, type: 'openai-compatible', name: 'Offline Postman Full Ingress', prefix, apiType: 'chat', baseUrl: `${facadeBase}/v1` });
27
+ await localDb.createProviderConnection({
28
+ provider: nodeId, authType: 'apikey', name: 'Offline Postman Full Ingress', apiKey: providerApiKey,
29
+ isActive: true, testStatus: 'active', priority: 1,
30
+ providerSpecificData: { baseUrl: `${facadeBase}/v1`, apiType: 'chat', connectionProxyEnabled: false },
31
+ });
32
+ const db = await import(pathToFileURL(path.join(root, 'src/lib/db/driver.js')).href);
33
+ const adapter = await db.getAdapter();
34
+ adapter.run('INSERT INTO apiKeys(id, key, name, machineId, isActive, createdAt) VALUES(?, ?, ?, ?, ?, ?)',
35
+ ['full-ingress-client-key', clientApiKey, 'Full ingress client', 'offline-fixture', 1, new Date().toISOString()]);
36
+ console.log(JSON.stringify({ seeded: true, dataDir: process.env.DATA_DIR, nodeId, prefix, facadeBase }));
@@ -0,0 +1,9 @@
1
+ import assert from 'node:assert/strict';
2
+ import { once } from 'node:events';
3
+ import { readFile } from 'node:fs/promises';
4
+ export const config=Object.freeze({host:'127.0.0.1',port:0,apiKey:'ingress-secret',upstreamToken:'upstream-secret',workspaceId:'ws_fixture',upstreamOrigin:'https://gateway.example.test',chatPath:'/chat',appVersion:'test',models:['fixture-model'],timeoutMs:1000,maxBodyBytes:4096,maxEventBytes:4096,maxOutputBytes:4096,maxToolArgumentBytes:1024,maxTools:8,maxConcurrent:2,exposeHealthDetails:false});
5
+ export function sseResponse(events,{status=200,contentType='text/event-stream',done=true,chunks=null}={}){const text=events.map(({event='message',data})=>`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`).join('')+(done?'data: [DONE]\n\n':'');const encoder=new TextEncoder();let parts=chunks?chunks.map(([a,b])=>encoder.encode(text.slice(a,b))):[encoder.encode(text)];return new Response(new ReadableStream({pull(c){if(parts.length)c.enqueue(parts.shift());else c.close();}}),{status,headers:{'content-type':contentType}});}
6
+ export async function listen(server){server.listen(0,'127.0.0.1');await once(server,'listening');return `http://127.0.0.1:${server.address().port}`;}
7
+ export async function request(base,path,{method='GET',token='ingress-secret',body,signal}={}){return fetch(`${base}${path}`,{method,signal,headers:{authorization:`Bearer ${token}`,...(body?{'content-type':'application/json'}:{})},body:body?JSON.stringify(body):undefined});}
8
+ export async function fixture(name){return JSON.parse(await readFile(new URL(`./fixtures/${name}.json`,import.meta.url),'utf8'));}
9
+ export async function json(response,status=200){assert.equal(response.status,status);return response.json();}
@@ -0,0 +1,49 @@
1
+ import assert from 'node:assert/strict';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import test from 'node:test';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { verifyIssueClosure } from '../scripts/verify-issue-closure.mjs';
7
+
8
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
+ const load = (name) => JSON.parse(fs.readFileSync(path.join(ROOT, name), 'utf8'));
10
+ const clone = (value) => structuredClone(value);
11
+ const baseline = () => ({ claude: load('CLAUDE-LIVE-TOOLS-EVIDENCE.json'), trace: load('FULL-TRACE-EVIDENCE.json') });
12
+
13
+ function expectReject(mutator, code) {
14
+ const evidence = baseline();
15
+ mutator(evidence);
16
+ assert.throws(() => verifyIssueClosure(evidence), (error) => error instanceof Error && error.message === code);
17
+ }
18
+
19
+ test('closure harness accepts the committed Claude and PMN evidence', () => {
20
+ const result = verifyIssueClosure(baseline());
21
+ assert.equal(result.verdict, 'PASS');
22
+ assert.equal(result.controls.failover, false);
23
+ assert.equal(result.controls.cpxActive, true);
24
+ });
25
+
26
+ test('fail-closed: retry/failover and stale-probe branches reject', () => {
27
+ expectReject(({ claude }) => { claude.policy.retry = true; }, 'claude_retry_must_be_false');
28
+ expectReject(({ claude }) => { claude.policy.failover = true; }, 'claude_failover_must_be_false');
29
+ expectReject(({ claude }) => { claude.startedWithPriorCurrentProbeAttempts = 1; }, 'claude_probe_not_fresh');
30
+ });
31
+
32
+ test('fail-closed: Claude malformed tool arguments and incomplete continuation reject', () => {
33
+ expectReject(({ claude }) => { claude.branches[1].initial.argumentsValid = false; }, 'claude_arguments_invalid');
34
+ expectReject(({ claude }) => { claude.branches[1].initial.argumentsDiagnostic.keyCount = 1; }, 'claude_arguments_not_empty');
35
+ expectReject(({ claude }) => { claude.branches[1].continuation.facadeStatus = 502; }, 'claude_continuation_not_200');
36
+ expectReject(({ claude }) => { claude.branches[1].finalMatchesMarker = false; }, 'claude_final_marker_mismatch');
37
+ });
38
+
39
+ test('fail-closed: PMN attribution cannot silently resolve to CPX or an inactive connection', () => {
40
+ expectReject(({ trace }) => { trace.rows.find((row) => row.connectionId.includes('6706879a')).connectionId = '27b72dc7-4a15-4b8a-815b-71f7839e5f2b'; }, 'pmn_row_count_invalid');
41
+ expectReject(({ trace }) => { trace.connections['6706879a-9897-4b2a-9142-728ca64276d4'].isActive = 0; }, 'pmn_connection_inactive');
42
+ expectReject(({ trace }) => { trace.connections['27b72dc7-4a15-4b8a-815b-71f7839e5f2b'].isActive = 0; }, 'cpx_not_active_during_trace');
43
+ });
44
+
45
+ test('fail-closed: ambiguous or mismatched PMN marker rows reject', () => {
46
+ expectReject(({ trace }) => { trace.rows.find((row) => row.connectionId.includes('6706879a')).completionTokens = 5; }, 'pmn_marker_token_count_invalid');
47
+ expectReject(({ trace }) => { trace.rows.push(clone(trace.rows.find((row) => row.connectionId.includes('6706879a')))); }, 'pmn_row_count_invalid');
48
+ expectReject(({ trace }) => { trace.rows = trace.rows.filter((row) => !row.connectionId.includes('27b72dc7')); }, 'cpx_control_row_missing');
49
+ });
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { resolveUpstreamModel, isThinkingModel } from '../src/web-session-request.mjs';
4
+
5
+ test('resolveUpstreamModel maps all 4 models and their thinking variants', () => {
6
+ assert.equal(resolveUpstreamModel('gpt-5.6-sol'), 'GPT_56_SOL');
7
+ assert.equal(resolveUpstreamModel('gpt-5.6-sol-thinking'), 'GPT_56_SOL');
8
+ assert.equal(resolveUpstreamModel('gpt-5.6-terra'), 'GPT_56_TERRA');
9
+ assert.equal(resolveUpstreamModel('gpt-5.6-terra-thinking'), 'GPT_56_TERRA');
10
+ assert.equal(resolveUpstreamModel('claude-opus-4-8'), 'CLAUDE_OPUS_48_BEDROCK');
11
+ assert.equal(resolveUpstreamModel('claude-opus-4-7'), 'CLAUDE_OPUS_47_BEDROCK');
12
+ assert.equal(resolveUpstreamModel('unknown', 'FALLBACK'), 'FALLBACK');
13
+ });
14
+
15
+ test('isThinkingModel identifies -thinking suffix', () => {
16
+ assert.equal(isThinkingModel('gpt-5.6-sol-thinking'), true);
17
+ assert.equal(isThinkingModel('gpt-5.6-sol'), false);
18
+ assert.equal(isThinkingModel('claude-opus-4-8-thinking'), true);
19
+ assert.equal(resolveUpstreamModel('claude-opus-4-8'), 'CLAUDE_OPUS_48_BEDROCK');
20
+ });
@@ -0,0 +1,16 @@
1
+ import test from 'node:test';import assert from 'node:assert/strict';
2
+ import { parseSSE } from '../src/sse.mjs';import { createRawEventAdapter } from '../src/postman-events.mjs';import { createResponseState } from '../src/openai-response.mjs';import { canonicalEvents } from '../src/transport.mjs';import { config,fixture,sseResponse } from './helpers.mjs';
3
+ async function* fragments(value){for(const x of value)yield new TextEncoder().encode(x);}
4
+ const adapter=(limit=1024)=>createRawEventAdapter({toolNameMap:new Map([['openai__lookup','lookup']]),maxToolArgumentBytes:limit,maxOutputBytes:limit});
5
+ const toolRequest={model:'fixture-model',toolNameMap:new Map([['openai__lookup','lookup']]),toolSchemaMap:new Map([['lookup',{type:'object',properties:{q:{type:'string'}},required:['q'],additionalProperties:false}]])};
6
+ test('SSE parser handles arbitrary chunk splits and exposes [DONE] once',async()=>{const text='event: message\ndata: {"x":1}\n\ndata: [DONE]\n\ndata: {"eventType":"textChunk","data":{"textContent":"late"}}\n\n';const got=[];for await(const f of parseSSE(fragments([text.slice(0,7),text.slice(7,21),text.slice(21)]),1024))got.push(f);assert.equal(got.length,3);assert.equal(got[0].event,'message');assert.equal(got[1].data,'[DONE]');});
7
+ test('source-derived text frames assemble at the [DONE] boundary',async()=>{const fx=await fixture('text');const a=adapter();const state=createResponseState({model:'fixture-model'});for(const f of fx.frames)for(const e of a.adapt({event:f.event,data:JSON.stringify(f.data)}))state.apply(e);for(const e of a.finish())state.apply(e);const r=state.result();assert.equal(r.choices[0].message.content,'hello');assert.equal(r.choices[0].finish_reason,'stop');assert.equal(r.usage.total_tokens,4);});
8
+ test('fragmented tool call preserves id index name args and validates only at completion',async()=>{const fx=await fixture('tool');const a=adapter();const state=createResponseState(toolRequest);for(const f of fx.frames)for(const e of a.adapt({event:f.event,data:JSON.stringify(f.data)}))state.apply(e);for(const e of a.finish())state.apply(e);const r=state.result();assert.equal(r.choices[0].finish_reason,'tool_calls');assert.deepEqual(r.choices[0].message.tool_calls[0],{id:'call_fixture',type:'function',function:{name:'lookup',arguments:'{"q":"x"}'}});});
9
+ test('empty object arguments are accepted when schema permits them',()=>{const a=adapter();const state=createResponseState({...toolRequest,toolSchemaMap:new Map([['lookup',{type:'object',properties:{},additionalProperties:false}]])});for(const e of a.adapt({event:'message',data:JSON.stringify({eventType:'toolCallChunk',data:{id:'c',index:0,name:'openai__lookup',arguments:''}})}))state.apply(e);for(const e of a.finish())state.apply(e);assert.equal(state.result().choices[0].message.tool_calls[0].function.arguments,'');});
10
+ test('truncated and malformed completed tool arguments fail closed',()=>{for(const value of ['{"q":','not-json']){const a=adapter();const state=createResponseState(toolRequest);for(const e of a.adapt({event:'message',data:JSON.stringify({eventType:'toolCallChunk',data:{id:'c',index:0,name:'openai__lookup',arguments:value}})}))state.apply(e);assert.throws(()=>{for(const e of a.finish())state.apply(e);},/invalid JSON arguments/);}});
11
+ test('unknown shape malformed JSON oversize and incomplete boundaries fail closed',async()=>{const a=adapter(3);assert.throws(()=>a.adapt({event:'message',data:'{"eventType":"finish","data":{"finishReason":"stop"}}'}),/Unsupported/);assert.throws(()=>a.adapt({event:'message',data:'not-json'}),/malformed/);assert.throws(()=>a.adapt({event:'message',data:'{"eventType":"textChunk","data":{"textContent":"long"}}'}),/exceeds/);await assert.rejects(async()=>{for await(const _ of parseSSE(fragments(['data: '+ 'x'.repeat(100)]),10)){}},/exceeds/);});
12
+ test('canonical transport ignores text/tool/double markers after exact [DONE] and rejects EOF before it',async()=>{const request={model:'fixture-model',messages:[{role:'user',content:'x'}],tools:[],toolNameMap:new Map(),toolSchemaMap:new Map(),toolResults:[],toolChoice:'none'};const events=[];const raw='event: message\ndata: {"eventType":"textChunk","data":{"textContent":"literal [DONE] is content"}}\n\ndata: [DONE]\n\nevent: message\ndata: {"eventType":"toolCallChunk","data":{"id":"late","index":0,"name":"unknown","arguments":"{}"}}\n\ndata: [DONE]\n\n';for await(const e of canonicalEvents(request,config,{fetchImpl:async()=>new Response(raw,{headers:{'content-type':'text/event-stream'}}),signal:new AbortController().signal}))events.push(e);assert.deepEqual(events.filter(e=>e.type==='text_delta').map(e=>e.text),['literal [DONE] is content']);assert.equal(events.filter(e=>e.type==='finish').length,1);assert.equal(events.some(e=>e.type==='tool_call_start'),false);await assert.rejects(async()=>{for await(const _ of canonicalEvents(request,config,{fetchImpl:async()=>sseResponse([],{done:false}),signal:new AbortController().signal})){}},/before \[DONE\]/);});
13
+ const frame=data=>({event:'message',data:JSON.stringify(data)});
14
+ test('source-backed failure envelopes are classified and sanitized without finish',()=>{for(const [value,code,status] of [[{result:'failure',message:'Bearer secret-value arbitrary body'},'upstream_gateway_failure',502],[{eventType:'error',errorType:'TOOL_VALIDATION_ERROR',message:'secret-value'},'upstream_tool_validation_error',400],[{eventType:'error',data:{errorType:'LLM_STREAM_ERROR',message:'secret-value'}},'upstream_stream_error',502]]){const a=adapter();assert.throws(()=>a.adapt(frame(value)),error=>error.code===code&&error.status===status&&!error.message.includes('secret-value'));assert.deepEqual(a.finish(),[{type:'finish',reason:'stop'}]);}});
15
+ test('credit states preserve known distinctions and unknown states fail closed',()=>{const available=adapter().adapt(frame({eventType:'usage',data:{usageState:'AVAILABLE',inputTokens:2,outputTokens:3}}));assert.equal(available[0].usage.total_tokens,5);assert.throws(()=>adapter().adapt(frame({eventType:'usage',data:{usageState:'BLOCKED',blockedUntil:'2026-09-06T19:17:37.905Z'}})),error=>error.code==='upstream_credit_blocked'&&error.status===400&&!error.message.includes('2026-'));assert.throws(()=>adapter().adapt(frame({eventType:'usage',data:{usageState:'MYSTERY'}})),error=>error.code==='upstream_unknown_credit_state'&&error.status===502);});
16
+ test('transport failures before text after text and during tools never yield finish',async()=>{const request={model:'fixture-model',messages:[{role:'user',content:'x'}],tools:[],toolNameMap:new Map([['openai__lookup','lookup']]),toolSchemaMap:new Map(),toolResults:[],toolChoice:'none'};for(const frames of [[{result:'failure'}],[{eventType:'textChunk',data:{textContent:'partial'}},{eventType:'error',data:{errorType:'LLM_STREAM_ERROR'}}],[{eventType:'conversation',data:{id:'conv'}},{eventType:'toolCallChunk',data:{id:'u',index:0,name:'openai__lookup',toolCallGroupId:'g'}},{eventType:'error',errorType:'TOOL_VALIDATION_ERROR'}]]){const events=[];await assert.rejects(async()=>{for await(const e of canonicalEvents(request,config,{fetchImpl:async()=>sseResponse(frames),signal:new AbortController().signal}))events.push(e);});assert.equal(events.some(event=>event.type==='finish'),false);}});
@@ -0,0 +1,10 @@
1
+ import test from 'node:test';import assert from 'node:assert/strict';
2
+ import { normalizeOpenAIRequest } from '../src/openai.mjs';import { buildPostmanRequest } from '../src/postman-request.mjs';import { config } from './helpers.mjs';
3
+ const tool={type:'function',function:{name:'lookup',description:'Lookup',parameters:{type:'object',properties:{q:{type:'string'}},required:['q'],additionalProperties:false}}};
4
+ test('normalizes tools with original-name map and full history raw request',()=>{const r=normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'system',content:'s'},{role:'user',content:'u'}],tools:[tool]},config);assert.equal(r.toolNameMap.get('openai__lookup'),'lookup');const raw=buildPostmanRequest(r,config);assert.equal(raw.url.href,'https://gateway.example.test/chat');assert.equal(raw.init.headers['x-access-token'],'upstream-secret');const body=JSON.parse(raw.init.body);assert.equal(body.input.chatType,'USER_QUERY');assert.equal(JSON.parse(body.input.query).format,'openai-full-history-v1');assert.equal(body.clientTools.thirdParty['openai-client'].tools[0].name,'openai__lookup');});
5
+ test('tool result continuation fails closed without trusted upstream correlation',()=>{const r=normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'user',content:'u'},{role:'assistant',content:null,tool_calls:[{id:'c1',type:'function',function:{name:'lookup',arguments:'{"q":"x"}'}}]},{role:'tool',tool_call_id:'c1',content:'ok'}],tools:[tool],tool_choice:{type:'function',function:{name:'lookup'}}},config);assert.throws(()=>buildPostmanRequest(r,config),error=>error.code==='unknown_tool_call'&&error.status===400);});
6
+ test('orphan result invalid args and unsupported surface reject',()=>{assert.throws(()=>normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'tool',tool_call_id:'x',content:'a'}]},config),/unknown tool call/);assert.throws(()=>normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'user',content:'u'}],n:2},config),/Only n=1/);});
7
+
8
+ test('operator catalog metadata is conditional cloned and request body cannot override it',()=>{const configured={...config,platform:'DESKTOP_CUSTOM',nativeToolsHash:'catalog:authoritative-1',excludedTools:Object.freeze(['readFile','webSearch'])};const source={model:'fixture-model',messages:[{role:'user',content:'u'}],tools:[tool],platform:'USER_OVERRIDE',nativeToolsHash:'USER_HASH',excludedTools:['USER_TOOL']};const r=normalizeOpenAIRequest(source,configured);const before=structuredClone(source);const raw=buildPostmanRequest(r,configured);const body=JSON.parse(raw.init.body);assert.equal(body.platform,'DESKTOP_CUSTOM');assert.equal(body.clientTools.nativeToolsHash,'catalog:authoritative-1');assert.deepEqual(body.clientTools.excludedTools,['readFile','webSearch']);assert.ok(body.clientTools.thirdParty['openai-client']);assert.deepEqual(source,before);body.clientTools.excludedTools.push('mutated');assert.deepEqual(configured.excludedTools,['readFile','webSearch']);});
9
+ test('unset catalog metadata omits hash and preserves OpenAI namespace query and platform defaults',()=>{const r=normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'user',content:'u'}],tools:[tool]},config);const body=JSON.parse(buildPostmanRequest(r,config).init.body);assert.equal(body.platform,'OPENAI_COMPATIBLE_ADAPTER');assert.equal('nativeToolsHash' in body.clientTools,false);assert.deepEqual(body.clientTools.excludedTools,[]);assert.deepEqual(Object.keys(body.clientTools.thirdParty),['openai-client']);assert.equal(JSON.parse(body.input.query).format,'openai-full-history-v1');});
10
+ test('initial and continuation envelopes use identical configured catalog metadata',()=>{const configured={...config,platform:'DESKTOP_CUSTOM',nativeToolsHash:'catalog:authoritative-1',excludedTools:['readFile']};const r=normalizeOpenAIRequest({model:'fixture-model',messages:[{role:'user',content:'u'}],tools:[tool]},configured);const group={conversationId:'conv',upstreamGroupId:'grp',calls:[{upstreamToolCallId:'up',originalName:'lookup'}]};const initial=JSON.parse(buildPostmanRequest(r,configured).init.body);const next=JSON.parse(buildPostmanRequest(r,configured,{group,orderedResults:[{content:'ok'}]}).init.body);assert.equal(next.input.chatType,'TOOL_RESPONSE');assert.equal(next.platform,initial.platform);assert.deepEqual(next.clientTools,initial.clientTools);});
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { SessionMappingStore } from '../src/session-store.mjs';
4
+
5
+ test('SessionMappingStore stores, retrieves, resets, and expires conversations', () => {
6
+ const store = new SessionMappingStore({ ttlMs: 100 });
7
+ store.setConversationId('ss-1', 'conv-123');
8
+ assert.equal(store.getConversationId('ss-1'), 'conv-123');
9
+ assert.equal(store.getConversationId('ss-2'), null);
10
+
11
+ // Reset
12
+ store.reset('ss-1');
13
+ assert.equal(store.getConversationId('ss-1'), null);
14
+
15
+ // Expiry
16
+ store.setConversationId('ss-3', 'conv-456');
17
+ assert.equal(store.getConversationId('ss-3'), 'conv-456');
18
+ store.sweep(Date.now() + 200);
19
+ assert.equal(store.getConversationId('ss-3'), null);
20
+ });
@@ -0,0 +1,124 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { loadConfig, webSessionUrl } from '../src/config.mjs';
4
+ import { normalizeOpenAIRequest } from '../src/openai.mjs';
5
+ import { buildPostmanRequest } from '../src/postman-request.mjs';
6
+ import { buildWebSessionTextRequest } from '../src/web-session-request.mjs';
7
+ import { createProviderServer } from '../src/server.mjs';
8
+ import { listen, request } from './helpers.mjs';
9
+
10
+ const base = {
11
+ PROVIDER_API_KEY: 'facade-test-dummy',
12
+ PROVIDER_MODELS: 'public-model',
13
+ POSTMAN_TRANSPORT_STRATEGY: 'web_session',
14
+ POSTMAN_SESSION_COOKIE: 'session-test-dummy',
15
+ POSTMAN_WORKSPACE_SUBDOMAIN: 'team-7',
16
+ POSTMAN_WORKSPACE_ID: 'workspace-test-dummy',
17
+ POSTMAN_WEB_SESSION_SELECTED_MODEL: 'UPSTREAM_PIN_TEST',
18
+ MAX_BODY_BYTES: '4096',
19
+ };
20
+ const config = () => loadConfig(base);
21
+ const normalized = (body = {}) => normalizeOpenAIRequest({ model: 'public-model', messages: [{ role: 'system', content: 'be exact' }, { role: 'user', content: 'hello' }], ...body }, config());
22
+
23
+ test('web_session config derives one exact HTTPS Postman chat URL', () => {
24
+ const c = config();
25
+ const url = webSessionUrl(c);
26
+ assert.equal(url.href, 'https://team-7.postman.co/_gw/chat');
27
+ assert.equal(url.origin, 'https://team-7.postman.co');
28
+ assert.equal(url.hostname, 'team-7.postman.co');
29
+ });
30
+
31
+ test('strategy is closed, web-session fields require explicit strategy, and credentials are exclusive', () => {
32
+ assert.throws(() => loadConfig({ ...base, POSTMAN_TRANSPORT_STRATEGY: 'other' }), /must be access_token or web_session/);
33
+ const { POSTMAN_TRANSPORT_STRATEGY, ...implicit } = base;
34
+ assert.throws(() => loadConfig(implicit), /STRATEGY is required/);
35
+ assert.throws(() => loadConfig({ ...base, POSTMAN_ACCESS_TOKEN: 'token-test-dummy' }), /web_session forbids/);
36
+ assert.throws(() => loadConfig({ ...base, POSTMAN_ORIGIN: 'https:\/\/gateway.example.test' }), /web_session forbids/);
37
+ assert.throws(() => loadConfig({ ...base, POSTMAN_CHAT_PATH: '/chat' }), /web_session forbids/);
38
+ const legacy = loadConfig({ PROVIDER_API_KEY: 'a', PROVIDER_MODELS: 'm', POSTMAN_ACCESS_TOKEN: 'b', POSTMAN_WORKSPACE_ID: 'w', POSTMAN_ORIGIN: 'https://gateway.example.test' });
39
+ assert.equal(legacy.transportStrategy, 'access_token');
40
+ assert.equal(legacy.transportStrategyExplicit, false);
41
+ });
42
+
43
+ test('cookie and subdomain reject injection, whole-cookie syntax, lookalikes, and bounds', () => {
44
+ for (const value of ['x\rY: z', 'x\ny', 'x y', 'x;y', 'x,y', 'x\\y', '\0x', 'é', 'x'.repeat(4097)]) assert.throws(() => loadConfig({ ...base, POSTMAN_SESSION_COOKIE: value }), /POSTMAN_SESSION_COOKIE/);
45
+ assert.equal(loadConfig({ ...base, POSTMAN_SESSION_COOKIE: 'x'.repeat(4096) }).webSessionCookie.length, 4096);
46
+ for (const value of ['foo.bar', 'foo.postman.co', 'postman.co', 'foo%2ebar', 'Foo', 'foo_bar', '-foo', 'foo-', 'foo/', 'foo/bar', 'foo:443', 'foo:80', 'foo@bar', 'xn--fsq', 'é', 'x'.repeat(64)]) assert.throws(() => loadConfig({ ...base, POSTMAN_WORKSPACE_SUBDOMAIN: value }), /POSTMAN_WORKSPACE_SUBDOMAIN/);
47
+ });
48
+
49
+ test('pure builder returns exact allowlisted URL/init and keeps facade/token headers separate', () => {
50
+ const raw = buildWebSessionTextRequest(normalized(), config());
51
+ assert.equal(raw.url.href, 'https://team-7.postman.co/_gw/chat');
52
+ assert.equal(raw.init.method, 'POST');
53
+ assert.equal(raw.init.redirect, 'error');
54
+ assert.deepEqual(Object.keys(raw.init.headers).sort(), ['accept', 'content-type', 'cookie', 'origin', 'referer', 'user-agent', 'x-pstmn-req-service'].sort());
55
+ assert.equal(raw.init.headers.cookie, 'postman.sid=session-test-dummy');
56
+ assert.equal(raw.init.headers.origin, 'https://team-7.postman.co');
57
+ assert.equal(raw.init.headers.referer, 'https://team-7.postman.co/');
58
+ assert.equal(raw.init.headers.authorization, undefined);
59
+ assert.equal(raw.init.headers['x-access-token'], undefined);
60
+ assert.equal(JSON.stringify(raw).includes('facade-test-dummy'), false);
61
+ });
62
+
63
+ test('body is deterministic, config-owned, fresh text-only, and does not mutate caller input', () => {
64
+ const source = { model: 'public-model', messages: [{ role: 'user', content: 'hello' }], platform: 'ATTACK', workspaceId: 'ATTACK', conversationId: 'ATTACK', cookie: 'ATTACK', origin: 'ATTACK', selectedModel: 'ATTACK', devModeOptions: { selectedModel: 'ATTACK' } };
65
+ const before = structuredClone(source);
66
+ const c = config();
67
+ const request = normalizeOpenAIRequest(source, c);
68
+ const a = buildWebSessionTextRequest(request, c);
69
+ const b = buildWebSessionTextRequest(request, c);
70
+ assert.equal(a.init.body, b.init.body);
71
+ assert.deepEqual(source, before);
72
+ const body = JSON.parse(a.init.body);
73
+ assert.equal(body.platform, 'WEB');
74
+ assert.equal(body.mandatoryContext.workspaceId, 'workspace-test-dummy');
75
+ assert.equal(body.input.conversationId, null);
76
+ assert.equal(body.devModeOptions.selectedModel, 'UPSTREAM_PIN_TEST');
77
+ assert.equal(body.devModeOptions.isParallelToolCallingSupported, false);
78
+ assert.deepEqual(body.clientTools, { native: [] });
79
+ assert.equal(a.init.body.includes('ATTACK'), false);
80
+ const query = JSON.parse(body.input.query);
81
+ assert.equal(query.format, 'openai-full-history-v1');
82
+ assert.deepEqual(query.messages, [{ role: 'user', content: 'hello' }]);
83
+ });
84
+
85
+ test('web-session builder advertises tools, preserves assistant tool history, and rejects uncorrelated results and named tool choice', () => {
86
+ const c = config();
87
+ const tool = { type: 'function', function: { name: 'lookup', parameters: { type: 'object', properties: {} } } };
88
+ const built=buildWebSessionTextRequest(normalizeOpenAIRequest({ model: 'public-model', messages: [{ role: 'user', content: 'x' }], tools: [tool] }, c), c);
89
+ assert.deepEqual(JSON.parse(built.init.body).clientTools.thirdParty['proxy-tools'].tools.map(value=>value.name), ['openai__lookup']);
90
+ const fake = { ...normalized(), messages: [{ role: 'assistant', content: '', tool_calls: [{ id: 'x' }] }], toolChoice: 'none' };
91
+ const historyBody=JSON.parse(buildWebSessionTextRequest(fake, c).init.body);
92
+ const historyQuery=JSON.parse(historyBody.input.query);
93
+ assert.equal(historyQuery.messages[0].role,'assistant');
94
+ assert.equal(historyQuery.messages[0].tool_calls[0].id,'x');
95
+ assert.throws(() => buildWebSessionTextRequest({ ...normalized(), toolResults: [{ toolCallId: 'x', content: 'y' }] }, c), error=>error.code==='unknown_tool_call');
96
+ assert.throws(() => buildWebSessionTextRequest({ ...normalized(), toolChoice: {type:'function',name:'lookup'} }, c), /named tool_choice/);
97
+ });
98
+
99
+ test('serialized body obeys MAX_BODY_BYTES and optional app version is validated/allowlisted', () => {
100
+ const c = loadConfig({ ...base, MAX_BODY_BYTES: '1024', POSTMAN_APP_VERSION: 'app-test-1' });
101
+ const raw = buildWebSessionTextRequest(normalizeOpenAIRequest({ model: 'public-model', messages: [{ role: 'user', content: 'ok' }] }, c), c);
102
+ assert.equal(raw.init.headers['x-app-version'], 'app-test-1');
103
+ assert.throws(() => buildWebSessionTextRequest(normalizeOpenAIRequest({ model: 'public-model', messages: [{ role: 'user', content: 'x'.repeat(2000) }] }, c), c), error => error.code === 'request_too_large' && error.status === 413);
104
+ assert.throws(() => loadConfig({ ...base, POSTMAN_APP_VERSION: 'bad\r\nheader' }), /POSTMAN_APP_VERSION/);
105
+ });
106
+
107
+ test('existing live request dispatcher rejects web_session before fetch/event parsing', () => {
108
+ assert.throws(() => buildPostmanRequest(normalized(), config()), error => error.code === 'web_session_transport_not_active' && error.status === 400);
109
+ });
110
+
111
+ test('provider server activates web_session only when the explicit strategy is configured', async () => {
112
+ const c = { ...config(), timeoutMs: 1000, maxConcurrent: 1, correlationPendingTtlMs: 1000, correlationTerminalTtlMs: 1000, correlationInflightTtlMs: 1000, correlationMaxGroups: 10, correlationMaxCalls: 10, correlationMaxBytes: 4096, maxEventBytes: 4096, maxOutputBytes: 4096, maxToolArgumentBytes: 1024, maxToolResultBytes: 1024, exposeHealthDetails: false };
113
+ let fetchCalls = 0;
114
+ const server = createProviderServer(c, { fetchImpl: async () => { fetchCalls++; return new Response('data: {"eventType":"textChunk","data":{"textContent":"active"}}\n\ndata: [DONE]\n\n', { status: 200, headers: { 'content-type': 'text/event-stream' } }); }, logger: { error() {} } });
115
+ const baseUrl = await listen(server);
116
+ try {
117
+ const response = await request(baseUrl, '/v1/chat/completions', { method: 'POST', token: 'facade-test-dummy', body: { model: 'public-model', messages: [{ role: 'user', content: 'hello' }] } });
118
+ assert.equal(response.status, 200);
119
+ assert.equal((await response.json()).choices[0].message.content, 'active');
120
+ assert.equal(fetchCalls, 1);
121
+ } finally {
122
+ await new Promise(resolve => server.close(resolve));
123
+ }
124
+ });