@antseed/cli 0.1.132 → 0.1.134

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 (157) hide show
  1. package/README.md +12 -1
  2. package/dist/cli/commands/buyer/channel-withdraw.d.ts +7 -0
  3. package/dist/cli/commands/buyer/channel-withdraw.d.ts.map +1 -0
  4. package/dist/cli/commands/buyer/channel-withdraw.js +178 -0
  5. package/dist/cli/commands/buyer/channel-withdraw.js.map +1 -0
  6. package/dist/cli/commands/buyer/channel-withdraw.test.d.ts +2 -0
  7. package/dist/cli/commands/buyer/channel-withdraw.test.d.ts.map +1 -0
  8. package/dist/cli/commands/buyer/channel-withdraw.test.js +45 -0
  9. package/dist/cli/commands/buyer/channel-withdraw.test.js.map +1 -0
  10. package/dist/cli/commands/buyer/channels.d.ts.map +1 -1
  11. package/dist/cli/commands/buyer/channels.js +60 -52
  12. package/dist/cli/commands/buyer/channels.js.map +1 -1
  13. package/dist/cli/commands/buyer/emissions.d.ts +3 -0
  14. package/dist/cli/commands/buyer/emissions.d.ts.map +1 -0
  15. package/dist/cli/commands/buyer/emissions.js +5 -0
  16. package/dist/cli/commands/buyer/emissions.js.map +1 -0
  17. package/dist/cli/commands/buyer/index.d.ts.map +1 -1
  18. package/dist/cli/commands/buyer/index.js +4 -0
  19. package/dist/cli/commands/buyer/index.js.map +1 -1
  20. package/dist/cli/commands/buyer/start.d.ts +1 -0
  21. package/dist/cli/commands/buyer/start.d.ts.map +1 -1
  22. package/dist/cli/commands/buyer/start.js +16 -0
  23. package/dist/cli/commands/buyer/start.js.map +1 -1
  24. package/dist/cli/commands/buyer/start.test.js +2 -0
  25. package/dist/cli/commands/buyer/start.test.js.map +1 -1
  26. package/dist/cli/commands/buyer/sweep.d.ts +3 -0
  27. package/dist/cli/commands/buyer/sweep.d.ts.map +1 -0
  28. package/dist/cli/commands/buyer/sweep.js +332 -0
  29. package/dist/cli/commands/buyer/sweep.js.map +1 -0
  30. package/dist/cli/commands/buyer/sweep.test.d.ts +2 -0
  31. package/dist/cli/commands/buyer/sweep.test.d.ts.map +1 -0
  32. package/dist/cli/commands/buyer/sweep.test.js +46 -0
  33. package/dist/cli/commands/buyer/sweep.test.js.map +1 -0
  34. package/dist/cli/commands/emissions.d.ts +10 -0
  35. package/dist/cli/commands/emissions.d.ts.map +1 -0
  36. package/dist/cli/commands/emissions.js +91 -0
  37. package/dist/cli/commands/emissions.js.map +1 -0
  38. package/dist/cli/commands/emissions.test.d.ts +2 -0
  39. package/dist/cli/commands/emissions.test.d.ts.map +1 -0
  40. package/dist/cli/commands/emissions.test.js +16 -0
  41. package/dist/cli/commands/emissions.test.js.map +1 -0
  42. package/dist/cli/commands/seller/emissions.d.ts.map +1 -1
  43. package/dist/cli/commands/seller/emissions.js +2 -77
  44. package/dist/cli/commands/seller/emissions.js.map +1 -1
  45. package/dist/cli/commands/seller/start.d.ts.map +1 -1
  46. package/dist/cli/commands/seller/start.js +5 -0
  47. package/dist/cli/commands/seller/start.js.map +1 -1
  48. package/dist/cli/commands/system-proxy/index.d.ts +3 -0
  49. package/dist/cli/commands/system-proxy/index.d.ts.map +1 -0
  50. package/dist/cli/commands/system-proxy/index.js +14 -0
  51. package/dist/cli/commands/system-proxy/index.js.map +1 -0
  52. package/dist/cli/commands/system-proxy/install-ca.d.ts +3 -0
  53. package/dist/cli/commands/system-proxy/install-ca.d.ts.map +1 -0
  54. package/dist/cli/commands/system-proxy/install-ca.js +43 -0
  55. package/dist/cli/commands/system-proxy/install-ca.js.map +1 -0
  56. package/dist/cli/commands/system-proxy/start.d.ts +3 -0
  57. package/dist/cli/commands/system-proxy/start.d.ts.map +1 -0
  58. package/dist/cli/commands/system-proxy/start.js +151 -0
  59. package/dist/cli/commands/system-proxy/start.js.map +1 -0
  60. package/dist/cli/commands/system-proxy/status.d.ts +3 -0
  61. package/dist/cli/commands/system-proxy/status.d.ts.map +1 -0
  62. package/dist/cli/commands/system-proxy/status.js +60 -0
  63. package/dist/cli/commands/system-proxy/status.js.map +1 -0
  64. package/dist/cli/commands/system-proxy/stop.d.ts +3 -0
  65. package/dist/cli/commands/system-proxy/stop.d.ts.map +1 -0
  66. package/dist/cli/commands/system-proxy/stop.js +71 -0
  67. package/dist/cli/commands/system-proxy/stop.js.map +1 -0
  68. package/dist/cli/index.js +2 -0
  69. package/dist/cli/index.js.map +1 -1
  70. package/dist/cli/payment-utils.d.ts +7 -1
  71. package/dist/cli/payment-utils.d.ts.map +1 -1
  72. package/dist/cli/payment-utils.js +20 -1
  73. package/dist/cli/payment-utils.js.map +1 -1
  74. package/dist/config/defaults.d.ts.map +1 -1
  75. package/dist/config/defaults.js +5 -1
  76. package/dist/config/defaults.js.map +1 -1
  77. package/dist/config/effective.d.ts +1 -0
  78. package/dist/config/effective.d.ts.map +1 -1
  79. package/dist/config/effective.js +18 -0
  80. package/dist/config/effective.js.map +1 -1
  81. package/dist/config/effective.test.js +10 -0
  82. package/dist/config/effective.test.js.map +1 -1
  83. package/dist/config/loader.d.ts.map +1 -1
  84. package/dist/config/loader.js +9 -0
  85. package/dist/config/loader.js.map +1 -1
  86. package/dist/config/loader.test.js +31 -0
  87. package/dist/config/loader.test.js.map +1 -1
  88. package/dist/config/types.d.ts +20 -0
  89. package/dist/config/types.d.ts.map +1 -1
  90. package/dist/config/validation.d.ts.map +1 -1
  91. package/dist/config/validation.js +17 -0
  92. package/dist/config/validation.js.map +1 -1
  93. package/dist/proxy/buyer-proxy.d.ts +2 -0
  94. package/dist/proxy/buyer-proxy.d.ts.map +1 -1
  95. package/dist/proxy/buyer-proxy.js +89 -16
  96. package/dist/proxy/buyer-proxy.js.map +1 -1
  97. package/dist/proxy/buyer-proxy.test.js +190 -1
  98. package/dist/proxy/buyer-proxy.test.js.map +1 -1
  99. package/dist/proxy/request-utils.d.ts +1 -1
  100. package/dist/proxy/request-utils.d.ts.map +1 -1
  101. package/dist/proxy/request-utils.js +9 -3
  102. package/dist/proxy/request-utils.js.map +1 -1
  103. package/dist/proxy/service-api-adapter.d.ts +2 -2
  104. package/dist/proxy/service-api-adapter.d.ts.map +1 -1
  105. package/dist/proxy/service-api-adapter.js +1 -1
  106. package/dist/proxy/service-api-adapter.js.map +1 -1
  107. package/dist/proxy/service-api-adapter.test.js +21 -12
  108. package/dist/proxy/service-api-adapter.test.js.map +1 -1
  109. package/dist/system-proxy/ca-manager.d.ts +21 -0
  110. package/dist/system-proxy/ca-manager.d.ts.map +1 -0
  111. package/dist/system-proxy/ca-manager.js +112 -0
  112. package/dist/system-proxy/ca-manager.js.map +1 -0
  113. package/dist/system-proxy/ca-manager.test.d.ts +2 -0
  114. package/dist/system-proxy/ca-manager.test.d.ts.map +1 -0
  115. package/dist/system-proxy/ca-manager.test.js +64 -0
  116. package/dist/system-proxy/ca-manager.test.js.map +1 -0
  117. package/dist/system-proxy/cert-cache.d.ts +16 -0
  118. package/dist/system-proxy/cert-cache.d.ts.map +1 -0
  119. package/dist/system-proxy/cert-cache.js +69 -0
  120. package/dist/system-proxy/cert-cache.js.map +1 -0
  121. package/dist/system-proxy/cert-cache.test.d.ts +2 -0
  122. package/dist/system-proxy/cert-cache.test.d.ts.map +1 -0
  123. package/dist/system-proxy/cert-cache.test.js +52 -0
  124. package/dist/system-proxy/cert-cache.test.js.map +1 -0
  125. package/dist/system-proxy/index.d.ts +11 -0
  126. package/dist/system-proxy/index.d.ts.map +1 -0
  127. package/dist/system-proxy/index.js +7 -0
  128. package/dist/system-proxy/index.js.map +1 -0
  129. package/dist/system-proxy/node-hook.d.ts +3 -0
  130. package/dist/system-proxy/node-hook.d.ts.map +1 -0
  131. package/dist/system-proxy/node-hook.js +27 -0
  132. package/dist/system-proxy/node-hook.js.map +1 -0
  133. package/dist/system-proxy/profiles.d.ts +13 -0
  134. package/dist/system-proxy/profiles.d.ts.map +1 -0
  135. package/dist/system-proxy/profiles.js +150 -0
  136. package/dist/system-proxy/profiles.js.map +1 -0
  137. package/dist/system-proxy/profiles.test.d.ts +2 -0
  138. package/dist/system-proxy/profiles.test.d.ts.map +1 -0
  139. package/dist/system-proxy/profiles.test.js +66 -0
  140. package/dist/system-proxy/profiles.test.js.map +1 -0
  141. package/dist/system-proxy/proxy-server.d.ts +98 -0
  142. package/dist/system-proxy/proxy-server.d.ts.map +1 -0
  143. package/dist/system-proxy/proxy-server.js +977 -0
  144. package/dist/system-proxy/proxy-server.js.map +1 -0
  145. package/dist/system-proxy/proxy-server.test.d.ts +2 -0
  146. package/dist/system-proxy/proxy-server.test.d.ts.map +1 -0
  147. package/dist/system-proxy/proxy-server.test.js +778 -0
  148. package/dist/system-proxy/proxy-server.test.js.map +1 -0
  149. package/dist/system-proxy/system-proxy.d.ts +25 -0
  150. package/dist/system-proxy/system-proxy.d.ts.map +1 -0
  151. package/dist/system-proxy/system-proxy.js +152 -0
  152. package/dist/system-proxy/system-proxy.js.map +1 -0
  153. package/dist/system-proxy/types.d.ts +69 -0
  154. package/dist/system-proxy/types.d.ts.map +1 -0
  155. package/dist/system-proxy/types.js +2 -0
  156. package/dist/system-proxy/types.js.map +1 -0
  157. package/package.json +8 -6
