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.
- package/.env.example +34 -0
- package/AICOWORKER-NATIVE-TOOLS.md +60 -0
- package/CLAUDE-LIVE-TOOLS-EVIDENCE.json +94 -0
- package/FULL-TRACE-EVIDENCE.json +109 -0
- package/ISSUE-1-REMOTE.json +1 -0
- package/ISSUE-2-POSTREVIEW.json +1 -0
- package/ISSUE-2-REMOTE.json +1 -0
- package/KEY-ROTATION-EVIDENCE.json +9 -0
- package/LICENSE +21 -0
- package/PMN-9ROUTER-FINAL.md +13 -0
- package/RAPID-BASIL-RECONCILIATION.json +10 -0
- package/README.md +232 -0
- package/RELEASE-ARCHIVE.json +15 -0
- package/SECURITY-RECONCILIATION.json +30 -0
- package/SECURITY.md +16 -0
- package/TEST-ISSUES12-FINAL.txt +0 -0
- package/TEST-ISSUES12-HARNESS.txt +0 -0
- package/VERIFICATION-REPORT.md +58 -0
- package/VERIFIER-ISSUES12-FINAL.txt +0 -0
- package/VERIFY-RELEASE-ISSUES12-FINAL.txt +0 -0
- package/VERIFY-RELEASE-ISSUES12-HARNESS.txt +0 -0
- package/bin/aki-pro-max.js +98 -0
- package/docs/ADMIN-GUI-CONTRACT.md +29 -0
- package/docs/ARCHITECTURE.md +109 -0
- package/docs/CAPABILITY-MATRIX.md +44 -0
- package/docs/CORRELATION-DESIGN.md +226 -0
- package/docs/FAIL-CLOSED-ISSUE-HARNESS.md +21 -0
- package/docs/WEB-SESSION-TRANSPORT-DESIGN.md +423 -0
- package/docs/assets/control-plane.jpg +0 -0
- package/gitleaks-report-all.json +1 -0
- package/gitleaks-report-latest.json +1 -0
- package/gitleaks-report.json +1 -0
- package/package.json +33 -0
- package/scripts/eventual-tool-loop.mjs +55 -0
- package/scripts/install-local.ps1 +35 -0
- package/scripts/live-eventual-multitool.mjs +18 -0
- package/scripts/upgrade-admin-v232.mjs +33 -0
- package/scripts/verify-issue-closure.mjs +81 -0
- package/scripts/verify-release.mjs +31 -0
- package/src/admin-auth.mjs +94 -0
- package/src/admin.mjs +133 -0
- package/src/canonical.mjs +23 -0
- package/src/config.mjs +88 -0
- package/src/correlation-store.mjs +120 -0
- package/src/errors.mjs +18 -0
- package/src/index.mjs +4 -0
- package/src/openai-response.mjs +72 -0
- package/src/openai.mjs +104 -0
- package/src/postman-events.mjs +43 -0
- package/src/postman-request.mjs +49 -0
- package/src/schema.mjs +35 -0
- package/src/server.mjs +73 -0
- package/src/session-store.mjs +48 -0
- package/src/sse.mjs +13 -0
- package/src/transport.mjs +90 -0
- package/src/web-session-events.mjs +358 -0
- package/src/web-session-request.mjs +280 -0
- package/test/9router-executor.integration.test.mjs +207 -0
- package/test/admin-auth.test.mjs +47 -0
- package/test/admin.test.mjs +68 -0
- package/test/config.test.mjs +14 -0
- package/test/contract.test.mjs +14 -0
- package/test/correlation-store.test.mjs +19 -0
- package/test/correlation.integration.test.mjs +48 -0
- package/test/eventual-tool-loop.test.mjs +42 -0
- package/test/fixtures/text.json +8 -0
- package/test/fixtures/tool.json +7 -0
- package/test/fixtures/web-session-observed-done.json +12 -0
- package/test/fixtures/web-session-tool-fragments.json +14 -0
- package/test/full-ingress/alias-loader.mjs +22 -0
- package/test/full-ingress/run-full-ingress.mjs +207 -0
- package/test/full-ingress/seed-9router.mjs +36 -0
- package/test/helpers.mjs +9 -0
- package/test/issue-closure-harness.test.mjs +49 -0
- package/test/model-thinking.test.mjs +20 -0
- package/test/protocol.test.mjs +16 -0
- package/test/request.test.mjs +10 -0
- package/test/session-store.test.mjs +20 -0
- package/test/web-session-builder.test.mjs +124 -0
- package/test/web-session-events.test.mjs +241 -0
- package/test/web-session-integration.test.mjs +103 -0
- package/test/web-session-tools.test.mjs +52 -0
- package/version.json +8 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createWebSessionEventParser } from '../src/web-session-events.mjs';
|
|
4
|
+
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const bytes = value => encoder.encode(value);
|
|
7
|
+
const jsonFrame = (value, options = {}) => {
|
|
8
|
+
const payload = JSON.stringify(value);
|
|
9
|
+
const lines = options.splitDataAt === undefined
|
|
10
|
+
? [`data: ${payload}`]
|
|
11
|
+
: [`data: ${payload.slice(0, options.splitDataAt)}`, `data:${payload.slice(options.splitDataAt)}`];
|
|
12
|
+
return `${options.event ? `event: ${options.event}\n` : ''}${lines.join('\n')}\n\n`;
|
|
13
|
+
};
|
|
14
|
+
const make = options => createWebSessionEventParser({ maxEventBytes: 4096, maxOutputBytes: 4096, maxObservationBytes: 256, maxEvents: 32, requestedModel: 'public-model', selectedUpstreamModel: 'SELECTED_PIN', allowedObservedModels: ['OBSERVED_PIN'], ...options });
|
|
15
|
+
const feedPieces = (parser, source, cuts) => {
|
|
16
|
+
const encoded = bytes(source);
|
|
17
|
+
let start = 0;
|
|
18
|
+
for (const end of cuts) { parser.feed(encoded.slice(start, end)); start = end; }
|
|
19
|
+
if (start < encoded.length) parser.feed(encoded.slice(start));
|
|
20
|
+
return parser.end();
|
|
21
|
+
};
|
|
22
|
+
const throwsCode = (fn, code) => assert.throws(fn, error => error?.code === code && !String(error?.upstreamDetail?.message ?? '').includes('secret-test-value'));
|
|
23
|
+
|
|
24
|
+
test('fragmented UTF-8, CRLF boundaries, source-backed progress, observations, and explicit DONE succeed', () => {
|
|
25
|
+
const parser = make();
|
|
26
|
+
const source = [
|
|
27
|
+
jsonFrame({ eventType: 'conversation', data: { id: 'conversation-1' } }).replaceAll('\n', '\r\n'),
|
|
28
|
+
jsonFrame({ eventType: 'planningChunk', data: {} }),
|
|
29
|
+
jsonFrame({ eventType: 'progressUpdate', data: {} }),
|
|
30
|
+
jsonFrame({ eventType: 'todoChunk', data: {} }),
|
|
31
|
+
jsonFrame({ eventType: 'streamingFormat', data: {} }),
|
|
32
|
+
jsonFrame({ eventType: 'thinkingComplete', data: {} }),
|
|
33
|
+
jsonFrame({ eventType: 'usage', data: { usageState: 'AVAILABLE', inputTokens: 2, outputTokens: 3, totalTokens: 5 } }),
|
|
34
|
+
jsonFrame({ eventType: 'textChunk', data: { textContent: 'héllo 🌍', metadata: { model: 'OBSERVED_PIN' } } }),
|
|
35
|
+
'data: [DONE]\n\n',
|
|
36
|
+
].join('');
|
|
37
|
+
const encoded = bytes(source);
|
|
38
|
+
const emoji = bytes('🌍');
|
|
39
|
+
const emojiAt = Buffer.from(encoded).indexOf(Buffer.from(emoji));
|
|
40
|
+
const result = feedPieces(parser, source, [1, 7, 19, emojiAt + 1, emojiAt + 3, encoded.length - 2]);
|
|
41
|
+
assert.equal(result.outcome, 'complete_explicit');
|
|
42
|
+
assert.equal(result.successful, true);
|
|
43
|
+
assert.equal(result.text, 'héllo 🌍');
|
|
44
|
+
assert.equal(result.error, null);
|
|
45
|
+
assert.deepEqual(result.observations.usage, { prompt_tokens: 2, completion_tokens: 3, total_tokens: 5, estimated: false });
|
|
46
|
+
assert.equal(result.observations.conversationId, 'conversation-1');
|
|
47
|
+
assert.equal(result.observations.requestedModel, 'public-model');
|
|
48
|
+
assert.equal(result.observations.selectedUpstreamModel, 'SELECTED_PIN');
|
|
49
|
+
assert.equal(result.observations.observedUpstreamModel, 'OBSERVED_PIN');
|
|
50
|
+
assert.equal(result.observations.modelVerification, 'matched');
|
|
51
|
+
assert.equal(result.observations.parserBufferComplete, true);
|
|
52
|
+
assert.deepEqual(result.observations.eventTypes, ['conversation', 'planningChunk', 'progressUpdate', 'todoChunk', 'streamingFormat', 'thinkingComplete', 'usage', 'textChunk']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('multiple data lines form one JSON payload and an absent model observation stays unobserved', () => {
|
|
56
|
+
const event = { eventType: 'textChunk', data: { textContent: 'split' } };
|
|
57
|
+
const frame = jsonFrame(event, { splitDataAt: JSON.stringify(event).indexOf(',') + 1 });
|
|
58
|
+
const parser = make();
|
|
59
|
+
parser.feed(bytes(frame));
|
|
60
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
61
|
+
const result = parser.end();
|
|
62
|
+
assert.equal(result.text, 'split');
|
|
63
|
+
assert.equal(result.successful, true);
|
|
64
|
+
assert.equal(result.observations.observedUpstreamModel, null);
|
|
65
|
+
assert.equal(result.observations.modelVerification, 'unobserved');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('EOF after terminated text frame is explicitly ambiguous and exposes text without successful final', () => {
|
|
69
|
+
const parser = make();
|
|
70
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'partial answer' } })));
|
|
71
|
+
const result = parser.end();
|
|
72
|
+
assert.equal(result.outcome, 'ambiguous_eof');
|
|
73
|
+
assert.equal(result.successful, false);
|
|
74
|
+
assert.equal(result.text, 'partial answer');
|
|
75
|
+
assert.equal(result.error.code, 'upstream_completion_ambiguous');
|
|
76
|
+
assert.equal(result.observations.parserBufferComplete, true);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('partial final line, partial frame, and partial UTF-8 EOF never silently flush or succeed', () => {
|
|
80
|
+
const partialLine = make();
|
|
81
|
+
partialLine.feed(bytes('data: {"eventType":"textChunk","data":{"textContent":"x"}}'));
|
|
82
|
+
let result = partialLine.end();
|
|
83
|
+
assert.equal(result.outcome, 'incomplete_truncated');
|
|
84
|
+
assert.equal(result.text, '');
|
|
85
|
+
assert.equal(result.error.code, 'upstream_incomplete_stream');
|
|
86
|
+
|
|
87
|
+
const partialFrame = make();
|
|
88
|
+
partialFrame.feed(bytes('data: {"eventType":"textChunk","data":{"textContent":"x"}}\n'));
|
|
89
|
+
result = partialFrame.end();
|
|
90
|
+
assert.equal(result.outcome, 'incomplete_truncated');
|
|
91
|
+
assert.equal(result.text, '');
|
|
92
|
+
assert.equal(result.error.code, 'upstream_incomplete_stream');
|
|
93
|
+
|
|
94
|
+
const partialUtf8 = make();
|
|
95
|
+
const encoded = bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: '🌍' } }));
|
|
96
|
+
const emojiAt = Buffer.from(encoded).indexOf(Buffer.from(bytes('🌍')));
|
|
97
|
+
partialUtf8.feed(encoded.slice(0, emojiAt + 2));
|
|
98
|
+
result = partialUtf8.end();
|
|
99
|
+
assert.equal(result.outcome, 'incomplete_truncated');
|
|
100
|
+
assert.equal(result.successful, false);
|
|
101
|
+
assert.equal(result.error.code, 'upstream_invalid_utf8');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('empty/metadata-only EOF is incomplete and exact DONE may complete an otherwise empty stream', () => {
|
|
105
|
+
let parser = make();
|
|
106
|
+
assert.equal(parser.end().outcome, 'incomplete_empty');
|
|
107
|
+
parser = make();
|
|
108
|
+
parser.feed(bytes(jsonFrame({ eventType: 'ping', data: {} })));
|
|
109
|
+
assert.equal(parser.end().outcome, 'incomplete_empty');
|
|
110
|
+
parser = make();
|
|
111
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
112
|
+
const result = parser.end();
|
|
113
|
+
assert.equal(result.outcome, 'complete_explicit');
|
|
114
|
+
assert.equal(result.successful, true);
|
|
115
|
+
assert.equal(result.text, '');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('captures conversation identifiers repeated on non-conversation chunks', () => {
|
|
119
|
+
const parser = make();
|
|
120
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'ok', metadata: { conversationId: 'conversation-late' } } })));
|
|
121
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
122
|
+
const result = parser.end();
|
|
123
|
+
assert.equal(result.observations.conversationId, 'conversation-late');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('unknown events, malformed tool fragments, loop approval, malformed JSON, invalid event values, and invalid terminal forms fail closed', () => {
|
|
127
|
+
const cases = [
|
|
128
|
+
[jsonFrame({ eventType: 'futureChunk', data: {} }), 'upstream_unverified_event_shape'],
|
|
129
|
+
[jsonFrame({ eventType: 'toolCallChunk', data: { secret: 'secret-test-value' } }), 'upstream_tool_fragment_error'],
|
|
130
|
+
[jsonFrame({ eventType: 'loopApprovalChunk', data: { message: 'secret-test-value' } }), 'upstream_web_session_loop_approval_unsupported'],
|
|
131
|
+
['data: {bad json secret-test-value}\n\n', 'upstream_invalid_json'],
|
|
132
|
+
['data: []\n\n', 'upstream_invalid_event'],
|
|
133
|
+
['event: completion\ndata: [DONE]\n\n', 'upstream_invalid_terminal'],
|
|
134
|
+
];
|
|
135
|
+
for (const [source, code] of cases) {
|
|
136
|
+
const parser = make();
|
|
137
|
+
throwsCode(() => parser.feed(bytes(source)), code);
|
|
138
|
+
const result = parser.end();
|
|
139
|
+
assert.equal(result.successful, false);
|
|
140
|
+
assert.equal(result.error.code, code);
|
|
141
|
+
assert.equal(JSON.stringify(result).includes('secret-test-value'), false);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('failure and credit states after text preserve bounded text but never become successful', () => {
|
|
146
|
+
for (const [event, code] of [
|
|
147
|
+
[{ eventType: 'failure', data: { message: 'secret-test-value' } }, 'upstream_gateway_failure'],
|
|
148
|
+
[{ eventType: 'error', data: { error: 'secret-test-value' } }, 'upstream_gateway_failure'],
|
|
149
|
+
[{ eventType: 'usage', data: { usageState: 'BLOCKED' } }, 'upstream_credit_blocked'],
|
|
150
|
+
[{ eventType: 'usage', data: { usageState: 'MYSTERY' } }, 'upstream_unknown_credit_state'],
|
|
151
|
+
]) {
|
|
152
|
+
const parser = make();
|
|
153
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'visible' } })));
|
|
154
|
+
throwsCode(() => parser.feed(bytes(jsonFrame(event))), code);
|
|
155
|
+
const result = parser.end();
|
|
156
|
+
assert.equal(result.text, 'visible');
|
|
157
|
+
assert.equal(result.successful, false);
|
|
158
|
+
assert.equal(result.error.code, code);
|
|
159
|
+
assert.equal(JSON.stringify(result).includes('secret-test-value'), false);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('model requested/selected/observed remain distinct; missing mapping is unverified and exact configured mismatch fails', () => {
|
|
164
|
+
let parser = make({ allowedObservedModels: [] });
|
|
165
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'x', metadata: { model: 'UNMAPPED_OBSERVATION' } } })));
|
|
166
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
167
|
+
let result = parser.end();
|
|
168
|
+
assert.equal(result.successful, true);
|
|
169
|
+
assert.equal(result.observations.modelVerification, 'unverified');
|
|
170
|
+
assert.equal(result.observations.requestedModel, 'public-model');
|
|
171
|
+
assert.equal(result.observations.selectedUpstreamModel, 'SELECTED_PIN');
|
|
172
|
+
assert.equal(result.observations.observedUpstreamModel, 'UNMAPPED_OBSERVATION');
|
|
173
|
+
|
|
174
|
+
parser = make();
|
|
175
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'x', metadata: { model: 'WRONG' } } }))), 'upstream_model_mismatch');
|
|
176
|
+
result = parser.end();
|
|
177
|
+
assert.equal(result.successful, false);
|
|
178
|
+
assert.equal(result.text, '');
|
|
179
|
+
assert.equal(result.observations.modelVerification, 'mismatched');
|
|
180
|
+
|
|
181
|
+
parser = make();
|
|
182
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'visible', metadata: { model: 'OBSERVED_PIN' } } })));
|
|
183
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'info', data: { metadata: { model: 'WRONG' } } }))), 'upstream_model_mismatch');
|
|
184
|
+
result = parser.end();
|
|
185
|
+
assert.equal(result.text, 'visible');
|
|
186
|
+
assert.equal(result.successful, false);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test('event, output, observation, and parser-buffer bounds fail without silent truncation', () => {
|
|
190
|
+
let parser = make({ maxOutputBytes: 4 });
|
|
191
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: '12345' } }))), 'upstream_output_too_large');
|
|
192
|
+
assert.equal(parser.end().text, '');
|
|
193
|
+
|
|
194
|
+
parser = make({ maxObservationBytes: 4 });
|
|
195
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'conversation', data: { id: '12345' } }))), 'upstream_invalid_conversation');
|
|
196
|
+
|
|
197
|
+
parser = make({ maxEvents: 1 });
|
|
198
|
+
parser.feed(bytes(jsonFrame({ eventType: 'ping', data: {} })));
|
|
199
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'info', data: {} }))), 'upstream_event_count_exceeded');
|
|
200
|
+
|
|
201
|
+
parser = make({ maxEventBytes: 64 });
|
|
202
|
+
throwsCode(() => parser.feed(bytes(`data: ${'x'.repeat(80)}\n\n`)), 'upstream_event_too_large');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('data after terminal, invalid bytes, wrong chunk type, and feed after end are rejected', () => {
|
|
206
|
+
let parser = make();
|
|
207
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
208
|
+
throwsCode(() => parser.feed(bytes(jsonFrame({ eventType: 'ping', data: {} }))), 'upstream_data_after_terminal');
|
|
209
|
+
|
|
210
|
+
parser = make();
|
|
211
|
+
parser.feed(bytes('data: [DONE]\n\ntrailing-secret-test-value'));
|
|
212
|
+
let result = parser.end();
|
|
213
|
+
assert.equal(result.successful, false);
|
|
214
|
+
assert.equal(result.error.code, 'upstream_data_after_terminal');
|
|
215
|
+
assert.equal(JSON.stringify(result).includes('secret-test-value'), false);
|
|
216
|
+
|
|
217
|
+
parser = make();
|
|
218
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
219
|
+
throwsCode(() => parser.feed(bytes('data: [DONE]\n\n')), 'upstream_data_after_terminal');
|
|
220
|
+
|
|
221
|
+
parser = make();
|
|
222
|
+
throwsCode(() => parser.feed(new Uint8Array([0xff])), 'upstream_invalid_utf8');
|
|
223
|
+
|
|
224
|
+
parser = make();
|
|
225
|
+
assert.throws(() => parser.feed('data: [DONE]\n\n'), /Uint8Array/);
|
|
226
|
+
parser.feed(bytes('data: [DONE]\n\n'));
|
|
227
|
+
parser.end();
|
|
228
|
+
assert.throws(() => parser.feed(bytes('')), /after end/);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('502 upstream_model_mismatch carries observedModel on the error object', () => {
|
|
232
|
+
const parser = make();
|
|
233
|
+
try {
|
|
234
|
+
parser.feed(bytes(jsonFrame({ eventType: 'textChunk', data: { textContent: 'x', metadata: { model: 'REAL_WIRELABEL_CLAUDE_TEST' } } })));
|
|
235
|
+
assert.fail('should have thrown');
|
|
236
|
+
} catch (err) {
|
|
237
|
+
assert.equal(err.code, 'upstream_model_mismatch');
|
|
238
|
+
assert.equal(err.observedModel, 'REAL_WIRELABEL_CLAUDE_TEST');
|
|
239
|
+
assert.equal(err.observedModelField, 'data.metadata.model');
|
|
240
|
+
}
|
|
241
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createProviderServer } from '../src/server.mjs';
|
|
4
|
+
import { loadConfig } from '../src/config.mjs';
|
|
5
|
+
import { listen, request, fixture } from './helpers.mjs';
|
|
6
|
+
|
|
7
|
+
const env = {
|
|
8
|
+
PROVIDER_API_KEY: 'facade-dummy-key', PROVIDER_MODELS: 'public-model',
|
|
9
|
+
POSTMAN_TRANSPORT_STRATEGY: 'web_session', POSTMAN_SESSION_COOKIE: 'dummy-cookie-value',
|
|
10
|
+
POSTMAN_WORKSPACE_SUBDOMAIN: 'fixture-team', POSTMAN_WORKSPACE_ID: 'fixture-workspace',
|
|
11
|
+
POSTMAN_WEB_SESSION_SELECTED_MODEL: 'GPT_56_SOL', POSTMAN_WEB_SESSION_OBSERVED_MODELS: 'gpt-5.6-sol',
|
|
12
|
+
REQUEST_TIMEOUT_MS: '1000', MAX_BODY_BYTES: '8192', MAX_EVENT_BYTES: '8192', MAX_OUTPUT_BYTES: '8192',
|
|
13
|
+
MAX_TOOL_ARGUMENT_BYTES: '1024', MAX_TOOL_RESULT_BYTES: '1024', MAX_TOOLS: '8', MAX_CONCURRENT_REQUESTS: '2',
|
|
14
|
+
};
|
|
15
|
+
const encoder = new TextEncoder();
|
|
16
|
+
const response = ({ events = [], done = true, status = 200, contentType = 'text/event-stream', suffix = '' }) => {
|
|
17
|
+
const wire = events.map(event => `data: ${JSON.stringify(event)}\n\n`).join('') + (done ? 'data: [DONE]\n\n' : '') + suffix;
|
|
18
|
+
return new Response(new ReadableStream({ start(controller) { controller.enqueue(encoder.encode(wire)); controller.close(); } }), { status, headers: { 'content-type': contentType } });
|
|
19
|
+
};
|
|
20
|
+
async function withServer(fetchImpl, fn, override = {}) {
|
|
21
|
+
const server = createProviderServer({ ...loadConfig(env), ...override }, { fetchImpl, logger: { error() {} } });
|
|
22
|
+
const base = await listen(server);
|
|
23
|
+
try { await fn(base); } finally { await new Promise(resolve => server.close(resolve)); }
|
|
24
|
+
}
|
|
25
|
+
const body = stream => ({ model: 'public-model', messages: [{ role: 'user', content: 'Reply exactly.' }], stream });
|
|
26
|
+
|
|
27
|
+
test('explicit web_session returns OpenAI JSON from source-derived observed-DONE fixture and isolates auth headers', async () => {
|
|
28
|
+
const source = await fixture('web-session-observed-done');
|
|
29
|
+
let capture;
|
|
30
|
+
await withServer(async (url, init) => { capture = { url: String(url), init, requestBody: JSON.parse(init.body) }; return response(source); }, async base => {
|
|
31
|
+
const result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(false) });
|
|
32
|
+
assert.equal(result.status, 200);
|
|
33
|
+
const value = await result.json();
|
|
34
|
+
assert.equal(value.choices[0].message.content, 'PROXY_OBSERVATION_OK');
|
|
35
|
+
assert.equal(value.choices[0].finish_reason, 'stop');
|
|
36
|
+
assert.equal(capture.url, 'https://fixture-team.postman.co/_gw/chat');
|
|
37
|
+
assert.equal(capture.init.headers.cookie, 'postman.sid=dummy-cookie-value');
|
|
38
|
+
assert.equal(capture.init.headers.authorization, undefined);
|
|
39
|
+
assert.equal(capture.init.headers['x-access-token'], undefined);
|
|
40
|
+
assert.equal(JSON.stringify(capture).includes('facade-dummy-key'), false);
|
|
41
|
+
assert.equal(capture.requestBody.devModeOptions.selectedModel, 'GPT_56_SOL');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('explicit web_session returns OpenAI SSE with content, stop, and DONE only after proven upstream DONE', async () => {
|
|
46
|
+
const source = await fixture('web-session-observed-done');
|
|
47
|
+
await withServer(() => response(source), async base => {
|
|
48
|
+
const result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(true) });
|
|
49
|
+
assert.match(result.headers.get('content-type'), /text\/event-stream/);
|
|
50
|
+
const wire = await result.text();
|
|
51
|
+
assert.match(wire, /"content":"PROXY_OBSERVATION_OK"/);
|
|
52
|
+
assert.match(wire, /"finish_reason":"stop"/);
|
|
53
|
+
assert.match(wire, /data: \[DONE\]/);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('missing model observation is allowed but configured observed mismatch fails before successful response', async () => {
|
|
58
|
+
await withServer(() => response({ events: [{ eventType: 'textChunk', data: { textContent: 'unobserved' } }] }), async base => {
|
|
59
|
+
const result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(false) });
|
|
60
|
+
assert.equal(result.status, 200); assert.equal((await result.json()).choices[0].message.content, 'unobserved');
|
|
61
|
+
});
|
|
62
|
+
await withServer(() => response({ events: [{ eventType: 'textChunk', data: { textContent: 'wrong', metadata: { model: 'other-model' } } }] }), async base => {
|
|
63
|
+
const result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(false) });
|
|
64
|
+
assert.equal(result.status, 502); assert.equal((await result.json()).error.code, 'upstream_model_mismatch');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('error event, ambiguous EOF, wrong content type, and HTTP failure never become successful web_session completions', async () => {
|
|
69
|
+
const cases = [
|
|
70
|
+
[() => response({ events: [{ eventType: 'textChunk', data: { textContent: 'partial' } }, { eventType: 'error', data: { error: 'secret-upstream-value' } }] }), 502, 'upstream_gateway_failure'],
|
|
71
|
+
[() => response({ events: [{ eventType: 'textChunk', data: { textContent: 'partial' } }], done: false }), 502, 'upstream_completion_ambiguous'],
|
|
72
|
+
[() => response({ events: [], contentType: 'application/json' }), 502, 'upstream_content_type_error'],
|
|
73
|
+
[() => response({ events: [], status: 401 }), 401, 'upstream_http_error'],
|
|
74
|
+
[() => response({ events: [], status: 503 }), 503, 'upstream_http_error'],
|
|
75
|
+
];
|
|
76
|
+
for (const [upstream, status, code] of cases) await withServer(upstream, async base => {
|
|
77
|
+
const result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(false) });
|
|
78
|
+
assert.equal(result.status, status); const wire = await result.text(); assert.equal(JSON.parse(wire).error.code, code); assert.equal(wire.includes('secret-upstream-value'), false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('web_session accepts declared tools while preserving ingress auth failure isolation', async () => {
|
|
83
|
+
let calls = 0; let capture;
|
|
84
|
+
await withServer(async (_url, init) => { calls++; capture=JSON.parse(init.body); return response({ events: [] }); }, async base => {
|
|
85
|
+
const toolBody = { ...body(false), tools: [{ type: 'function', function: { name: 'lookup', parameters: { type: 'object', properties: {} } } }] };
|
|
86
|
+
let result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: toolBody });
|
|
87
|
+
assert.equal(result.status, 200); assert.deepEqual(capture.clientTools.thirdParty['proxy-tools'].tools.map(tool=>tool.name), ['openai__lookup']); assert.equal(calls, 1);
|
|
88
|
+
result = await request(base, '/v1/chat/completions', { method: 'POST', token: 'wrong-key', body: body(false) });
|
|
89
|
+
assert.equal(result.status, 401); assert.equal((await result.json()).error.code, 'invalid_api_key'); assert.equal(calls, 1);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('web_session propagates client abort to injected upstream', async () => {
|
|
94
|
+
let upstreamAborted = false;
|
|
95
|
+
await withServer((url, init) => new Promise((resolve, reject) => init.signal.addEventListener('abort', () => { upstreamAborted = true; reject(init.signal.reason); }, { once: true })), async base => {
|
|
96
|
+
const controller = new AbortController();
|
|
97
|
+
const pending = request(base, '/v1/chat/completions', { method: 'POST', token: 'facade-dummy-key', body: body(false), signal: controller.signal });
|
|
98
|
+
setTimeout(() => controller.abort(new DOMException('fixture abort', 'AbortError')), 20);
|
|
99
|
+
await assert.rejects(pending, error => error?.name === 'AbortError');
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
101
|
+
assert.equal(upstreamAborted, true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createProviderServer } from '../src/server.mjs';
|
|
4
|
+
import { loadConfig } from '../src/config.mjs';
|
|
5
|
+
import { CorrelationStore } from '../src/correlation-store.mjs';
|
|
6
|
+
import { listen, request, fixture } from './helpers.mjs';
|
|
7
|
+
|
|
8
|
+
const env={PROVIDER_API_KEY:'key',PROVIDER_MODELS:'m',POSTMAN_TRANSPORT_STRATEGY:'web_session',POSTMAN_SESSION_COOKIE:'dummy',POSTMAN_WORKSPACE_SUBDOMAIN:'team',POSTMAN_WORKSPACE_ID:'ws',POSTMAN_WEB_SESSION_SELECTED_MODEL:'GPT',REQUEST_TIMEOUT_MS:'1000',MAX_BODY_BYTES:'65536',MAX_EVENT_BYTES:'65536',MAX_OUTPUT_BYTES:'65536',MAX_TOOL_ARGUMENT_BYTES:'4096',MAX_TOOL_RESULT_BYTES:'4096',MAX_TOOLS:'8',MAX_CONCURRENT_REQUESTS:'4'};
|
|
9
|
+
const config=loadConfig(env); const encoder=new TextEncoder();
|
|
10
|
+
const sse=(events,{done=true}={})=>new Response(new ReadableStream({start(c){for(const e of events)c.enqueue(encoder.encode(`data: ${JSON.stringify(e)}\n\n`));if(done)c.enqueue(encoder.encode('data: [DONE]\n\n'));c.close();}}),{status:200,headers:{'content-type':'text/event-stream'}});
|
|
11
|
+
const tools=[
|
|
12
|
+
{type:'function',function:{name:'lookup',description:'Lookup',parameters:{type:'object',properties:{q:{type:'string'}},required:['q'],additionalProperties:false}}},
|
|
13
|
+
{type:'function',function:{name:'count',description:'Count',parameters:{type:'object',properties:{n:{type:'integer'}},required:['n'],additionalProperties:false}}},
|
|
14
|
+
];
|
|
15
|
+
const body=(messages= [{role:'user',content:'use tools'}],more={})=>({model:'m',messages,tools,stream:false,...more});
|
|
16
|
+
async function withServer(fetchImpl,fn,override={}){const server=createProviderServer({...config,...override},{fetchImpl,logger:{error(){}}});const base=await listen(server);try{await fn(base);}finally{await new Promise(r=>server.close(r));}}
|
|
17
|
+
|
|
18
|
+
test('web_session advertises minimal catalog, buffers grouped fragments, continues with correlated TOOL_RESPONSE, and rejects replay',async()=>{
|
|
19
|
+
const source=await fixture('web-session-tool-fragments'); let turn=0; const captures=[];
|
|
20
|
+
await withServer(async(url,init)=>{captures.push(JSON.parse(init.body));return turn++===0?sse(source.events):sse([{eventType:'conversation',data:{id:'conv_source_fixture'}},{eventType:'textChunk',data:{textContent:'final'}}]);},async base=>{
|
|
21
|
+
const first=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:body()}); assert.equal(first.status,200); const v=await first.json(); assert.equal(v.choices[0].finish_reason,'tool_calls'); const assistant=v.choices[0].message; assert.equal(assistant.tool_calls.length,2); assert.match(assistant.tool_calls[0].id,/^call_pmn_/); assert.deepEqual(JSON.parse(assistant.tool_calls[0].function.arguments),{q:'x'}); assert.deepEqual(JSON.parse(assistant.tool_calls[1].function.arguments),{n:2});
|
|
22
|
+
assert.deepEqual(captures[0].clientTools.thirdParty['proxy-tools'].tools.map(t=>t.name),['openai__lookup','openai__count']); assert.equal(captures[0].devModeOptions.autoRun,true);
|
|
23
|
+
const continuation=body([{role:'user',content:'use tools'},assistant,{role:'tool',tool_call_id:assistant.tool_calls[0].id,content:'A'},{role:'tool',tool_call_id:assistant.tool_calls[1].id,content:'B'}]);
|
|
24
|
+
const second=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:continuation}); assert.equal(second.status,200); assert.equal((await second.json()).choices[0].message.content,'final'); assert.equal(captures[1].input.chatType,'TOOL_RESPONSE'); assert.equal(captures[1].input.toolCallGroupId,'up_group_1'); assert.equal(captures[1].input.toolCallId,'up_call_a'); assert.deepEqual(JSON.parse(captures[1].input.toolResponse).map(x=>x.toolCallId),['up_call_a','up_call_b']);
|
|
25
|
+
const replay=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:continuation}); assert.equal(replay.status,409); assert.equal((await replay.json()).error.code,'tool_call_already_consumed'); assert.equal(captures.length,2);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('AICoworker-style continuation preserves role=tool correlation and dispatches exactly once',async()=>{
|
|
30
|
+
const source=await fixture('web-session-tool-fragments'); let turn=0; const captures=[];
|
|
31
|
+
await withServer(async(url,init)=>{captures.push(JSON.parse(init.body));return turn++===0?sse(source.events):sse([{eventType:'conversation',data:{id:'conv_source_fixture'}},{eventType:'textChunk',data:{textContent:'AKI_ONCE'}}]);},async base=>{
|
|
32
|
+
const first=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:body()}); const assistant=(await first.json()).choices[0].message;
|
|
33
|
+
const continuation=body([{role:'user',content:'use tools'},assistant,{role:'tool',tool_call_id:assistant.tool_calls[0].id,content:[{type:'text',text:'A'}]},{role:'tool',tool_call_id:assistant.tool_calls[1].id,content:[{type:'text',text:'B'}]}]);
|
|
34
|
+
const second=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:continuation}); assert.equal(second.status,200); assert.equal((await second.json()).choices[0].message.content,'AKI_ONCE');
|
|
35
|
+
assert.equal(captures.length,2); assert.equal(captures[1].input.chatType,'TOOL_RESPONSE'); assert.equal(captures[1].input.toolCallId,'up_call_a'); assert.deepEqual(JSON.parse(captures[1].input.toolResponse).map(x=>x.content),['A','B']);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('web_session rejects bad arguments, partial group, loop approval, and ambiguous EOF before admission',async()=>{
|
|
40
|
+
const cases=[
|
|
41
|
+
[[{eventType:'conversation',data:{id:'c'}},{eventType:'toolCallChunk',data:{id:'a',index:0,name:'openai__count',arguments:'{\"n\":\"bad\"}'}}],{},'upstream_tool_schema_mismatch'],
|
|
42
|
+
[[{eventType:'conversation',data:{id:'c'}},{eventType:'toolCallChunk',data:{toolCalls:[{id:'a',index:0,name:'openai__lookup',toolCallGroupId:'g',arguments:'{\"q\":\"x\"}'},{id:'b',index:1,name:'openai__count',arguments:'{\"n\":2}' }]}}],{},'upstream_ungrouped_tool_batch'],
|
|
43
|
+
[[{eventType:'loopApprovalChunk',data:{message:'approve'}}],{},'upstream_web_session_loop_approval_unsupported'],
|
|
44
|
+
[[{eventType:'conversation',data:{id:'c'}},{eventType:'toolCallChunk',data:{id:'a',index:0,name:'openai__lookup',arguments:'{\"q\":\"x\"}'}}],{done:false},'upstream_completion_ambiguous'],
|
|
45
|
+
];
|
|
46
|
+
for(const [events,opts,code] of cases){let calls=0;await withServer(()=>{calls++;return sse(events,opts);},async base=>{const r=await request(base,'/v1/chat/completions',{method:'POST',token:'key',body:body()});const expectedStatus=code==='upstream_web_session_loop_approval_unsupported'?400:502;assert.equal(r.status,expectedStatus);assert.equal((await r.json()).error.code,code);assert.equal(calls,1);});}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('correlation binds principal, strategy and origin without mutating pending record',()=>{
|
|
50
|
+
const store=new CorrelationStore(); const common={principalId:'p',workspaceId:'ws',model:'m',transportStrategy:'web_session',transportOrigin:'https://team.postman.co',conversationId:'c',transcript:[{role:'user',content:'u'},{role:'assistant',content:'',tool_calls:[{id:'up',type:'function',function:{name:'lookup',arguments:'{\"q\":\"x\"}'}}]}],toolsBinding:{tools:[tools[0]],toolChoice:'auto'},catalogMetadata:{strategy:'web_session'},calls:[{upstreamToolCallId:'up',upstreamGroupId:null,originalName:'lookup',argumentsJson:'{\"q\":\"x\"}',schemaHash:'s'}]}; const g=store.admit(common); const result=[{toolCallId:g.calls[0].publicToolCallId,content:'ok'}]; const claim={principalId:'p',results:result,workspaceId:'ws',model:'m',transportStrategy:'web_session',transportOrigin:'https://team.postman.co',transcript:g.transcript,toolsBinding:common.toolsBinding,catalogMetadata:common.catalogMetadata};
|
|
51
|
+
assert.throws(()=>store.claim({...claim,transportStrategy:'access_token'}),e=>e.code==='continuation_strategy_mismatch'); assert.equal(g.state,'PENDING'); assert.throws(()=>store.claim({...claim,transportOrigin:'https://other.postman.co'}),e=>e.code==='continuation_origin_mismatch'); assert.equal(g.state,'PENDING'); assert.throws(()=>store.claim({...claim,principalId:'other'}),e=>e.code==='unknown_tool_call');
|
|
52
|
+
});
|
package/version.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"latest": "2.3.3",
|
|
3
|
+
"minSupported": "2.3.0",
|
|
4
|
+
"releasedAt": "2026-09-13T06:30:00Z",
|
|
5
|
+
"releaseNotesUrl": "https://github.com/khangtudo/aki-pro-max/releases/tag/v2.3.3",
|
|
6
|
+
"installPackage": "aki-pro-max",
|
|
7
|
+
"updateCommand": "npm install -g aki-pro-max@latest"
|
|
8
|
+
}
|