@@ -0,0 +1,778 @@
1
+ import assert from 'node:assert/strict';
2
+ import * as http from 'node:http';
3
+ import * as tls from 'node:tls';
4
+ import { mkdtemp, rm } from 'node:fs/promises';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import test from 'node:test';
8
+ import { buildBuyerForwardRequest, ConversationSseAdapter, ResponsesSseNormalizer, rewriteRequestBody, transformRequestBody, shouldSystemProxyRequest, SystemProxyServer, } from './proxy-server.js';
9
+ import { CAManager } from './ca-manager.js';
10
+ import { CertCache } from './cert-cache.js';
11
+ const CONVERSATION_FORWARD_RULE = {
12
+ source: 'conversation',
13
+ targetPath: '/v1/chat/completions',
14
+ headers: 'browser-sse',
15
+ request: {
16
+ kind: 'chat-completions-from-messages',
17
+ messagesPath: ['messages'],
18
+ modelPath: ['model'],
19
+ missingModel: 'auto',
20
+ stream: true,
21
+ },
22
+ response: { kind: 'conversation-sse', assistantParent: 'user-message' },
23
+ };
24
+ const WORKFLOW_FORWARD_RULE = {
25
+ source: 'workflow',
26
+ targetPath: '/v1/responses',
27
+ headers: 'browser-sse',
28
+ request: {
29
+ kind: 'responses-from-messages',
30
+ messagesPath: ['input', 'messages'],
31
+ modelPath: ['model'],
32
+ stream: true,
33
+ },
34
+ cors: { allowOrigin: 'request', allowCredentials: true, allowHeaders: 'request' },
35
+ response: {
36
+ kind: 'responses-sse',
37
+ injectResponseId: true,
38
+ stripDataType: true,
39
+ stripItemPhase: true,
40
+ emptyContentPartDoneText: true,
41
+ emptyOutputItemDoneContent: true,
42
+ messageOutputIndex: 1,
43
+ messageItemId: 'agent-node:llm:0',
44
+ },
45
+ };
46
+ // ── Unit tests for rewriteRequestBody ────────────────────────────────────────
47
+ test('rewriteRequestBody: injects peerId into model field', () => {
48
+ const body = Buffer.from(JSON.stringify({ model: 'model-default', messages: [] }));
49
+ const result = rewriteRequestBody(body, 'peer123');
50
+ const parsed = JSON.parse(result.toString('utf8'));
51
+ assert.equal(parsed.model, 'peer123@model-default');
52
+ });
53
+ test('rewriteRequestBody: leaves other fields intact', () => {
54
+ const input = { model: 'model-legacy', messages: [{ role: 'user', content: 'hi' }], max_tokens: 100 };
55
+ const result = rewriteRequestBody(Buffer.from(JSON.stringify(input)), 'peer');
56
+ const parsed = JSON.parse(result.toString('utf8'));
57
+ assert.deepEqual(parsed.messages, input.messages);
58
+ assert.equal(parsed.max_tokens, 100);
59
+ });
60
+ test('rewriteRequestBody: no-op when model field is absent', () => {
61
+ const body = Buffer.from(JSON.stringify({ input: 'hello', stream: true }));
62
+ const result = rewriteRequestBody(body, 'peer123');
63
+ const parsed = JSON.parse(result.toString('utf8'));
64
+ assert.equal(parsed.model, undefined);
65
+ assert.equal(parsed.input, 'hello');
66
+ });
67
+ test('rewriteRequestBody: no-op for empty peerId', () => {
68
+ const body = Buffer.from(JSON.stringify({ model: 'model-default' }));
69
+ const result = rewriteRequestBody(body, '');
70
+ const parsed = JSON.parse(result.toString('utf8'));
71
+ assert.equal(parsed.model, 'model-default');
72
+ });
73
+ test('rewriteRequestBody: passes through non-JSON unchanged', () => {
74
+ const raw = Buffer.from('not json at all');
75
+ const result = rewriteRequestBody(raw, 'peer');
76
+ assert.deepEqual(result, raw);
77
+ });
78
+ test('rewriteRequestBody: passes through empty buffer unchanged', () => {
79
+ const result = rewriteRequestBody(Buffer.alloc(0), 'peer');
80
+ assert.equal(result.length, 0);
81
+ });
82
+ test('rewriteRequestBody: handles model as non-string (leaves unchanged)', () => {
83
+ const body = Buffer.from(JSON.stringify({ model: 42 }));
84
+ const result = rewriteRequestBody(body, 'peer');
85
+ const parsed = JSON.parse(result.toString('utf8'));
86
+ assert.equal(parsed.model, 42);
87
+ });
88
+ test('rewriteRequestBody: uses default model when requested model is not served by the peer', () => {
89
+ const body = Buffer.from(JSON.stringify({ model: 'gpt-5.5', messages: [] }));
90
+ const result = rewriteRequestBody(body, 'peer', {
91
+ defaultModel: 'model-large',
92
+ servedModels: new Set(['model-large']),
93
+ });
94
+ const parsed = JSON.parse(result.toString('utf8'));
95
+ assert.equal(parsed.model, 'peer@model-large');
96
+ });
97
+ test('rewriteRequestBody: keeps requested model when selected peer serves it', () => {
98
+ const body = Buffer.from(JSON.stringify({ model: 'model-large', messages: [] }));
99
+ const result = rewriteRequestBody(body, 'peer', {
100
+ defaultModel: 'model-small',
101
+ servedModels: new Set(['model-large', 'model-small']),
102
+ });
103
+ const parsed = JSON.parse(result.toString('utf8'));
104
+ assert.equal(parsed.model, 'peer@model-large');
105
+ });
106
+ test('shouldSystemProxyRequest: only matches configured model API routes', () => {
107
+ const routes = new Map([
108
+ ['api-a.example.test', new Set(['/v1/messages', '/v1/complete'])],
109
+ ['api-b.example.test', new Set(['/v1/responses', '/v1/chat/completions'])],
110
+ ]);
111
+ assert.equal(shouldSystemProxyRequest('api-a.example.test', '/v1/messages', routes), true);
112
+ assert.equal(shouldSystemProxyRequest('api-a.example.test', '/v1/messages?beta=true', routes), true);
113
+ assert.equal(shouldSystemProxyRequest('api-b.example.test', '/v1/responses', routes), true);
114
+ assert.equal(shouldSystemProxyRequest('api-a.example.test', '/auth', routes), false);
115
+ assert.equal(shouldSystemProxyRequest('api-a.example.test', '/v1/oauth/token', routes), false);
116
+ assert.equal(shouldSystemProxyRequest('console.example.test', '/v1/messages', routes), false);
117
+ });
118
+ test('transformRequestBody: converts configured message body to chat completions completions', () => {
119
+ const body = Buffer.from(JSON.stringify({
120
+ action: 'next',
121
+ model: 'auto',
122
+ messages: [
123
+ {
124
+ id: 'msg-1',
125
+ author: { role: 'user' },
126
+ content: { content_type: 'text', parts: ['hello from profile'] },
127
+ },
128
+ ],
129
+ }));
130
+ const result = transformRequestBody(body, 'peer123', CONVERSATION_FORWARD_RULE.request);
131
+ assert.ok(result);
132
+ const parsed = JSON.parse(result.toString('utf8'));
133
+ assert.equal(parsed.model, 'peer123@auto');
134
+ assert.deepEqual(parsed.messages, [{ role: 'user', content: 'hello from profile' }]);
135
+ assert.equal(parsed.stream, true);
136
+ });
137
+ test('transformRequestBody: maps auto model to selected default model', () => {
138
+ const body = Buffer.from(JSON.stringify({
139
+ action: 'next',
140
+ model: 'auto',
141
+ messages: [
142
+ {
143
+ id: 'msg-1',
144
+ author: { role: 'user' },
145
+ content: { content_type: 'text', parts: ['hello from profile'] },
146
+ },
147
+ ],
148
+ }));
149
+ const result = transformRequestBody(body, 'peer123', CONVERSATION_FORWARD_RULE.request, {
150
+ defaultModel: 'model-large',
151
+ servedModels: new Set(['model-large']),
152
+ });
153
+ assert.ok(result);
154
+ const parsed = JSON.parse(result.toString('utf8'));
155
+ assert.equal(parsed.model, 'peer123@model-large');
156
+ });
157
+ test('transformRequestBody: returns null for configured message transform without messages', () => {
158
+ const body = Buffer.from(JSON.stringify({
159
+ action: 'next',
160
+ model: 'auto',
161
+ client_prepare_state: 'none',
162
+ client_contextual_info: { app_name: 'conversation.com' },
163
+ }));
164
+ assert.equal(transformRequestBody(body, 'peer123', CONVERSATION_FORWARD_RULE.request), null);
165
+ });
166
+ test('buildBuyerForwardRequest: routes conversation message requests to chat completions completions', () => {
167
+ const routes = new Map([
168
+ ['conversation.com', new Set(['/profile-api/conversation'])],
169
+ ]);
170
+ const rawBody = Buffer.from(JSON.stringify({
171
+ model: 'auto',
172
+ messages: [
173
+ {
174
+ author: { role: 'user' },
175
+ content: { parts: ['hello'] },
176
+ },
177
+ ],
178
+ }));
179
+ const result = buildBuyerForwardRequest({
180
+ host: 'conversation.com',
181
+ path: '/profile-api/conversation',
182
+ rawBody,
183
+ isJson: true,
184
+ peerId: 'peer123',
185
+ prefixesByHost: routes,
186
+ forwardRulesByHost: new Map([
187
+ ['conversation.com', CONVERSATION_FORWARD_RULE],
188
+ ]),
189
+ });
190
+ assert.ok(result);
191
+ assert.equal(result.path, '/v1/chat/completions');
192
+ assert.equal(result.source, 'conversation');
193
+ assert.ok(result.conversation);
194
+ assert.equal(result.conversation.conversationId.length > 0, true);
195
+ assert.equal(result.conversation.modelSlug, 'auto');
196
+ });
197
+ test('buildBuyerForwardRequest: passes conversation prepare requests through when no messages are present', () => {
198
+ const routes = new Map([
199
+ ['conversation.com', new Set(['/profile-api/conversation'])],
200
+ ]);
201
+ const result = buildBuyerForwardRequest({
202
+ host: 'conversation.com',
203
+ path: '/profile-api/conversation/prepare',
204
+ rawBody: Buffer.from(JSON.stringify({ model: 'auto', client_prepare_state: 'none' })),
205
+ isJson: true,
206
+ peerId: 'peer123',
207
+ prefixesByHost: routes,
208
+ forwardRulesByHost: new Map([
209
+ ['conversation.com', CONVERSATION_FORWARD_RULE],
210
+ ]),
211
+ });
212
+ assert.equal(result, null);
213
+ });
214
+ test('transformRequestBody: converts configured nested message body to responses', () => {
215
+ const body = Buffer.from(JSON.stringify({
216
+ input: {
217
+ messages: [
218
+ {
219
+ content: [
220
+ { annotations: [], text: 'hi', type: 'output_text' },
221
+ ],
222
+ id: 'workflow-msg-1',
223
+ role: 'user',
224
+ status: 'completed',
225
+ type: 'message',
226
+ },
227
+ ],
228
+ },
229
+ }));
230
+ const result = transformRequestBody(body, 'peer123', WORKFLOW_FORWARD_RULE.request, {
231
+ defaultModel: 'model-large',
232
+ servedModels: new Set(['model-large']),
233
+ });
234
+ assert.ok(result);
235
+ const parsed = JSON.parse(result.toString('utf8'));
236
+ assert.equal(parsed.model, 'peer123@model-large');
237
+ assert.deepEqual(parsed.input, [{ role: 'user', content: 'hi' }]);
238
+ assert.equal(parsed.stream, true);
239
+ });
240
+ test('buildBuyerForwardRequest: routes workflow requests to responses', () => {
241
+ const routes = new Map([
242
+ ['workflow.example.test', new Set(['/profile-api/workflow'])],
243
+ ]);
244
+ const rawBody = Buffer.from(JSON.stringify({
245
+ input: {
246
+ messages: [
247
+ {
248
+ role: 'user',
249
+ content: [{ text: 'hello workflow', type: 'output_text' }],
250
+ },
251
+ ],
252
+ },
253
+ }));
254
+ const result = buildBuyerForwardRequest({
255
+ host: 'workflow.example.test',
256
+ path: '/profile-api/workflow',
257
+ rawBody,
258
+ isJson: true,
259
+ peerId: 'peer123',
260
+ defaultModel: 'model-default',
261
+ servedModels: new Set(['model-default']),
262
+ prefixesByHost: routes,
263
+ forwardRulesByHost: new Map([
264
+ ['workflow.example.test', WORKFLOW_FORWARD_RULE],
265
+ ]),
266
+ });
267
+ assert.ok(result);
268
+ assert.equal(result.path, '/v1/responses');
269
+ assert.equal(result.source, 'workflow');
270
+ const parsed = JSON.parse(result.body.toString('utf8'));
271
+ assert.equal(parsed.model, 'peer123@model-default');
272
+ assert.deepEqual(parsed.input, [{ role: 'user', content: 'hello workflow' }]);
273
+ assert.equal(parsed.stream, true);
274
+ });
275
+ test('ConversationSseAdapter: converts chat completions SSE into conversation events', () => {
276
+ const adapter = new ConversationSseAdapter({
277
+ conversationId: 'conv-1',
278
+ parentMessageId: 'parent-1',
279
+ userMessageId: 'user-1',
280
+ assistantMessageId: 'assistant-1',
281
+ });
282
+ const input = [
283
+ 'data: {"id":"chatcmpl_1","choices":[{"delta":{"content":"hi"},"finish_reason":null}]}',
284
+ '',
285
+ 'data: {"id":"chatcmpl_1","choices":[{"delta":{"content":" there"},"finish_reason":null}]}',
286
+ '',
287
+ 'data: {"id":"chatcmpl_1","choices":[{"delta":{},"finish_reason":"stop"}]}',
288
+ '',
289
+ 'data: [DONE]',
290
+ '',
291
+ '',
292
+ ].join('\n');
293
+ const output = adapter.feed(Buffer.from(input), false).map((chunk) => chunk.toString('utf8')).join('');
294
+ assert.ok(output.includes('"conversation_id":"conv-1"'));
295
+ assert.ok(output.includes('"id":"assistant-1"'));
296
+ assert.ok(output.includes('"parts":["hi"]'));
297
+ assert.ok(output.includes('"parts":["hi there"]'));
298
+ assert.ok(output.includes('"status":"finished_successfully"'));
299
+ assert.ok(output.includes('"message_type":"next"'));
300
+ assert.ok(output.includes('"parent_id":"parent-1"'));
301
+ assert.ok(output.includes('"is_complete":true'));
302
+ assert.ok(output.includes('"content_references":[]'));
303
+ assert.ok(output.includes('data: [DONE]'));
304
+ });
305
+ test('ConversationSseAdapter: can parent assistant messages to the user message', () => {
306
+ const adapter = new ConversationSseAdapter({
307
+ conversationId: 'conv-1',
308
+ parentMessageId: 'parent-1',
309
+ userMessageId: 'user-1',
310
+ assistantMessageId: 'assistant-1',
311
+ assistantParent: 'user-message',
312
+ });
313
+ const output = adapter
314
+ .feed(Buffer.from('data: {"choices":[{"delta":{"content":"hi"}}]}\n\ndata: [DONE]\n\n'), false)
315
+ .map((chunk) => chunk.toString('utf8'))
316
+ .join('');
317
+ assert.ok(output.includes('"parent_id":"user-1"'));
318
+ assert.equal(output.includes('"parent_id":"parent-1"'), false);
319
+ });
320
+ test('ConversationSseAdapter: preserves configured assistant create time', () => {
321
+ const adapter = new ConversationSseAdapter({
322
+ conversationId: 'conv-1',
323
+ parentMessageId: 'parent-1',
324
+ userMessageId: 'user-1',
325
+ assistantMessageId: 'assistant-1',
326
+ assistantCreateTime: 123.456,
327
+ });
328
+ const output = adapter
329
+ .feed(Buffer.from('data: {"choices":[{"delta":{"content":"hi"}}]}\n\ndata: [DONE]\n\n'), false)
330
+ .map((chunk) => chunk.toString('utf8'))
331
+ .join('');
332
+ assert.ok(output.includes('"create_time":123.456'));
333
+ });
334
+ test('ConversationSseAdapter: converts non-streaming chat completions JSON into conversation events', () => {
335
+ const adapter = new ConversationSseAdapter({
336
+ conversationId: 'conv-1',
337
+ parentMessageId: 'parent-1',
338
+ userMessageId: 'user-1',
339
+ assistantMessageId: 'assistant-1',
340
+ });
341
+ const body = Buffer.from(JSON.stringify({
342
+ choices: [{ message: { role: 'assistant', content: 'hello' }, finish_reason: 'stop' }],
343
+ }));
344
+ const output = adapter.fromJsonResponse(body).map((chunk) => chunk.toString('utf8')).join('');
345
+ assert.ok(output.includes('"parts":["hello"]'));
346
+ assert.ok(output.includes('"status":"finished_successfully"'));
347
+ assert.ok(output.includes('data: [DONE]'));
348
+ });
349
+ test('ResponsesSseNormalizer: removes duplicate terminal text payloads', () => {
350
+ const normalizer = new ResponsesSseNormalizer({
351
+ kind: 'responses-sse',
352
+ injectResponseId: true,
353
+ stripDataType: true,
354
+ stripItemPhase: true,
355
+ emptyContentPartDoneText: true,
356
+ emptyOutputItemDoneContent: true,
357
+ messageOutputIndex: 1,
358
+ messageItemId: 'agent-node:llm:0',
359
+ });
360
+ const input = [
361
+ 'event: response.created',
362
+ 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_1","status":"in_progress"}}',
363
+ '',
364
+ 'event: response.output_item.added',
365
+ 'data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"type":"message","id":"msg_1","role":"assistant","status":"in_progress","content":[],"phase":"final_answer"}}',
366
+ '',
367
+ 'event: response.output_text.done',
368
+ 'data: {"type":"response.output_text.done","sequence_number":2,"output_index":0,"item_id":"msg_1","text":"Hi"}',
369
+ '',
370
+ 'event: response.content_part.done',
371
+ 'data: {"type":"response.content_part.done","sequence_number":3,"output_index":0,"item_id":"msg_1","part":{"type":"output_text","text":"Hi","annotations":[]}}',
372
+ '',
373
+ 'event: response.output_item.done',
374
+ 'data: {"type":"response.output_item.done","sequence_number":4,"output_index":0,"item":{"type":"message","id":"msg_1","content":[{"type":"output_text","text":"Hi"}],"phase":"final_answer"}}',
375
+ '',
376
+ '',
377
+ ].join('\n');
378
+ const output = normalizer.feed(Buffer.from(input), true).map((chunk) => chunk.toString('utf8')).join('');
379
+ assert.equal(output.includes('"type":"response.output_text.done"'), false);
380
+ assert.ok(output.includes('"response_id":"resp_1"'));
381
+ assert.ok(output.includes('"text":"Hi"'));
382
+ assert.ok(output.includes('"item":{"type":"message","id":"agent-node:llm:0","role":"assistant","status":"in_progress","content":[]}'));
383
+ assert.ok(output.includes('"item_id":"agent-node:llm:0"'));
384
+ assert.ok(output.includes('"output_index":1'));
385
+ assert.equal(output.includes('"phase":"final_answer"'), false);
386
+ assert.ok(output.includes('"part":{"type":"output_text","text":"","annotations":[]}'));
387
+ assert.ok(output.includes('"item":{"type":"message","id":"agent-node:llm:0","content":[]}'));
388
+ });
389
+ // ── Integration tests ─────────────────────────────────────────────────────────
390
+ // Tests connect directly to the inner TLS server (innerTlsPort), bypassing the
391
+ // CONNECT tunnel. This keeps tests reliable while covering the critical
392
+ // handleDecrypted logic: body rewriting, header stripping, response forwarding.
393
+ async function startFakeBuyerProxy() {
394
+ const captured = [];
395
+ const server = http.createServer((req, res) => {
396
+ const chunks = [];
397
+ req.on('data', (c) => chunks.push(c));
398
+ req.on('end', () => {
399
+ captured.push({
400
+ method: req.method ?? 'GET',
401
+ path: req.url ?? '/',
402
+ headers: req.headers,
403
+ body: Buffer.concat(chunks),
404
+ });
405
+ if (req.url === '/v1/responses') {
406
+ const responseBody = [
407
+ 'event: response.created',
408
+ 'data: {"type":"response.created","sequence_number":0,"response":{"id":"resp_1","object":"response","status":"in_progress"}}',
409
+ '',
410
+ 'event: response.output_item.added',
411
+ 'data: {"type":"response.output_item.added","sequence_number":1,"output_index":0,"item":{"type":"message","id":"msg_1","role":"assistant","status":"in_progress","content":[],"phase":"final_answer"}}',
412
+ '',
413
+ 'event: response.content_part.added',
414
+ 'data: {"type":"response.content_part.added","sequence_number":2,"output_index":0,"item_id":"msg_1","content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}',
415
+ '',
416
+ 'event: response.output_text.delta',
417
+ 'data: {"type":"response.output_text.delta","sequence_number":3,"output_index":0,"item_id":"msg_1","content_index":0,"delta":"Hi"}',
418
+ '',
419
+ 'event: response.output_text.done',
420
+ 'data: {"type":"response.output_text.done","sequence_number":4,"output_index":0,"item_id":"msg_1","content_index":0,"text":"Hi"}',
421
+ '',
422
+ 'event: response.content_part.done',
423
+ 'data: {"type":"response.content_part.done","sequence_number":5,"output_index":0,"item_id":"msg_1","content_index":0,"part":{"type":"output_text","text":"Hi","annotations":[]}}',
424
+ '',
425
+ 'event: response.output_item.done',
426
+ 'data: {"type":"response.output_item.done","sequence_number":6,"output_index":0,"item":{"type":"message","id":"msg_1","role":"assistant","status":"completed","content":[{"type":"output_text","text":"Hi","annotations":[]}],"phase":"final_answer"}}',
427
+ '',
428
+ 'data: [DONE]',
429
+ '',
430
+ '',
431
+ ].join('\n');
432
+ res.writeHead(200, {
433
+ 'content-type': 'text/event-stream',
434
+ 'content-length': String(Buffer.byteLength(responseBody)),
435
+ 'connection': 'close',
436
+ });
437
+ res.end(responseBody);
438
+ return;
439
+ }
440
+ res.writeHead(200, { 'content-type': 'application/json', 'connection': 'close' });
441
+ res.end(JSON.stringify({ ok: true }));
442
+ });
443
+ });
444
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
445
+ const port = server.address().port;
446
+ return {
447
+ port,
448
+ captured,
449
+ close: () => new Promise((resolve) => server.close(() => resolve())),
450
+ };
451
+ }
452
+ // Send one HTTP/1.1 request over a TLS connection directly to the inner server.
453
+ function tlsHttpRequest(opts) {
454
+ return new Promise((resolve, reject) => {
455
+ const socket = tls.connect({ host: opts.host, port: opts.port, servername: opts.servername, ca: opts.caCert, rejectUnauthorized: true }, () => {
456
+ const headerPairs = [
457
+ `${opts.method} ${opts.path} HTTP/1.1`,
458
+ `Host: ${opts.servername}`,
459
+ `Connection: close`,
460
+ ...Object.entries(opts.headers).map(([k, v]) => `${k}: ${v}`),
461
+ `Content-Length: ${opts.body.length}`,
462
+ '',
463
+ ];
464
+ socket.write(headerPairs.join('\r\n') + '\r\n');
465
+ if (opts.body.length > 0)
466
+ socket.write(opts.body);
467
+ });
468
+ const chunks = [];
469
+ socket.on('data', (c) => chunks.push(c));
470
+ socket.on('end', () => {
471
+ const raw = Buffer.concat(chunks).toString('utf8');
472
+ const bodyStart = raw.indexOf('\r\n\r\n');
473
+ const headerLines = (bodyStart >= 0 ? raw.slice(0, bodyStart) : raw).split('\r\n');
474
+ const statusLine = headerLines[0] ?? '';
475
+ const statusMatch = /HTTP\/\d+\.?\d* (\d+)/.exec(statusLine);
476
+ const headers = {};
477
+ for (const line of headerLines.slice(1)) {
478
+ const separator = line.indexOf(':');
479
+ if (separator <= 0)
480
+ continue;
481
+ headers[line.slice(0, separator).trim().toLowerCase()] = line.slice(separator + 1).trim();
482
+ }
483
+ resolve({
484
+ statusCode: statusMatch ? parseInt(statusMatch[1], 10) : 0,
485
+ headers,
486
+ body: bodyStart >= 0 ? Buffer.from(raw.slice(bodyStart + 4)) : Buffer.alloc(0),
487
+ });
488
+ });
489
+ socket.on('error', reject);
490
+ setTimeout(() => { socket.destroy(); reject(new Error('TLS request timed out')); }, 8_000);
491
+ });
492
+ }
493
+ async function makeProxy(tmpDir, buyerPort) {
494
+ const caManager = new CAManager(tmpDir);
495
+ const caKeys = await caManager.generate();
496
+ const certCache = new CertCache(caKeys);
497
+ const proxy = new SystemProxyServer({
498
+ port: 0,
499
+ buyerProxyPort: buyerPort,
500
+ certCache,
501
+ proxiedDomains: new Set(['api-b.example.test', 'api-a.example.test', 'conversation.com', 'workflow.example.test']),
502
+ proxiedPathPrefixes: new Map([
503
+ ['api-b.example.test', new Set(['/v1/audio/transcriptions', '/v1/chat/completions'])],
504
+ ['api-a.example.test', new Set(['/v1/messages'])],
505
+ ['conversation.com', new Set(['/profile-api/conversation', '/profile-api/conversation/prepare'])],
506
+ ['workflow.example.test', new Set(['/profile-api/workflow'])],
507
+ ]),
508
+ systemProxyForwardRules: new Map([
509
+ ['conversation.com', CONVERSATION_FORWARD_RULE],
510
+ ['workflow.example.test', WORKFLOW_FORWARD_RULE],
511
+ ]),
512
+ defaultPeerId: 'testPeer',
513
+ defaultModel: 'model-default',
514
+ servedModels: new Set(['model-default']),
515
+ });
516
+ await proxy.start();
517
+ return { proxy, caKeys };
518
+ }
519
+ test('Proxy: rewrites model field and strips Authorization header', { timeout: 15_000 }, async () => {
520
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
521
+ try {
522
+ const buyer = await startFakeBuyerProxy();
523
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
524
+ try {
525
+ const requestBody = Buffer.from(JSON.stringify({ model: 'model-default', messages: [{ role: 'user', content: 'hi' }] }));
526
+ const result = await tlsHttpRequest({
527
+ host: '127.0.0.1',
528
+ port: proxy.innerTlsPort,
529
+ servername: 'api-b.example.test',
530
+ caCert: caKeys.certPem,
531
+ method: 'POST',
532
+ path: '/v1/chat/completions',
533
+ headers: {
534
+ 'content-type': 'application/json',
535
+ 'authorization': 'Bearer sk-secret-key',
536
+ },
537
+ body: requestBody,
538
+ });
539
+ assert.equal(result.statusCode, 200, 'should get 200 from buyer proxy');
540
+ assert.equal(buyer.captured.length, 1, 'buyer proxy should receive exactly one request');
541
+ const captured = buyer.captured[0];
542
+ const capturedBody = JSON.parse(captured.body.toString('utf8'));
543
+ assert.equal(capturedBody.model, 'testPeer@model-default', 'model should be rewritten with peerId prefix');
544
+ assert.equal(captured.headers['authorization'], undefined, 'Authorization header should be stripped');
545
+ }
546
+ finally {
547
+ await proxy.stop();
548
+ await buyer.close();
549
+ }
550
+ }
551
+ finally {
552
+ await rm(tmpDir, { recursive: true, force: true });
553
+ }
554
+ });
555
+ test('Proxy: passes non-JSON body through without modification', { timeout: 15_000 }, async () => {
556
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
557
+ try {
558
+ const buyer = await startFakeBuyerProxy();
559
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
560
+ try {
561
+ const rawBody = Buffer.from('raw binary data');
562
+ await tlsHttpRequest({
563
+ host: '127.0.0.1',
564
+ port: proxy.innerTlsPort,
565
+ servername: 'api-b.example.test',
566
+ caCert: caKeys.certPem,
567
+ method: 'POST',
568
+ path: '/v1/audio/transcriptions',
569
+ headers: { 'content-type': 'application/octet-stream' },
570
+ body: rawBody,
571
+ });
572
+ assert.equal(buyer.captured.length, 1);
573
+ assert.deepEqual(buyer.captured[0].body, rawBody);
574
+ }
575
+ finally {
576
+ await proxy.stop();
577
+ await buyer.close();
578
+ }
579
+ }
580
+ finally {
581
+ await rm(tmpDir, { recursive: true, force: true });
582
+ }
583
+ });
584
+ test('Proxy: forwards correct path to buyer proxy', { timeout: 15_000 }, async () => {
585
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
586
+ try {
587
+ const buyer = await startFakeBuyerProxy();
588
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
589
+ try {
590
+ await tlsHttpRequest({
591
+ host: '127.0.0.1',
592
+ port: proxy.innerTlsPort,
593
+ servername: 'api-a.example.test',
594
+ caCert: caKeys.certPem,
595
+ method: 'POST',
596
+ path: '/v1/messages',
597
+ headers: { 'content-type': 'application/json', 'profile-version': '2023-06-01' },
598
+ body: Buffer.from(JSON.stringify({ model: 'model-legacy', messages: [] })),
599
+ });
600
+ assert.equal(buyer.captured[0]?.path, '/v1/messages', 'path should be preserved');
601
+ assert.equal(buyer.captured[0]?.headers['profile-version'], '2023-06-01', 'non-hop-by-hop headers preserved');
602
+ }
603
+ finally {
604
+ await proxy.stop();
605
+ await buyer.close();
606
+ }
607
+ }
608
+ finally {
609
+ await rm(tmpDir, { recursive: true, force: true });
610
+ }
611
+ });
612
+ test('Proxy: strips oversized conversation browser headers before forwarding', { timeout: 15_000 }, async () => {
613
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
614
+ try {
615
+ const buyer = await startFakeBuyerProxy();
616
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
617
+ try {
618
+ const requestBody = Buffer.from(JSON.stringify({
619
+ action: 'next',
620
+ model: 'auto',
621
+ messages: [
622
+ {
623
+ id: 'msg-1',
624
+ author: { role: 'user' },
625
+ content: { content_type: 'text', parts: ['hi'] },
626
+ },
627
+ ],
628
+ parent_message_id: 'client-created-root',
629
+ }));
630
+ const hugeHeader = 'x'.repeat(48 * 1024);
631
+ const result = await tlsHttpRequest({
632
+ host: '127.0.0.1',
633
+ port: proxy.innerTlsPort,
634
+ servername: 'conversation.com',
635
+ caCert: caKeys.certPem,
636
+ method: 'POST',
637
+ path: '/profile-api/conversation',
638
+ headers: {
639
+ 'accept': 'text/event-stream',
640
+ 'content-type': 'application/json',
641
+ 'authorization': `Bearer ${hugeHeader}`,
642
+ 'cookie': `session=${hugeHeader}`,
643
+ 'x-large-profile-token': hugeHeader,
644
+ 'x-profile-route-token': hugeHeader,
645
+ },
646
+ body: requestBody,
647
+ });
648
+ assert.equal(result.statusCode, 200, 'should get 200 from buyer proxy');
649
+ assert.equal(buyer.captured.length, 1, 'buyer proxy should receive exactly one request');
650
+ const captured = buyer.captured[0];
651
+ assert.equal(captured.path, '/v1/chat/completions');
652
+ assert.equal(captured.headers['accept'], 'text/event-stream');
653
+ assert.equal(captured.headers['content-type'], 'application/json');
654
+ assert.equal(captured.headers['authorization'], undefined);
655
+ assert.equal(captured.headers['cookie'], undefined);
656
+ assert.equal(captured.headers['x-large-profile-token'], undefined);
657
+ assert.equal(captured.headers['x-profile-route-token'], undefined);
658
+ const capturedBody = JSON.parse(captured.body.toString('utf8'));
659
+ assert.equal(capturedBody.model, 'testPeer@model-default');
660
+ assert.deepEqual(capturedBody.messages, [{ role: 'user', content: 'hi' }]);
661
+ assert.equal(capturedBody.stream, true);
662
+ }
663
+ finally {
664
+ await proxy.stop();
665
+ await buyer.close();
666
+ }
667
+ }
668
+ finally {
669
+ await rm(tmpDir, { recursive: true, force: true });
670
+ }
671
+ });
672
+ test('Proxy: routes workflow requests to responses and strips browser headers', { timeout: 15_000 }, async () => {
673
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
674
+ try {
675
+ const buyer = await startFakeBuyerProxy();
676
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
677
+ try {
678
+ const requestBody = Buffer.from(JSON.stringify({
679
+ input: {
680
+ messages: [
681
+ {
682
+ id: 'workflow-msg-1',
683
+ role: 'user',
684
+ status: 'completed',
685
+ type: 'message',
686
+ content: [{ annotations: [], text: 'hi', type: 'output_text' }],
687
+ },
688
+ ],
689
+ },
690
+ }));
691
+ const hugeHeader = 'x'.repeat(48 * 1024);
692
+ const result = await tlsHttpRequest({
693
+ host: '127.0.0.1',
694
+ port: proxy.innerTlsPort,
695
+ servername: 'workflow.example.test',
696
+ caCert: caKeys.certPem,
697
+ method: 'POST',
698
+ path: '/profile-api/workflow',
699
+ headers: {
700
+ 'accept': 'text/event-stream',
701
+ 'content-type': 'application/json',
702
+ 'origin': 'https://app.example.test',
703
+ 'cookie': `session=${hugeHeader}`,
704
+ 'x-workflow-distinct-id': hugeHeader,
705
+ },
706
+ body: requestBody,
707
+ });
708
+ assert.equal(result.statusCode, 200, 'should get 200 from buyer proxy');
709
+ assert.equal(result.headers['access-control-allow-origin'], 'https://app.example.test');
710
+ assert.equal(result.headers['access-control-allow-credentials'], 'true');
711
+ assert.equal(result.headers['vary'], 'Origin');
712
+ const responseText = result.body.toString('utf8');
713
+ assert.equal(responseText.includes('"type":"response.output_text.delta"'), false);
714
+ assert.ok(responseText.includes('"response_id":"resp_1"'));
715
+ assert.ok(responseText.includes('"item_id":"agent-node:llm:0"'));
716
+ assert.ok(responseText.includes('"output_index":1'));
717
+ assert.equal(responseText.includes('"phase":"final_answer"'), false);
718
+ assert.ok(responseText.includes('"part":{"type":"output_text","text":"","annotations":[]}'));
719
+ assert.ok(responseText.includes('"item":{"type":"message","id":"agent-node:llm:0","role":"assistant","status":"completed","content":[]}'));
720
+ assert.equal(buyer.captured.length, 1, 'buyer proxy should receive exactly one request');
721
+ const captured = buyer.captured[0];
722
+ assert.equal(captured.path, '/v1/responses');
723
+ assert.equal(captured.headers['accept'], 'text/event-stream');
724
+ assert.equal(captured.headers['content-type'], 'application/json');
725
+ assert.equal(captured.headers['cookie'], undefined);
726
+ assert.equal(captured.headers['x-workflow-distinct-id'], undefined);
727
+ const capturedBody = JSON.parse(captured.body.toString('utf8'));
728
+ assert.equal(capturedBody.model, 'testPeer@model-default');
729
+ assert.deepEqual(capturedBody.input, [{ role: 'user', content: 'hi' }]);
730
+ assert.equal(capturedBody.stream, true);
731
+ }
732
+ finally {
733
+ await proxy.stop();
734
+ await buyer.close();
735
+ }
736
+ }
737
+ finally {
738
+ await rm(tmpDir, { recursive: true, force: true });
739
+ }
740
+ });
741
+ test('Proxy: handles profile CORS preflight without forwarding', { timeout: 15_000 }, async () => {
742
+ const tmpDir = await mkdtemp(join(tmpdir(), 'antseed-proxy-test-'));
743
+ try {
744
+ const buyer = await startFakeBuyerProxy();
745
+ const { proxy, caKeys } = await makeProxy(tmpDir, buyer.port);
746
+ try {
747
+ const result = await tlsHttpRequest({
748
+ host: '127.0.0.1',
749
+ port: proxy.innerTlsPort,
750
+ servername: 'workflow.example.test',
751
+ caCert: caKeys.certPem,
752
+ method: 'OPTIONS',
753
+ path: '/profile-api/workflow?agentModelId=kimi-k2-5',
754
+ headers: {
755
+ 'origin': 'https://app.example.test',
756
+ 'access-control-request-method': 'POST',
757
+ 'access-control-request-headers': 'content-type,x-workflow-version,x-workflow-distinct-id',
758
+ },
759
+ body: Buffer.alloc(0),
760
+ });
761
+ assert.equal(result.statusCode, 204);
762
+ assert.equal(result.headers['access-control-allow-origin'], 'https://app.example.test');
763
+ assert.equal(result.headers['access-control-allow-credentials'], 'true');
764
+ assert.equal(result.headers['access-control-allow-methods'], 'GET, POST, OPTIONS');
765
+ assert.equal(result.headers['access-control-allow-headers'], 'content-type,x-workflow-version,x-workflow-distinct-id');
766
+ assert.equal(result.headers['access-control-max-age'], '600');
767
+ assert.equal(buyer.captured.length, 0, 'preflight should not hit buyer proxy');
768
+ }
769
+ finally {
770
+ await proxy.stop();
771
+ await buyer.close();
772
+ }
773
+ }
774
+ finally {
775
+ await rm(tmpDir, { recursive: true, force: true });
776
+ }
777
+ });
778
+ //# sourceMappingURL=proxy-server.test.js.map