@lazyingart/agent-web 0.1.40

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 (42) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +438 -0
  3. package/docs/architecture.md +503 -0
  4. package/package.json +43 -0
  5. package/src/aginti-adapter.js +602 -0
  6. package/src/chat-context.js +1020 -0
  7. package/src/chat-migrations.js +947 -0
  8. package/src/chat-store.js +3308 -0
  9. package/src/cli.js +134 -0
  10. package/src/cloud-server.js +2043 -0
  11. package/src/contracts.js +103 -0
  12. package/src/deterministic-context-summarizer.js +254 -0
  13. package/src/direct-chat-capability-limits.js +66 -0
  14. package/src/direct-chat-contract.js +3 -0
  15. package/src/errors.js +50 -0
  16. package/src/http-contract.js +592 -0
  17. package/src/index.js +88 -0
  18. package/src/localllm-connector.js +667 -0
  19. package/src/migrations.js +231 -0
  20. package/src/operator-health.js +184 -0
  21. package/src/password-verifier.js +131 -0
  22. package/src/service-config.js +547 -0
  23. package/src/service.js +408 -0
  24. package/src/sqlite-health.js +83 -0
  25. package/src/storage-path.js +130 -0
  26. package/src/store.js +914 -0
  27. package/src/validation.js +181 -0
  28. package/src/vision-attachment.js +404 -0
  29. package/src/web/aginti-client.js +552 -0
  30. package/src/web/aginti-protocol.js +1146 -0
  31. package/src/web/asset-map.js +462 -0
  32. package/src/web/browser-app.js +6491 -0
  33. package/src/web/cloud-session-client.js +427 -0
  34. package/src/web/direct-chat-client.js +1482 -0
  35. package/src/web/index.js +10 -0
  36. package/src/web/presentation-state.js +107 -0
  37. package/src/web/pwa-assets.js +854 -0
  38. package/src/web/pwa-update-handoff-store.js +179 -0
  39. package/src/web/safe-rendering.js +836 -0
  40. package/src/web/vision-image-client.js +546 -0
  41. package/src/web/vision-image-sanitizer.js +168 -0
  42. package/src/web/web-release.js +28 -0
@@ -0,0 +1,2043 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { createServer as createNodeServer } from 'node:http';
3
+ import { isIP } from 'node:net';
4
+
5
+ import {
6
+ AGENT_ROUTE_MAP,
7
+ CLOUD_HTTP_LIMITS,
8
+ CLOUD_ROUTES,
9
+ CLIENT_RELEASE_HEADER_NAME,
10
+ CSRF_COOKIE_NAME,
11
+ CSRF_HEADER_NAME,
12
+ IDEMPOTENCY_HEADER_NAME,
13
+ SESSION_COOKIE_NAME,
14
+ TRUSTED_CLIENT_ADDRESS_HEADER,
15
+ TRUSTED_PUBLIC_AUTHORITY_HEADER,
16
+ CloudHttpError,
17
+ bodyLimitForRoute,
18
+ classifyRequestTarget,
19
+ routeRequiresIdempotency,
20
+ snapshotAndValidateAssetMap,
21
+ validateAccountConfig,
22
+ validateAgentIdempotencyKey,
23
+ validateChatRequest,
24
+ validateEmptyBody,
25
+ validateLoginBody,
26
+ validatePublicOrigin,
27
+ validateRequestIdempotencyKey,
28
+ validateTransportAgentRequest
29
+ } from './http-contract.js';
30
+ import { ControlPlaneError } from './errors.js';
31
+ import { VISION_MODEL_ALIAS } from './vision-attachment.js';
32
+ import {
33
+ AGINTI_MAX_FILE_ARTIFACT_BYTES,
34
+ AGINTI_RPC_PATHS,
35
+ FAIL_CLOSED_AGENT_CAPABILITIES,
36
+ validateFileSpec,
37
+ validateAgentResponse,
38
+ validateEventEnvelope
39
+ } from './web/aginti-protocol.js';
40
+
41
+ const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
42
+ const IMMUTABLE_CACHE_CONTROL = 'public, max-age=31536000, immutable';
43
+ const DYNAMIC_CACHE_CONTROL = 'no-store';
44
+ const SESSION_MAX_AGE_SECONDS = 12 * 60 * 60;
45
+ const REMEMBERED_SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
46
+ const EPHEMERAL_SESSION_MAX_AGE_SECONDS = 60;
47
+ const GLOBAL_DISPATCH_RETRY_MS = 250;
48
+ const REQUEST_BODY_TIMEOUT_MARGIN_MS = 30_000;
49
+ const RESPONSE_RELEASE_ID = Symbol('responseReleaseId');
50
+ const SAFE_FAILURE_CODES = new Set([
51
+ 'provider_unavailable',
52
+ 'timeout',
53
+ 'internal_error',
54
+ 'response_limit',
55
+ 'content_rejected'
56
+ ]);
57
+ const PUBLIC_THREAD_FIELDS = Object.freeze([
58
+ 'threadId', 'title', 'modelAlias', 'revision', 'ledgerHash', 'messageCount',
59
+ 'ledgerBytes', 'currentGenerationId', 'createdAt', 'updatedAt'
60
+ ]);
61
+ const PUBLIC_MESSAGE_FIELDS = Object.freeze([
62
+ 'threadId', 'messageId', 'revision', 'role', 'content', 'contentBytes',
63
+ 'previousHash', 'messageHash', 'generationId', 'createdAt'
64
+ ]);
65
+ const PUBLIC_GENERATION_FIELDS = Object.freeze([
66
+ 'threadId', 'generationId', 'assistantMessageId', 'status', 'terminal',
67
+ 'modelAlias', 'sourceRevision', 'sourceHash', 'deltaCount', 'deltaBytes',
68
+ 'lastDeltaHash', 'finalRevision', 'finalHash', 'failureCode', 'deltasPruned',
69
+ 'startedAt', 'updatedAt', 'terminalAt', 'prunedAt'
70
+ ]);
71
+ const PUBLIC_DELTA_FIELDS = Object.freeze([
72
+ 'threadId', 'generationId', 'sequence', 'content', 'contentBytes',
73
+ 'previousHash', 'deltaHash', 'createdAt'
74
+ ]);
75
+
76
+ function epochMilliseconds(clock) {
77
+ const value = clock();
78
+ const date = value instanceof Date ? value : new Date(value);
79
+ const result = date.getTime();
80
+ if (!Number.isFinite(result)) throw new TypeError('clock returned an invalid time');
81
+ return result;
82
+ }
83
+
84
+ function exactLimitOverrides(input = {}) {
85
+ if (input === null || typeof input !== 'object' || Array.isArray(input)
86
+ || Object.getPrototypeOf(input) !== Object.prototype) {
87
+ throw new TypeError('limits must be a plain object');
88
+ }
89
+ const bounds = Object.freeze({
90
+ bodyTimeoutMs: [50, 15_000],
91
+ visionBodyTimeoutMs: [1_000, 300_000],
92
+ dependencyTimeoutMs: [50, 120_000],
93
+ jobTimeoutMs: [50, 600_000],
94
+ visionJobTimeoutMs: [50, 900_000],
95
+ sseLifetimeMs: [100, 120_000],
96
+ ssePollMs: [5, 5_000],
97
+ concurrentBodies: [1, 256],
98
+ concurrentBodiesPerSource: [1, 16],
99
+ concurrentLogins: [1, 32],
100
+ concurrentLoginsPerSource: [1, 4],
101
+ concurrentStreams: [1, 64],
102
+ concurrentStreamsPerSession: [1, 8],
103
+ loginAttemptsPerMinute: [1, 60],
104
+ directChatJobs: [1, 32]
105
+ });
106
+ const result = { ...CLOUD_HTTP_LIMITS };
107
+ for (const [name, value] of Object.entries(input)) {
108
+ if (!Object.hasOwn(bounds, name)) throw new TypeError(`unsupported HTTP limit ${name}`);
109
+ const [minimum, maximum] = bounds[name];
110
+ if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
111
+ throw new TypeError(`${name} is outside its safe range`);
112
+ }
113
+ result[name] = value;
114
+ }
115
+ if (result.concurrentBodiesPerSource > result.concurrentBodies
116
+ || result.concurrentLoginsPerSource > result.concurrentLogins
117
+ || result.concurrentStreamsPerSession > result.concurrentStreams) {
118
+ throw new TypeError('per-source concurrency cannot exceed total concurrency');
119
+ }
120
+ return Object.freeze(result);
121
+ }
122
+
123
+ function requireMethods(value, name, methods) {
124
+ if (!value || methods.some((method) => typeof value[method] !== 'function')) {
125
+ throw new TypeError(`${name} must provide ${methods.join('(), ')}()`);
126
+ }
127
+ return value;
128
+ }
129
+
130
+ function validatePasswordVerifier(value) {
131
+ if (!value || value.algorithm !== 'scrypt' || typeof value.verify !== 'function') {
132
+ throw new TypeError('passwordVerifier must be an injected scrypt verifier');
133
+ }
134
+ return value;
135
+ }
136
+
137
+ function sha256(value) {
138
+ return createHash('sha256').update(value, 'utf8').digest('hex');
139
+ }
140
+
141
+ function derivedIdempotencyKey(label, ...parts) {
142
+ return `${label}.${sha256(parts.join('\u0000'))}`;
143
+ }
144
+
145
+ function safeEqual(first, second) {
146
+ if (typeof first !== 'string' || typeof second !== 'string') return false;
147
+ const firstBytes = Buffer.from(first, 'utf8');
148
+ const secondBytes = Buffer.from(second, 'utf8');
149
+ const maximum = Math.max(firstBytes.byteLength, secondBytes.byteLength, 1);
150
+ const left = Buffer.alloc(maximum);
151
+ const right = Buffer.alloc(maximum);
152
+ firstBytes.copy(left);
153
+ secondBytes.copy(right);
154
+ return timingSafeEqual(left, right) && firstBytes.byteLength === secondBytes.byteLength;
155
+ }
156
+
157
+ function opaqueToken() {
158
+ return randomBytes(32).toString('base64url');
159
+ }
160
+
161
+ function commonSecurityHeaders() {
162
+ return {
163
+ 'x-content-type-options': 'nosniff',
164
+ 'referrer-policy': 'same-origin',
165
+ 'x-frame-options': 'DENY',
166
+ 'permissions-policy': 'camera=(), microphone=(), geolocation=(), payment=(), usb=()',
167
+ 'strict-transport-security': 'max-age=31536000; includeSubDomains',
168
+ 'cross-origin-resource-policy': 'same-origin',
169
+ 'cross-origin-opener-policy': 'same-origin'
170
+ };
171
+ }
172
+
173
+ function dynamicHeaders(extra = {}, releaseId) {
174
+ return {
175
+ ...commonSecurityHeaders(),
176
+ 'cache-control': DYNAMIC_CACHE_CONTROL,
177
+ pragma: 'no-cache',
178
+ expires: '0',
179
+ ...(releaseId === undefined ? {} : { [CLIENT_RELEASE_HEADER_NAME]: releaseId }),
180
+ ...extra
181
+ };
182
+ }
183
+
184
+ function writeHead(res, status, headers) {
185
+ if (res.headersSent) return;
186
+ res.writeHead(status, headers);
187
+ }
188
+
189
+ function sendBuffer(req, res, status, body, headers) {
190
+ const payload = Buffer.isBuffer(body) ? body : Buffer.from(body);
191
+ writeHead(res, status, { ...headers, 'content-length': String(payload.byteLength) });
192
+ res.end(req.method === 'HEAD' ? undefined : payload);
193
+ }
194
+
195
+ function encodeJson(value, maximum = CLOUD_HTTP_LIMITS.responseJsonBytes) {
196
+ const body = Buffer.from(`${JSON.stringify(value)}\n`, 'utf8');
197
+ if (body.byteLength > maximum) throw new CloudHttpError(502, 'response_too_large', 'An upstream response exceeded its public limit.');
198
+ return body;
199
+ }
200
+
201
+ function sendJson(req, res, status, value, extraHeaders = {}) {
202
+ sendBuffer(req, res, status, encodeJson(value), dynamicHeaders({
203
+ 'content-type': JSON_CONTENT_TYPE,
204
+ ...extraHeaders
205
+ }, res[RESPONSE_RELEASE_ID]));
206
+ }
207
+
208
+ function publicError(error) {
209
+ if (error instanceof CloudHttpError) return error;
210
+ if (error instanceof ControlPlaneError) {
211
+ const mapping = {
212
+ invalid_input: [400, 'invalid_request'],
213
+ not_found: [404, 'not_found'],
214
+ conflict: [409, 'conflict'],
215
+ idempotency_conflict: [409, 'idempotency_conflict'],
216
+ storage_security_error: [503, 'storage_unavailable'],
217
+ storage_corruption: [503, 'storage_unavailable'],
218
+ unsupported_schema: [503, 'storage_unavailable']
219
+ };
220
+ const [status, code] = mapping[error.code] ?? [500, 'internal_error'];
221
+ return new CloudHttpError(status, code, status >= 500 ? 'The service is temporarily unavailable.' : 'The request could not be completed.');
222
+ }
223
+ if (typeof error?.code === 'string' && error.code.startsWith('AGINTI_')) {
224
+ const status = Number(error.statusCode);
225
+ return new CloudHttpError(
226
+ [400, 401, 403, 404, 409, 429, 502, 503, 504].includes(status) ? status : 503,
227
+ status === 429 ? 'agent_rate_limited' : 'agent_unavailable',
228
+ status < 500 ? 'The Agent request was not accepted.' : 'AgInTi Agent is temporarily unavailable.'
229
+ );
230
+ }
231
+ if (error?.name === 'AbortError') return new CloudHttpError(499, 'request_cancelled', 'The request was cancelled.');
232
+ if (error?.name === 'TimeoutError') return new CloudHttpError(504, 'dependency_timeout', 'A required service timed out.');
233
+ return new CloudHttpError(500, 'internal_error', 'The service could not complete the request.');
234
+ }
235
+
236
+ function sendError(req, res, error) {
237
+ const safe = publicError(error);
238
+ if (res.headersSent) {
239
+ if (!res.writableEnded) res.end();
240
+ return;
241
+ }
242
+ const headers = {
243
+ ...(safe.retryAfter === undefined ? {} : { 'retry-after': String(safe.retryAfter) }),
244
+ ...([408, 413].includes(safe.status) || safe.code === 'unexpected_body' || req.shouldKeepAlive === false
245
+ ? { connection: 'close' }
246
+ : {})
247
+ };
248
+ sendJson(req, res, safe.status === 499 ? 400 : safe.status, {
249
+ error: { code: safe.code, message: safe.message }
250
+ }, headers);
251
+ }
252
+
253
+ function methodNotAllowed(req, res, allow) {
254
+ sendJson(req, res, 405, { error: { code: 'method_not_allowed', message: 'The request method is not allowed.' } }, { allow });
255
+ }
256
+
257
+ function rawHeaderValues(req, requestedName) {
258
+ const name = requestedName.toLowerCase();
259
+ const values = [];
260
+ if (Array.isArray(req.rawHeaders)) {
261
+ for (let index = 0; index + 1 < req.rawHeaders.length; index += 2) {
262
+ if (String(req.rawHeaders[index]).toLowerCase() === name) values.push(req.rawHeaders[index + 1]);
263
+ }
264
+ return values;
265
+ }
266
+ const value = req.headers?.[name];
267
+ if (Array.isArray(value)) return value;
268
+ return value === undefined ? [] : [value];
269
+ }
270
+
271
+ function isLoopbackPeer(peer) {
272
+ return peer === '127.0.0.1' || peer === '::1' || peer === '::ffff:127.0.0.1';
273
+ }
274
+
275
+ function requirePublicAuthority(req, publicHost) {
276
+ const hostValues = rawHeaderValues(req, 'host');
277
+ const asserted = rawHeaderValues(req, TRUSTED_PUBLIC_AUTHORITY_HEADER);
278
+ const peer = req.socket?.remoteAddress;
279
+ if (hostValues.length !== 1 || hostValues[0] !== publicHost) {
280
+ throw new CloudHttpError(421, 'misdirected_request', 'The request authority is not accepted.');
281
+ }
282
+ if (isLoopbackPeer(peer)) {
283
+ if (asserted.length !== 1 || asserted[0] !== publicHost) {
284
+ throw new CloudHttpError(421, 'misdirected_request', 'The trusted proxy authority assertion is not accepted.');
285
+ }
286
+ } else if (asserted.length !== 0) {
287
+ throw new CloudHttpError(421, 'misdirected_request', 'A trusted proxy authority assertion was received from an untrusted peer.');
288
+ }
289
+ }
290
+
291
+ export function resolveTrustedClientAddress(req) {
292
+ const peer = req.socket?.remoteAddress;
293
+ const loopback = isLoopbackPeer(peer);
294
+ const asserted = rawHeaderValues(req, TRUSTED_CLIENT_ADDRESS_HEADER);
295
+ if (loopback) {
296
+ if (asserted.length !== 1 || typeof asserted[0] !== 'string' || isIP(asserted[0]) === 0) {
297
+ throw new CloudHttpError(403, 'proxy_assertion_rejected', 'The trusted proxy client-address assertion is missing or invalid.');
298
+ }
299
+ return asserted[0];
300
+ }
301
+ if (asserted.length !== 0) {
302
+ throw new CloudHttpError(403, 'proxy_assertion_rejected', 'A client-address assertion was received from an untrusted peer.');
303
+ }
304
+ if (typeof peer !== 'string' || isIP(peer) === 0) {
305
+ throw new CloudHttpError(403, 'peer_rejected', 'The network peer address is invalid.');
306
+ }
307
+ return peer;
308
+ }
309
+
310
+ class ConcurrencyGate {
311
+ #maximum;
312
+ #perSource;
313
+ #sources = new Map();
314
+ #total = 0;
315
+
316
+ constructor(maximum, perSource) {
317
+ this.#maximum = maximum;
318
+ this.#perSource = perSource;
319
+ }
320
+
321
+ enter(source) {
322
+ const current = this.#sources.get(source) ?? 0;
323
+ if (this.#total >= this.#maximum || current >= this.#perSource) return null;
324
+ this.#total += 1;
325
+ this.#sources.set(source, current + 1);
326
+ let released = false;
327
+ return () => {
328
+ if (released) return;
329
+ released = true;
330
+ this.#total -= 1;
331
+ const next = (this.#sources.get(source) ?? 1) - 1;
332
+ if (next <= 0) this.#sources.delete(source);
333
+ else this.#sources.set(source, next);
334
+ };
335
+ }
336
+
337
+ get active() {
338
+ return this.#total;
339
+ }
340
+ }
341
+
342
+ function hasRequestBodyFraming(req) {
343
+ const contentLengths = rawHeaderValues(req, 'content-length');
344
+ const transferEncodings = rawHeaderValues(req, 'transfer-encoding');
345
+ const contentEncodings = rawHeaderValues(req, 'content-encoding');
346
+ const expectations = rawHeaderValues(req, 'expect');
347
+ const validEmptyLength = contentLengths.length === 0
348
+ || (contentLengths.length === 1 && contentLengths[0] === '0');
349
+ return !validEmptyLength || transferEncodings.length !== 0
350
+ || contentEncodings.length !== 0 || expectations.length !== 0;
351
+ }
352
+
353
+ function rejectRequestBodyFraming(req) {
354
+ if (hasRequestBodyFraming(req)) {
355
+ req.shouldKeepAlive = false;
356
+ throw new CloudHttpError(400, 'unexpected_body', 'This request does not accept a body.');
357
+ }
358
+ }
359
+
360
+ function publicOwnedRecord(value, accountId, fields, label) {
361
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
362
+ throw new CloudHttpError(503, 'storage_unavailable', `The stored ${label} ownership is invalid.`);
363
+ }
364
+ const descriptors = Object.getOwnPropertyDescriptors(value);
365
+ const owner = descriptors.accountId;
366
+ if (!owner || !owner.enumerable || !Object.hasOwn(owner, 'value') || owner.value !== accountId) {
367
+ throw new CloudHttpError(503, 'storage_unavailable', `The stored ${label} ownership is invalid.`);
368
+ }
369
+ const result = {};
370
+ for (const field of fields) {
371
+ const descriptor = descriptors[field];
372
+ if (!descriptor || !descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) {
373
+ throw new CloudHttpError(503, 'storage_unavailable', `The stored ${label} shape is invalid.`);
374
+ }
375
+ result[field] = descriptor.value;
376
+ }
377
+ return Object.freeze(result);
378
+ }
379
+
380
+ function publicThread(value, accountId) {
381
+ return publicOwnedRecord(value, accountId, PUBLIC_THREAD_FIELDS, 'chat thread');
382
+ }
383
+
384
+ function publicMessage(value, accountId, { attachmentSchema = 2 } = {}) {
385
+ const message = publicOwnedRecord(value, accountId, PUBLIC_MESSAGE_FIELDS, 'chat message');
386
+ if (value.attachment === undefined && value.attachments === undefined) return message;
387
+ if (value.attachment !== undefined && value.attachments !== undefined) {
388
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment shape is ambiguous.');
389
+ }
390
+ const values = value.attachments ?? [value.attachment];
391
+ if (!Array.isArray(values) || values.length < 1 || values.length > 4) {
392
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment list is invalid.');
393
+ }
394
+ const checked = [];
395
+ const identifiers = new Set();
396
+ let bytes = 0;
397
+ for (const attachment of values) {
398
+ if (!attachment || typeof attachment !== 'object' || Array.isArray(attachment)) {
399
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment descriptor is invalid.');
400
+ }
401
+ const descriptors = Object.getOwnPropertyDescriptors(attachment);
402
+ const expected = ['attachmentId', 'mediaType', 'byteLength', 'width', 'height', 'sha256'];
403
+ if (Reflect.ownKeys(descriptors).length !== expected.length
404
+ || expected.some((field) => !descriptors[field]?.enumerable
405
+ || !Object.hasOwn(descriptors[field], 'value'))) {
406
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment descriptor is invalid.');
407
+ }
408
+ if (identifiers.has(attachment.attachmentId)) {
409
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment identifiers are not unique.');
410
+ }
411
+ identifiers.add(attachment.attachmentId);
412
+ bytes += attachment.byteLength;
413
+ if (bytes > 16 * 1024 * 1024) {
414
+ throw new CloudHttpError(503, 'storage_unavailable', 'The stored chat attachment list is too large.');
415
+ }
416
+ checked.push(Object.freeze({ ...attachment }));
417
+ }
418
+ return Object.freeze(checked.length === 1 || attachmentSchema === 1
419
+ ? { ...message, attachment: checked[0] }
420
+ : { ...message, attachments: Object.freeze(checked) });
421
+ }
422
+
423
+ function publicGeneration(value, accountId) {
424
+ return publicOwnedRecord(value, accountId, PUBLIC_GENERATION_FIELDS, 'chat generation');
425
+ }
426
+
427
+ function publicDelta(value, accountId) {
428
+ return publicOwnedRecord(value, accountId, PUBLIC_DELTA_FIELDS, 'chat delta');
429
+ }
430
+
431
+ function requireAgentResponseCorrelation(pathname, input, response) {
432
+ let matches = true;
433
+ if ([AGINTI_RPC_PATHS.threadsGet, AGINTI_RPC_PATHS.threadsUpdate].includes(pathname)) {
434
+ matches = response.thread.id === input.threadId;
435
+ } else if (pathname === AGINTI_RPC_PATHS.threadsDelete) {
436
+ matches = response.threadId === input.threadId;
437
+ } else if (pathname === AGINTI_RPC_PATHS.runsStart) {
438
+ matches = response.run.threadId === input.threadId;
439
+ } else if ([AGINTI_RPC_PATHS.runsStatus, AGINTI_RPC_PATHS.runsCancel].includes(pathname)) {
440
+ matches = response.run.id === input.runId;
441
+ } else if (pathname === AGINTI_RPC_PATHS.runsResume) {
442
+ matches = response.run.previousRunId === input.runId;
443
+ } else if (pathname === AGINTI_RPC_PATHS.artifactsGet) {
444
+ matches = response.artifact.id === input.artifactId;
445
+ }
446
+ if (!matches) {
447
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned a response for a different resource.');
448
+ }
449
+ return response;
450
+ }
451
+
452
+ class LoginAdmission {
453
+ #attempts = new Map();
454
+ #clock;
455
+ #gate;
456
+ #maximumAttempts;
457
+
458
+ constructor({ clock, maximum, perSource, maximumAttempts }) {
459
+ this.#clock = clock;
460
+ this.#gate = new ConcurrencyGate(maximum, perSource);
461
+ this.#maximumAttempts = maximumAttempts;
462
+ }
463
+
464
+ enter(source) {
465
+ const now = epochMilliseconds(this.#clock);
466
+ const existing = this.#attempts.get(source);
467
+ const record = !existing || now - existing.windowStart >= 60_000
468
+ ? { windowStart: now, count: 0, lastSeen: now }
469
+ : existing;
470
+ if (record.count >= this.#maximumAttempts) {
471
+ record.lastSeen = now;
472
+ this.#attempts.set(source, record);
473
+ return { error: new CloudHttpError(429, 'login_rate_limited', 'Sign-in is temporarily rate limited.', { retryAfter: 60 }) };
474
+ }
475
+ const release = this.#gate.enter(source);
476
+ if (!release) {
477
+ return { error: new CloudHttpError(503, 'login_busy', 'The sign-in service is temporarily busy.', { retryAfter: 2 }) };
478
+ }
479
+ record.count += 1;
480
+ record.lastSeen = now;
481
+ this.#attempts.set(source, record);
482
+ if (this.#attempts.size > 1_024) {
483
+ const oldest = [...this.#attempts.entries()].sort((a, b) => a[1].lastSeen - b[1].lastSeen).slice(0, 256);
484
+ for (const [key] of oldest) if (key !== source) this.#attempts.delete(key);
485
+ }
486
+ return { release };
487
+ }
488
+ }
489
+
490
+ function validateJsonContentType(req) {
491
+ const encoding = req.headers['content-encoding'];
492
+ if (encoding !== undefined && String(encoding).toLowerCase() !== 'identity') {
493
+ throw new CloudHttpError(415, 'unsupported_content_encoding', 'Compressed request bodies are not accepted.');
494
+ }
495
+ const value = req.headers['content-type'];
496
+ if (typeof value !== 'string') throw new CloudHttpError(415, 'unsupported_media_type', 'Content-Type must be application/json.');
497
+ const parts = value.toLowerCase().split(';').map((part) => part.trim());
498
+ if (parts[0] !== 'application/json' || parts.length > 2 || (parts.length === 2 && parts[1] !== 'charset=utf-8')) {
499
+ throw new CloudHttpError(415, 'unsupported_media_type', 'Content-Type must be application/json.');
500
+ }
501
+ }
502
+
503
+ function readJsonBody(req, maximum, timeoutMs) {
504
+ validateJsonContentType(req);
505
+ const advertised = req.headers['content-length'];
506
+ if (advertised !== undefined && (typeof advertised !== 'string' || !/^\d+$/u.test(advertised)
507
+ || Number(advertised) > maximum)) {
508
+ req.shouldKeepAlive = false;
509
+ throw new CloudHttpError(413, 'request_too_large', 'The request body is too large.');
510
+ }
511
+ return new Promise((resolve, reject) => {
512
+ const chunks = [];
513
+ let bytes = 0;
514
+ let settled = false;
515
+ const finish = (callback) => {
516
+ if (settled) return;
517
+ settled = true;
518
+ clearTimeout(timer);
519
+ req.off('data', onData);
520
+ req.off('end', onEnd);
521
+ req.off('aborted', onAborted);
522
+ req.off('error', onError);
523
+ callback();
524
+ };
525
+ const onData = (chunk) => {
526
+ bytes += chunk.byteLength;
527
+ if (bytes > maximum) {
528
+ req.shouldKeepAlive = false;
529
+ finish(() => reject(new CloudHttpError(413, 'request_too_large', 'The request body is too large.')));
530
+ req.pause();
531
+ return;
532
+ }
533
+ chunks.push(Buffer.from(chunk));
534
+ };
535
+ const onEnd = () => finish(() => {
536
+ let value;
537
+ try {
538
+ const source = Buffer.concat(chunks, bytes).toString('utf8');
539
+ if (Buffer.byteLength(source, 'utf8') !== bytes || source.length === 0) throw new Error('invalid UTF-8 or empty body');
540
+ value = JSON.parse(source);
541
+ } catch (error) {
542
+ reject(new CloudHttpError(400, 'invalid_json', 'The request body must be valid UTF-8 JSON.', { cause: error }));
543
+ return;
544
+ }
545
+ resolve(value);
546
+ });
547
+ const onAborted = () => finish(() => reject(new CloudHttpError(400, 'request_aborted', 'The request body was interrupted.')));
548
+ const onError = (error) => finish(() => reject(new CloudHttpError(400, 'request_error', 'The request body could not be read.', { cause: error })));
549
+ const timer = setTimeout(() => finish(() => {
550
+ req.shouldKeepAlive = false;
551
+ reject(new CloudHttpError(408, 'request_timeout', 'The request body was not received in time.'));
552
+ }), timeoutMs);
553
+ timer.unref?.();
554
+ req.on('data', onData);
555
+ req.once('end', onEnd);
556
+ req.once('aborted', onAborted);
557
+ req.once('error', onError);
558
+ });
559
+ }
560
+
561
+ function requireOrigin(req, publicOrigin) {
562
+ if (req.headers.origin !== publicOrigin) {
563
+ throw new CloudHttpError(403, 'origin_rejected', 'The request origin is not allowed.');
564
+ }
565
+ }
566
+
567
+ function fetchMetadataState(req) {
568
+ const site = req.headers['sec-fetch-site'];
569
+ const mode = req.headers['sec-fetch-mode'];
570
+ const destination = req.headers['sec-fetch-dest'];
571
+ const present = [site, mode, destination].filter((value) => value !== undefined).length;
572
+ if (present === 0) return 'missing';
573
+ if ((site !== undefined && site !== 'same-origin')
574
+ || (mode !== undefined && !['cors', 'same-origin'].includes(mode))
575
+ || (destination !== undefined && destination !== 'empty')) return 'invalid';
576
+ return present === 3 ? 'complete' : 'partial';
577
+ }
578
+
579
+ function artifactFetchMetadataState(req) {
580
+ const site = req.headers['sec-fetch-site'];
581
+ const mode = req.headers['sec-fetch-mode'];
582
+ const destination = req.headers['sec-fetch-dest'];
583
+ const present = [site, mode, destination].filter((value) => value !== undefined).length;
584
+ if (present === 0) return 'missing';
585
+ if ((site !== undefined && site !== 'same-origin')
586
+ || (mode !== undefined && !['cors', 'same-origin', 'navigate'].includes(mode))
587
+ || (destination !== undefined && !['empty', 'document'].includes(destination))) return 'invalid';
588
+ return present === 3 ? 'complete' : 'partial';
589
+ }
590
+
591
+ function rejectFetchMetadata() {
592
+ throw new CloudHttpError(403, 'fetch_metadata_rejected', 'The request fetch metadata is not allowed.');
593
+ }
594
+
595
+ function clientReleaseState(req, currentRelease) {
596
+ const value = req.headers[CLIENT_RELEASE_HEADER_NAME];
597
+ if (value === undefined) return 'missing';
598
+ return typeof value === 'string' && value === currentRelease ? 'match' : 'mismatch';
599
+ }
600
+
601
+ function artifactClientReleaseState(req, routeRelease, currentRelease) {
602
+ const header = clientReleaseState(req, currentRelease);
603
+ if (routeRelease !== currentRelease || header === 'mismatch') return 'mismatch';
604
+ return 'match';
605
+ }
606
+
607
+ function rejectClientRelease() {
608
+ throw new CloudHttpError(409, 'client_release_mismatch', 'The browser app must load the current release before retrying.');
609
+ }
610
+
611
+ function parseCookie(req, name) {
612
+ const header = req.headers.cookie;
613
+ if (header === undefined) return null;
614
+ if (typeof header !== 'string' || Buffer.byteLength(header, 'utf8') > CLOUD_HTTP_LIMITS.cookieBytes) {
615
+ throw new CloudHttpError(400, 'invalid_cookie', 'The Cookie header is invalid.');
616
+ }
617
+ const matches = [];
618
+ for (const part of header.split(';')) {
619
+ const index = part.indexOf('=');
620
+ if (index < 1) continue;
621
+ if (part.slice(0, index).trim() === name) matches.push(part.slice(index + 1).trim());
622
+ }
623
+ if (matches.length > 1) throw new CloudHttpError(400, 'invalid_cookie', 'A session cookie was duplicated.');
624
+ if (matches.length === 0) return null;
625
+ if (!/^[A-Za-z0-9_-]{32,128}$/u.test(matches[0])) throw new CloudHttpError(401, 'invalid_session', 'The browser session is invalid.');
626
+ return matches[0];
627
+ }
628
+
629
+ function csrfFromRequest(req) {
630
+ const value = req.headers[CSRF_HEADER_NAME];
631
+ if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{32,128}$/u.test(value)) {
632
+ throw new CloudHttpError(403, 'csrf_rejected', 'The CSRF token is missing or invalid.');
633
+ }
634
+ return value;
635
+ }
636
+
637
+ function requestIdempotency(req, agent = false) {
638
+ const value = req.headers[IDEMPOTENCY_HEADER_NAME];
639
+ return agent ? validateAgentIdempotencyKey(value) : validateRequestIdempotencyKey(value);
640
+ }
641
+
642
+ function artifactByteRange(req) {
643
+ const values = rawHeaderValues(req, 'range');
644
+ if (values.length === 0) return undefined;
645
+ if (values.length !== 1 || typeof values[0] !== 'string') {
646
+ throw new CloudHttpError(400, 'invalid_range', 'The artifact byte range is invalid.');
647
+ }
648
+ const match = /^bytes=(0|[1-9]\d*)-(?:(0|[1-9]\d*))?$/u.exec(values[0]);
649
+ if (!match) throw new CloudHttpError(400, 'invalid_range', 'Only one start-based byte range is supported.');
650
+ const start = Number(match[1]);
651
+ const end = match[2] === undefined ? undefined : Number(match[2]);
652
+ if (!Number.isSafeInteger(start)
653
+ || (end !== undefined && (!Number.isSafeInteger(end) || end < start))) {
654
+ throw new CloudHttpError(400, 'invalid_range', 'The artifact byte range is outside its supported bound.');
655
+ }
656
+ return Object.freeze({ start, ...(end === undefined ? {} : { end }) });
657
+ }
658
+
659
+ function artifactContentDisposition(filename, download = false) {
660
+ const fallback = filename.normalize('NFKD')
661
+ .replace(/\p{M}+/gu, '')
662
+ .replace(/[^A-Za-z0-9._-]+/gu, '_')
663
+ .replace(/^\.+/u, '')
664
+ .slice(0, 120) || 'artifact';
665
+ const encoded = encodeURIComponent(filename).replace(/['()*]/gu, (value) => (
666
+ `%${value.codePointAt(0).toString(16).toUpperCase()}`
667
+ ));
668
+ return `${download ? 'attachment' : 'inline'}; filename="${fallback}"; filename*=UTF-8''${encoded}`;
669
+ }
670
+
671
+ function sessionCookie(value, maximumAge) {
672
+ return `${SESSION_COOKIE_NAME}=${value}; Path=/; Secure; HttpOnly; SameSite=Strict; Max-Age=${maximumAge}`;
673
+ }
674
+
675
+ function csrfCookie(value, maximumAge) {
676
+ return `${CSRF_COOKIE_NAME}=${value}; Path=/; Secure; SameSite=Strict; Max-Age=${maximumAge}`;
677
+ }
678
+
679
+ function clearedCookies() {
680
+ const suffix = 'Path=/; Secure; SameSite=Strict; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT';
681
+ return [
682
+ `${SESSION_COOKIE_NAME}=; ${suffix}; HttpOnly`,
683
+ `${CSRF_COOKIE_NAME}=; ${suffix}`
684
+ ];
685
+ }
686
+
687
+ function browserSessionId(sessionToken) {
688
+ return sha256(sessionToken);
689
+ }
690
+
691
+ function requestAbortController(req, res) {
692
+ const controller = new AbortController();
693
+ const abort = () => {
694
+ if (!controller.signal.aborted) controller.abort(new DOMException('request disconnected', 'AbortError'));
695
+ };
696
+ const close = () => { if (!res.writableEnded) abort(); };
697
+ req.once('aborted', abort);
698
+ res.once('close', close);
699
+ return {
700
+ controller,
701
+ cleanup() {
702
+ req.off('aborted', abort);
703
+ res.off('close', close);
704
+ }
705
+ };
706
+ }
707
+
708
+ function deadlineSignal(parentSignal, milliseconds, message) {
709
+ const controller = new AbortController();
710
+ const abortFromParent = () => controller.abort(parentSignal.reason ?? new DOMException('aborted', 'AbortError'));
711
+ if (parentSignal.aborted) abortFromParent();
712
+ else parentSignal.addEventListener('abort', abortFromParent, { once: true });
713
+ const timer = setTimeout(() => {
714
+ const error = new Error(message);
715
+ error.name = 'TimeoutError';
716
+ controller.abort(error);
717
+ }, milliseconds);
718
+ timer.unref?.();
719
+ return {
720
+ signal: controller.signal,
721
+ cleanup() {
722
+ clearTimeout(timer);
723
+ parentSignal.removeEventListener('abort', abortFromParent);
724
+ }
725
+ };
726
+ }
727
+
728
+ async function withTimeout(callback, { signal, milliseconds, timeoutMessage = 'dependency timed out' }) {
729
+ const controller = new AbortController();
730
+ const abortFromCaller = () => controller.abort(signal.reason ?? new DOMException('aborted', 'AbortError'));
731
+ if (signal?.aborted) abortFromCaller();
732
+ else signal?.addEventListener('abort', abortFromCaller, { once: true });
733
+ const timer = setTimeout(() => {
734
+ const error = new Error(timeoutMessage);
735
+ error.name = 'TimeoutError';
736
+ controller.abort(error);
737
+ }, milliseconds);
738
+ timer.unref?.();
739
+ try {
740
+ return await Promise.race([
741
+ Promise.resolve().then(() => callback(controller.signal)),
742
+ new Promise((_, reject) => {
743
+ const aborted = () => reject(controller.signal.reason ?? new DOMException('aborted', 'AbortError'));
744
+ if (controller.signal.aborted) aborted();
745
+ else controller.signal.addEventListener('abort', aborted, { once: true });
746
+ })
747
+ ]);
748
+ } finally {
749
+ clearTimeout(timer);
750
+ signal?.removeEventListener('abort', abortFromCaller);
751
+ }
752
+ }
753
+
754
+ function delay(milliseconds, signal) {
755
+ return new Promise((resolve, reject) => {
756
+ if (signal?.aborted) {
757
+ reject(signal.reason ?? new DOMException('aborted', 'AbortError'));
758
+ return;
759
+ }
760
+ const abort = () => {
761
+ clearTimeout(timer);
762
+ reject(signal.reason ?? new DOMException('aborted', 'AbortError'));
763
+ };
764
+ const timer = setTimeout(() => {
765
+ signal?.removeEventListener('abort', abort);
766
+ resolve();
767
+ }, milliseconds);
768
+ timer.unref?.();
769
+ signal?.addEventListener('abort', abort, { once: true });
770
+ });
771
+ }
772
+
773
+ function nextWithAbort(iterator, signal) {
774
+ return new Promise((resolve, reject) => {
775
+ let settled = false;
776
+ const finish = (callback, value) => {
777
+ if (settled) return;
778
+ settled = true;
779
+ signal.removeEventListener('abort', onAbort);
780
+ callback(value);
781
+ };
782
+ const onAbort = () => finish(reject, signal.reason ?? new DOMException('aborted', 'AbortError'));
783
+ if (signal.aborted) {
784
+ onAbort();
785
+ return;
786
+ }
787
+ signal.addEventListener('abort', onAbort, { once: true });
788
+ Promise.resolve().then(() => iterator.next()).then(
789
+ (value) => finish(resolve, value),
790
+ (error) => finish(reject, error)
791
+ );
792
+ });
793
+ }
794
+
795
+ async function* abortableAsyncIterable(iterable, signal) {
796
+ const iterator = iterable[Symbol.asyncIterator]();
797
+ try {
798
+ while (true) {
799
+ const item = await nextWithAbort(iterator, signal);
800
+ if (item.done) return;
801
+ yield item.value;
802
+ }
803
+ } finally {
804
+ if (signal.aborted && typeof iterator.return === 'function') {
805
+ try { void iterator.return(); } catch { /* The aborted producer is already detached. */ }
806
+ }
807
+ }
808
+ }
809
+
810
+ function valueWithAbort(value, signal) {
811
+ return new Promise((resolve, reject) => {
812
+ let settled = false;
813
+ const finish = (callback, result) => {
814
+ if (settled) return;
815
+ settled = true;
816
+ signal.removeEventListener('abort', onAbort);
817
+ callback(result);
818
+ };
819
+ const onAbort = () => finish(reject, signal.reason ?? new DOMException('aborted', 'AbortError'));
820
+ if (signal.aborted) {
821
+ onAbort();
822
+ return;
823
+ }
824
+ signal.addEventListener('abort', onAbort, { once: true });
825
+ Promise.resolve(value).then(
826
+ (result) => finish(resolve, result),
827
+ (error) => finish(reject, error)
828
+ );
829
+ });
830
+ }
831
+
832
+ function jsonSseEvent({ event, data, id }) {
833
+ return `${id === undefined ? '' : `id: ${id}\n`}event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
834
+ }
835
+
836
+ async function writeSse(res, value, signal) {
837
+ if (signal.aborted || res.writableEnded || res.destroyed) throw signal.reason ?? new DOMException('aborted', 'AbortError');
838
+ if (res.write(value)) return;
839
+ await new Promise((resolve, reject) => {
840
+ let settled = false;
841
+ const finish = (callback, value) => {
842
+ if (settled) return;
843
+ settled = true;
844
+ res.off('drain', onDrain);
845
+ res.off('close', onClose);
846
+ signal.removeEventListener('abort', onAbort);
847
+ callback(value);
848
+ };
849
+ const onDrain = () => finish(resolve);
850
+ const onClose = () => finish(reject, new DOMException('response closed', 'AbortError'));
851
+ const onAbort = () => finish(reject, signal.reason ?? new DOMException('aborted', 'AbortError'));
852
+ res.once('drain', onDrain);
853
+ res.once('close', onClose);
854
+ signal.addEventListener('abort', onAbort, { once: true });
855
+ });
856
+ }
857
+
858
+ async function waitForResponseDrain(res, signal) {
859
+ await new Promise((resolve, reject) => {
860
+ let settled = false;
861
+ const finish = (callback, value) => {
862
+ if (settled) return;
863
+ settled = true;
864
+ res.off('drain', onDrain);
865
+ res.off('close', onClose);
866
+ signal.removeEventListener('abort', onAbort);
867
+ callback(value);
868
+ };
869
+ const onDrain = () => finish(resolve);
870
+ const onClose = () => finish(reject, new DOMException('response closed', 'AbortError'));
871
+ const onAbort = () => finish(reject, signal.reason ?? new DOMException('aborted', 'AbortError'));
872
+ if (signal.aborted || res.destroyed || res.writableEnded) {
873
+ onAbort();
874
+ return;
875
+ }
876
+ res.once('drain', onDrain);
877
+ res.once('close', onClose);
878
+ signal.addEventListener('abort', onAbort, { once: true });
879
+ });
880
+ }
881
+
882
+ async function streamArtifactBytes(res, body, expectedBytes, signal) {
883
+ if (!body || typeof body.getReader !== 'function'
884
+ || !Number.isSafeInteger(expectedBytes) || expectedBytes < 1
885
+ || expectedBytes > AGINTI_MAX_FILE_ARTIFACT_BYTES) {
886
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid artifact stream.');
887
+ }
888
+ const reader = body.getReader();
889
+ let received = 0;
890
+ let ended = false;
891
+ try {
892
+ for (;;) {
893
+ const item = await valueWithAbort(reader.read(), signal);
894
+ if (item.done) {
895
+ ended = true;
896
+ break;
897
+ }
898
+ if (!(item.value instanceof Uint8Array) || item.value.byteLength < 1) {
899
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid artifact stream.');
900
+ }
901
+ received += item.value.byteLength;
902
+ if (received > expectedBytes || received > AGINTI_MAX_FILE_ARTIFACT_BYTES) {
903
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned too many artifact bytes.');
904
+ }
905
+ if (!res.write(Buffer.from(item.value))) await waitForResponseDrain(res, signal);
906
+ }
907
+ if (received !== expectedBytes) {
908
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an incomplete artifact stream.');
909
+ }
910
+ res.end();
911
+ } finally {
912
+ if (!ended) {
913
+ try { void Promise.resolve(reader.cancel()).catch(() => {}); }
914
+ catch { /* The interrupted upstream reader is already unusable. */ }
915
+ }
916
+ try { reader.releaseLock?.(); }
917
+ catch { /* A hostile pending read must not retain the public stream admission slot. */ }
918
+ }
919
+ }
920
+
921
+ function validateArtifactContentResult(value, request, metadataOnly) {
922
+ if (!value || typeof value !== 'object' || Array.isArray(value)
923
+ || ![200, 206].includes(value.status)) {
924
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned invalid artifact metadata.');
925
+ }
926
+ try {
927
+ validateFileSpec({
928
+ schemaVersion: '1',
929
+ filename: value.filename,
930
+ mime: value.mime,
931
+ bytes: value.totalBytes,
932
+ sha256: value.sha256
933
+ });
934
+ } catch (error) {
935
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned invalid artifact metadata.', { cause: error });
936
+ }
937
+ if (!Number.isSafeInteger(value.selectedBytes) || value.selectedBytes < 1
938
+ || value.selectedBytes > value.totalBytes
939
+ || (metadataOnly ? value.body !== null : (!value.body || typeof value.body.getReader !== 'function'))) {
940
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned invalid artifact byte metadata.');
941
+ }
942
+ let contentRange;
943
+ if (request.range === undefined) {
944
+ if (value.status !== 200 || value.range !== null || value.selectedBytes !== value.totalBytes) {
945
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an unsolicited artifact range.');
946
+ }
947
+ } else {
948
+ const range = value.range;
949
+ if (value.status !== 206 || !range || !Number.isSafeInteger(range.start)
950
+ || !Number.isSafeInteger(range.end) || !Number.isSafeInteger(range.total)
951
+ || range.start !== request.range.start || range.total !== value.totalBytes
952
+ || range.end < range.start || range.end >= range.total
953
+ || value.selectedBytes !== range.end - range.start + 1
954
+ || (request.range.end !== undefined && range.end > request.range.end)) {
955
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid artifact range.');
956
+ }
957
+ contentRange = `bytes ${range.start}-${range.end}/${range.total}`;
958
+ }
959
+ return Object.freeze({
960
+ status: value.status,
961
+ filename: value.filename,
962
+ mime: value.mime,
963
+ totalBytes: value.totalBytes,
964
+ selectedBytes: value.selectedBytes,
965
+ sha256: value.sha256,
966
+ body: value.body,
967
+ contentRange
968
+ });
969
+ }
970
+
971
+ function safeConnectorFailure(error, timedOut) {
972
+ if (timedOut || error?.name === 'TimeoutError') return 'timeout';
973
+ return SAFE_FAILURE_CODES.has(error?.failureCode) ? error.failureCode : 'provider_unavailable';
974
+ }
975
+
976
+ function splitUtf8(value, maximumBytes) {
977
+ if (typeof value !== 'string' || value.length < 1 || value.includes('\u0000')) {
978
+ throw new Error('connector returned an invalid delta');
979
+ }
980
+ const chunks = [];
981
+ let chunk = '';
982
+ let bytes = 0;
983
+ for (const scalar of value) {
984
+ const scalarBytes = Buffer.byteLength(scalar, 'utf8');
985
+ if (bytes + scalarBytes > maximumBytes && chunk) {
986
+ chunks.push(chunk);
987
+ chunk = '';
988
+ bytes = 0;
989
+ }
990
+ if (scalarBytes > maximumBytes) throw new Error('connector delta scalar exceeds its byte limit');
991
+ chunk += scalar;
992
+ bytes += scalarBytes;
993
+ }
994
+ if (chunk) chunks.push(chunk);
995
+ return chunks;
996
+ }
997
+
998
+ async function persistedDeltas(store, accountId, threadId, generationId) {
999
+ const result = [];
1000
+ let afterSequence = 0;
1001
+ let generation = null;
1002
+ while (result.length < 8_192) {
1003
+ const page = store.replayGeneration({ accountId, threadId, generationId, afterSequence, limit: 200 });
1004
+ generation = page.generation;
1005
+ result.push(...page.deltas);
1006
+ if (page.deltas.length === 0 || !page.hasMore) break;
1007
+ afterSequence = page.deltas[page.deltas.length - 1].sequence;
1008
+ }
1009
+ return { generation, deltas: result };
1010
+ }
1011
+
1012
+ export function createCloudRequestHandler({
1013
+ releaseId,
1014
+ assetMap,
1015
+ publicOrigin,
1016
+ account,
1017
+ passwordVerifier,
1018
+ sessionStore,
1019
+ controlStore,
1020
+ directChatStore,
1021
+ directChatContext,
1022
+ directChatConnector,
1023
+ visionEnabled = false,
1024
+ visionModelAlias = VISION_MODEL_ALIAS,
1025
+ agintiAdapter,
1026
+ clock = () => new Date(),
1027
+ requestOutcomeObserver,
1028
+ limits: limitOverrides = {}
1029
+ } = {}) {
1030
+ const assets = snapshotAndValidateAssetMap(assetMap, releaseId);
1031
+ const origin = validatePublicOrigin(publicOrigin);
1032
+ const publicHost = new URL(origin).host;
1033
+ const configuredAccount = validateAccountConfig(account);
1034
+ const verifier = validatePasswordVerifier(passwordVerifier);
1035
+ const sessions = requireMethods(sessionStore ?? controlStore, 'sessionStore', [
1036
+ 'createBrowserSession', 'authenticateBrowserSession', 'authenticateBrowserMutation', 'revokeBrowserSession'
1037
+ ]);
1038
+ const controls = requireMethods(controlStore, 'controlStore', ['getAccount']);
1039
+ const chat = requireMethods(directChatStore, 'directChatStore', [
1040
+ 'createThread', 'getThread', 'listThreads', 'deleteThread', 'startTurn',
1041
+ 'appendGenerationDelta', 'finalizeGeneration', 'cancelGeneration', 'failGeneration',
1042
+ 'getGeneration', 'replayGeneration', 'listMessages',
1043
+ 'getVisionAttachment', 'getLatestVisionAttachment', 'getLatestVisionAttachments',
1044
+ 'claimGenerationLease', 'markGenerationDispatchStarted', 'renewGenerationLease',
1045
+ 'releaseGenerationLease', 'getGenerationLease'
1046
+ ]);
1047
+ if (directChatConnector !== undefined && directChatConnector !== null && typeof directChatConnector.generate !== 'function') {
1048
+ throw new TypeError('directChatConnector must provide generate()');
1049
+ }
1050
+ if (typeof visionEnabled !== 'boolean' || typeof visionModelAlias !== 'string'
1051
+ || !/^[a-z0-9][a-z0-9._-]{0,63}$/u.test(visionModelAlias)) {
1052
+ throw new TypeError('Direct Chat vision configuration is invalid');
1053
+ }
1054
+ const contextCoordinator = directChatContext === undefined || directChatContext === null
1055
+ ? null
1056
+ : requireMethods(directChatContext, 'directChatContext', ['prepareForTurn', 'assemble']);
1057
+ if (directChatConnector && contextCoordinator === null) {
1058
+ throw new TypeError('directChatContext is required when Direct LocalLLM chat is enabled');
1059
+ }
1060
+ if (agintiAdapter !== undefined && agintiAdapter !== null
1061
+ && (typeof agintiAdapter.rpc !== 'function' || typeof agintiAdapter.capabilities !== 'function')) {
1062
+ throw new TypeError('agintiAdapter must provide the frozen rpc() and capabilities() interface');
1063
+ }
1064
+ if (typeof clock !== 'function') throw new TypeError('clock must be a function');
1065
+ if (requestOutcomeObserver !== undefined && typeof requestOutcomeObserver !== 'function') {
1066
+ throw new TypeError('requestOutcomeObserver must be a function');
1067
+ }
1068
+ const limits = exactLimitOverrides(limitOverrides);
1069
+ const bodyGate = new ConcurrencyGate(limits.concurrentBodies, limits.concurrentBodiesPerSource);
1070
+ const loginAdmission = new LoginAdmission({
1071
+ clock,
1072
+ maximum: limits.concurrentLogins,
1073
+ perSource: limits.concurrentLoginsPerSource,
1074
+ maximumAttempts: limits.loginAttemptsPerMinute
1075
+ });
1076
+ const streamGate = new ConcurrencyGate(limits.concurrentStreams, limits.concurrentStreamsPerSession);
1077
+ const jobs = new Map();
1078
+ const ephemeralSessions = new Map();
1079
+ let ephemeralExpiryTimer = null;
1080
+ let stopping = false;
1081
+ let backgroundDrain = null;
1082
+
1083
+ async function authenticate(req, { csrf = true } = {}) {
1084
+ const sessionToken = parseCookie(req, SESSION_COOKIE_NAME);
1085
+ if (!sessionToken) return null;
1086
+ const ephemeral = ephemeralSessions.get(sessionToken);
1087
+ if (ephemeral) {
1088
+ if (ephemeral.expiresAt <= epochMilliseconds(clock)) {
1089
+ ephemeralSessions.delete(sessionToken);
1090
+ return null;
1091
+ }
1092
+ if (csrf && !safeEqual(csrfFromRequest(req), ephemeral.csrfToken)) {
1093
+ throw new CloudHttpError(403, 'csrf_rejected', 'The CSRF token is missing or invalid.');
1094
+ }
1095
+ return Object.freeze({
1096
+ token: sessionToken,
1097
+ view: ephemeral.view,
1098
+ browserSession: browserSessionId(sessionToken),
1099
+ ephemeral: true
1100
+ });
1101
+ }
1102
+ const session = await sessions.authenticateBrowserSession({ sessionToken });
1103
+ if (!session || session.accountId !== configuredAccount.principalId) return null;
1104
+ if (csrf) {
1105
+ const csrfToken = csrfFromRequest(req);
1106
+ const mutated = await sessions.authenticateBrowserMutation({ sessionToken, csrfToken });
1107
+ if (!mutated || mutated.accountId !== configuredAccount.principalId) {
1108
+ throw new CloudHttpError(403, 'csrf_rejected', 'The CSRF token is missing or invalid.');
1109
+ }
1110
+ }
1111
+ return Object.freeze({
1112
+ token: sessionToken,
1113
+ view: session,
1114
+ browserSession: browserSessionId(sessionToken),
1115
+ ephemeral: false
1116
+ });
1117
+ }
1118
+
1119
+ function requireAuthentication(value) {
1120
+ if (!value) throw new CloudHttpError(401, 'authentication_required', 'A valid browser session is required.');
1121
+ return value;
1122
+ }
1123
+
1124
+ async function handleLogin(req, res, body, requestSignal, clientAddress) {
1125
+ const admission = loginAdmission.enter(`${clientAddress}\u0000${configuredAccount.principalId}`);
1126
+ if (admission.error) throw admission.error;
1127
+ let password = body.password;
1128
+ try {
1129
+ const passwordAccepted = await withTimeout(
1130
+ (signal) => verifier.verify(password, { signal }),
1131
+ { signal: requestSignal, milliseconds: limits.dependencyTimeoutMs, timeoutMessage: 'password verification timed out' }
1132
+ );
1133
+ const accepted = passwordAccepted === true && safeEqual(body.username, configuredAccount.username);
1134
+ password = undefined;
1135
+ if (!accepted) throw new CloudHttpError(401, 'invalid_credentials', 'The username or password was not accepted.');
1136
+ const boundAccount = await controls.getAccount(configuredAccount.principalId);
1137
+ if (!boundAccount || boundAccount.id !== configuredAccount.principalId) {
1138
+ throw new CloudHttpError(503, 'account_unavailable', 'The configured account is unavailable.');
1139
+ }
1140
+ const sessionToken = opaqueToken();
1141
+ const csrfToken = opaqueToken();
1142
+ const ephemeral = body.sessionMode === 'ephemeral-memory';
1143
+ const maximumAge = ephemeral
1144
+ ? EPHEMERAL_SESSION_MAX_AGE_SECONDS
1145
+ : (body.remember ? REMEMBERED_SESSION_MAX_AGE_SECONDS : SESSION_MAX_AGE_SECONDS);
1146
+ const expiresAt = new Date(epochMilliseconds(clock) + maximumAge * 1_000).toISOString();
1147
+ if (ephemeral) {
1148
+ ephemeralSessions.clear();
1149
+ if (ephemeralExpiryTimer) clearTimeout(ephemeralExpiryTimer);
1150
+ ephemeralSessions.set(sessionToken, Object.freeze({
1151
+ csrfToken,
1152
+ expiresAt: Date.parse(expiresAt),
1153
+ view: Object.freeze({ accountId: configuredAccount.principalId })
1154
+ }));
1155
+ ephemeralExpiryTimer = setTimeout(() => {
1156
+ ephemeralSessions.delete(sessionToken);
1157
+ ephemeralExpiryTimer = null;
1158
+ }, maximumAge * 1_000);
1159
+ ephemeralExpiryTimer.unref?.();
1160
+ } else {
1161
+ await sessions.createBrowserSession({
1162
+ accountId: configuredAccount.principalId,
1163
+ sessionToken,
1164
+ csrfToken,
1165
+ expiresAt,
1166
+ idempotencyKey: derivedIdempotencyKey('login', sessionToken)
1167
+ });
1168
+ }
1169
+ sendJson(req, res, 200, {
1170
+ authenticated: true,
1171
+ username: configuredAccount.username,
1172
+ csrfToken,
1173
+ ...(ephemeral ? { sessionDisposition: 'ephemeral-memory' } : {})
1174
+ }, {
1175
+ 'set-cookie': [sessionCookie(sessionToken, maximumAge), csrfCookie(csrfToken, maximumAge)]
1176
+ });
1177
+ } finally {
1178
+ password = undefined;
1179
+ admission.release?.();
1180
+ }
1181
+ }
1182
+
1183
+ async function handleSession(req, res) {
1184
+ const withoutCsrf = await authenticate(req, { csrf: false });
1185
+ if (!withoutCsrf) {
1186
+ sendJson(req, res, 200, { authenticated: false }, { 'set-cookie': clearedCookies() });
1187
+ return;
1188
+ }
1189
+ const authenticated = requireAuthentication(await authenticate(req));
1190
+ sendJson(req, res, 200, {
1191
+ authenticated: true,
1192
+ username: configuredAccount.username,
1193
+ csrfToken: csrfFromRequest(req)
1194
+ });
1195
+ return authenticated;
1196
+ }
1197
+
1198
+ async function handleLogout(req, res) {
1199
+ const session = requireAuthentication(await authenticate(req));
1200
+ if (session.ephemeral) {
1201
+ ephemeralSessions.delete(session.token);
1202
+ if (ephemeralSessions.size === 0 && ephemeralExpiryTimer) {
1203
+ clearTimeout(ephemeralExpiryTimer);
1204
+ ephemeralExpiryTimer = null;
1205
+ }
1206
+ } else {
1207
+ const idempotencyKey = derivedIdempotencyKey('logout', session.token);
1208
+ await sessions.revokeBrowserSession({
1209
+ accountId: configuredAccount.principalId,
1210
+ sessionToken: session.token,
1211
+ idempotencyKey
1212
+ });
1213
+ }
1214
+ sendJson(req, res, 200, {
1215
+ signedOut: true,
1216
+ // The frozen transport has no browser-session-scoped cancellation RPC.
1217
+ // Never imply that revoking a cloud cookie cancelled AgInTi-owned work.
1218
+ agentCancellationPending: agintiAdapter !== undefined && agintiAdapter !== null
1219
+ }, { 'set-cookie': clearedCookies() });
1220
+ }
1221
+
1222
+ function closeGenerationJobs() {
1223
+ stopping = true;
1224
+ ephemeralSessions.clear();
1225
+ if (ephemeralExpiryTimer) {
1226
+ clearTimeout(ephemeralExpiryTimer);
1227
+ ephemeralExpiryTimer = null;
1228
+ }
1229
+ if (backgroundDrain) return backgroundDrain;
1230
+ const pending = [...jobs.values()];
1231
+ for (const job of pending) {
1232
+ if (!job.controller.signal.aborted) {
1233
+ const error = new Error('cloud server is stopping');
1234
+ error.code = 'server_stopping';
1235
+ job.controller.abort(error);
1236
+ }
1237
+ }
1238
+ backgroundDrain = Promise.allSettled(pending.map((job) => job.promise)).then(() => undefined);
1239
+ return backgroundDrain;
1240
+ }
1241
+
1242
+ function scheduleGeneration(accountId, threadId, generationId, startKey, preparation) {
1243
+ const jobKey = `${threadId}:${generationId}`;
1244
+ if (jobs.has(jobKey)) return true;
1245
+ if (stopping || !directChatConnector || jobs.size >= limits.directChatJobs) return false;
1246
+ const scheduled = chat.getGeneration({ accountId, threadId, generationId });
1247
+ const jobTimeoutMs = scheduled?.modelAlias === visionModelAlias
1248
+ ? limits.visionJobTimeoutMs
1249
+ : limits.jobTimeoutMs;
1250
+ const controller = new AbortController();
1251
+ const job = { controller, timedOut: false, promise: null, lease: null };
1252
+ jobs.set(jobKey, job);
1253
+ const timer = setTimeout(() => {
1254
+ job.timedOut = true;
1255
+ const error = new Error('direct chat generation timed out');
1256
+ error.name = 'TimeoutError';
1257
+ controller.abort(error);
1258
+ }, jobTimeoutMs);
1259
+ timer.unref?.();
1260
+ job.promise = (async () => {
1261
+ try {
1262
+ const thread = chat.getThread(accountId, threadId);
1263
+ if (!thread) throw new Error('direct chat thread disappeared');
1264
+ const persisted = await persistedDeltas(chat, accountId, threadId, generationId);
1265
+ if (!persisted.generation || persisted.generation.status !== 'in_progress') return;
1266
+ const ownerToken = opaqueToken();
1267
+ let lease;
1268
+ try {
1269
+ lease = chat.claimGenerationLease({
1270
+ accountId,
1271
+ threadId,
1272
+ generationId,
1273
+ ownerToken,
1274
+ ttlMs: 30_000
1275
+ });
1276
+ } catch (error) {
1277
+ const observed = chat.getGenerationLease({ accountId, threadId, generationId });
1278
+ if (observed?.phase === 'interrupted') {
1279
+ try {
1280
+ chat.failGeneration({
1281
+ accountId,
1282
+ threadId,
1283
+ generationId,
1284
+ failureCode: 'provider_unavailable',
1285
+ idempotencyKey: derivedIdempotencyKey('chat-interrupted', startKey, generationId)
1286
+ });
1287
+ } catch {
1288
+ // Another process may have resolved the terminal state.
1289
+ }
1290
+ }
1291
+ return;
1292
+ }
1293
+ job.lease = Object.freeze({ ownerToken, fence: lease.fence });
1294
+ const renewTimer = setInterval(() => {
1295
+ try {
1296
+ chat.renewGenerationLease({
1297
+ accountId,
1298
+ threadId,
1299
+ generationId,
1300
+ ownerToken,
1301
+ fence: lease.fence,
1302
+ ttlMs: 30_000
1303
+ });
1304
+ } catch (error) {
1305
+ if (!controller.signal.aborted) controller.abort(error);
1306
+ }
1307
+ }, 10_000);
1308
+ renewTimer.unref?.();
1309
+ job.renewTimer = renewTimer;
1310
+ if (persisted.deltas.length !== 0) {
1311
+ const error = new Error('a partially delivered stateless generation cannot be redispatched');
1312
+ error.failureCode = 'provider_unavailable';
1313
+ throw error;
1314
+ }
1315
+ const context = await contextCoordinator.assemble({
1316
+ accountId,
1317
+ threadId,
1318
+ sourceRevision: persisted.generation.sourceRevision,
1319
+ sourceHash: persisted.generation.sourceHash,
1320
+ ...(preparation === undefined ? {} : { preparation })
1321
+ });
1322
+ const visionAttachments = chat.getLatestVisionAttachments({
1323
+ accountId,
1324
+ threadId,
1325
+ sourceRevision: persisted.generation.sourceRevision
1326
+ });
1327
+ if ((persisted.generation.modelAlias === visionModelAlias) !== (visionAttachments.length > 0)) {
1328
+ const error = new Error('persisted vision inference authority is inconsistent');
1329
+ error.failureCode = 'content_rejected';
1330
+ throw error;
1331
+ }
1332
+ while (true) {
1333
+ if (controller.signal.aborted) throw controller.signal.reason;
1334
+ const marker = chat.markGenerationDispatchStarted({
1335
+ accountId,
1336
+ threadId,
1337
+ generationId,
1338
+ ownerToken,
1339
+ fence: lease.fence
1340
+ });
1341
+ if (marker.dispatchAuthorized === true) break;
1342
+ if (marker.dispatchState !== 'global_busy') {
1343
+ const error = new Error('inference dispatch is already ambiguous');
1344
+ error.failureCode = 'provider_unavailable';
1345
+ throw error;
1346
+ }
1347
+ await delay(GLOBAL_DISPATCH_RETRY_MS, controller.signal);
1348
+ }
1349
+ const output = await valueWithAbort(directChatConnector.generate({
1350
+ modelAlias: persisted.generation.modelAlias,
1351
+ context: context.payload,
1352
+ ...(visionAttachments.length === 0 ? {} : (visionAttachments.length === 1 ? {
1353
+ visionAttachment: Object.freeze({
1354
+ attachmentId: visionAttachments[0].attachmentId,
1355
+ messageId: visionAttachments[0].messageId,
1356
+ mediaType: visionAttachments[0].mediaType,
1357
+ byteLength: visionAttachments[0].byteLength,
1358
+ width: visionAttachments[0].width,
1359
+ height: visionAttachments[0].height,
1360
+ contentSha256: visionAttachments[0].contentSha256,
1361
+ content: visionAttachments[0].content
1362
+ })
1363
+ } : {
1364
+ visionAttachments: Object.freeze(visionAttachments.map((visionAttachment) => Object.freeze({
1365
+ attachmentId: visionAttachment.attachmentId,
1366
+ messageId: visionAttachment.messageId,
1367
+ mediaType: visionAttachment.mediaType,
1368
+ byteLength: visionAttachment.byteLength,
1369
+ width: visionAttachment.width,
1370
+ height: visionAttachment.height,
1371
+ contentSha256: visionAttachment.contentSha256,
1372
+ content: visionAttachment.content
1373
+ })))
1374
+ })),
1375
+ replay: Object.freeze({
1376
+ deltaCount: persisted.generation.deltaCount,
1377
+ lastDeltaHash: persisted.generation.lastDeltaHash
1378
+ }),
1379
+ signal: controller.signal
1380
+ }), controller.signal);
1381
+ if (!output || typeof output[Symbol.asyncIterator] !== 'function') {
1382
+ throw new Error('directChatConnector.generate() must return an async iterable');
1383
+ }
1384
+ let sequence = persisted.generation.deltaCount;
1385
+ let hash = persisted.generation.lastDeltaHash;
1386
+ let outputBytes = persisted.generation.deltaBytes;
1387
+ for await (const rawDelta of abortableAsyncIterable(output, controller.signal)) {
1388
+ if (controller.signal.aborted) throw controller.signal.reason;
1389
+ for (const delta of splitUtf8(rawDelta, limits.connectorDeltaBytes)) {
1390
+ outputBytes += Buffer.byteLength(delta, 'utf8');
1391
+ if (outputBytes > limits.connectorOutputBytes) {
1392
+ const error = new Error('connector output limit exceeded');
1393
+ error.failureCode = 'response_limit';
1394
+ throw error;
1395
+ }
1396
+ const appended = chat.appendGenerationDelta({
1397
+ accountId,
1398
+ threadId,
1399
+ generationId,
1400
+ expectedSequence: sequence,
1401
+ expectedHash: hash,
1402
+ content: delta,
1403
+ dispatchLease: job.lease
1404
+ });
1405
+ sequence = appended.sequence;
1406
+ hash = appended.deltaHash;
1407
+ }
1408
+ }
1409
+ if (controller.signal.aborted) throw controller.signal.reason;
1410
+ chat.finalizeGeneration({
1411
+ accountId,
1412
+ threadId,
1413
+ generationId,
1414
+ idempotencyKey: derivedIdempotencyKey('chat-finalize', startKey, generationId),
1415
+ dispatchLease: job.lease
1416
+ });
1417
+ } catch (error) {
1418
+ const current = chat.getGeneration({ accountId, threadId, generationId });
1419
+ if (!current || current.status !== 'in_progress') return;
1420
+ if (error?.code === 'server_stopping') return;
1421
+ try {
1422
+ chat.failGeneration({
1423
+ accountId,
1424
+ threadId,
1425
+ generationId,
1426
+ failureCode: safeConnectorFailure(error, job.timedOut),
1427
+ idempotencyKey: derivedIdempotencyKey('chat-fail', startKey, generationId),
1428
+ ...(job.lease === null ? {} : { dispatchLease: job.lease })
1429
+ });
1430
+ } catch {
1431
+ // Another request may have cancelled or completed the same durable generation.
1432
+ }
1433
+ } finally {
1434
+ clearTimeout(timer);
1435
+ if (job.renewTimer) clearInterval(job.renewTimer);
1436
+ if (job.lease) {
1437
+ try {
1438
+ chat.releaseGenerationLease({
1439
+ accountId,
1440
+ threadId,
1441
+ generationId,
1442
+ ownerToken: job.lease.ownerToken,
1443
+ fence: job.lease.fence
1444
+ });
1445
+ } catch {
1446
+ // A terminal write, cancellation, expiry, or newer fence already owns the durable result.
1447
+ }
1448
+ }
1449
+ if (jobs.get(jobKey) === job) jobs.delete(jobKey);
1450
+ }
1451
+ })();
1452
+ void job.promise.catch(() => {
1453
+ // The durable generation remains recoverable; shutdown waits for this task.
1454
+ });
1455
+ return true;
1456
+ }
1457
+
1458
+ function schedulePersistedGeneration(accountId, threadId, generationId) {
1459
+ return scheduleGeneration(
1460
+ accountId,
1461
+ threadId,
1462
+ generationId,
1463
+ derivedIdempotencyKey('chat-recovery', accountId, threadId, generationId),
1464
+ undefined
1465
+ );
1466
+ }
1467
+
1468
+ async function handleChat(req, res, route, body, session, requestSignal) {
1469
+ const accountId = configuredAccount.principalId;
1470
+ const input = validateChatRequest(route.pathname, body);
1471
+ const idempotencyKey = routeRequiresIdempotency(route.pathname)
1472
+ ? requestIdempotency(req)
1473
+ : undefined;
1474
+ if (route.pathname === CLOUD_ROUTES.chatCapabilities) {
1475
+ sendJson(req, res, 200, {
1476
+ visionInput: visionEnabled,
1477
+ visionMediaTypes: visionEnabled ? ['image/jpeg', 'image/png'] : [],
1478
+ maximumImageBytes: visionEnabled ? 4 * 1024 * 1024 : 0
1479
+ });
1480
+ return;
1481
+ }
1482
+ if (route.pathname === CLOUD_ROUTES.chatThreadsList) {
1483
+ const threads = chat.listThreads({ accountId, limit: input.limit })
1484
+ .map((thread) => publicThread(thread, accountId));
1485
+ sendJson(req, res, 200, { threads });
1486
+ return;
1487
+ }
1488
+ if (route.pathname === CLOUD_ROUTES.chatThreadsCreate) {
1489
+ const thread = chat.createThread({ accountId, ...input, idempotencyKey });
1490
+ sendJson(req, res, 201, { thread: publicThread(thread, accountId) });
1491
+ return;
1492
+ }
1493
+ if (route.pathname === CLOUD_ROUTES.chatThreadsGet) {
1494
+ const thread = chat.getThread(accountId, input.threadId);
1495
+ if (!thread) throw new CloudHttpError(404, 'not_found', 'The chat thread does not exist.');
1496
+ sendJson(req, res, 200, { thread: publicThread(thread, accountId) });
1497
+ return;
1498
+ }
1499
+ if (route.pathname === CLOUD_ROUTES.chatThreadsDelete) {
1500
+ const deleted = chat.deleteThread({ accountId, ...input, idempotencyKey });
1501
+ sendJson(req, res, 200, { deleted: deleted.deleted, threadId: deleted.threadId });
1502
+ return;
1503
+ }
1504
+ if (route.pathname === CLOUD_ROUTES.chatMessagesList) {
1505
+ const thread = chat.getThread(accountId, input.threadId);
1506
+ if (!thread) throw new CloudHttpError(404, 'not_found', 'The chat thread does not exist.');
1507
+ const { attachmentSchema, ...messageQuery } = input;
1508
+ const messages = chat.listMessages({ accountId, ...messageQuery });
1509
+ sendJson(req, res, 200, {
1510
+ messages: messages.map((message) => publicMessage(message, accountId, { attachmentSchema }))
1511
+ });
1512
+ return;
1513
+ }
1514
+ if (route.pathname === CLOUD_ROUTES.chatAttachmentsGet) {
1515
+ const attachment = chat.getVisionAttachment({ accountId, ...input });
1516
+ if (!attachment) throw new CloudHttpError(404, 'not_found', 'The chat attachment does not exist.');
1517
+ sendBuffer(req, res, 200, attachment.content, dynamicHeaders({
1518
+ 'content-type': attachment.mediaType,
1519
+ 'content-disposition': 'inline',
1520
+ 'x-content-type-options': 'nosniff'
1521
+ }, res[RESPONSE_RELEASE_ID]));
1522
+ return;
1523
+ }
1524
+ if (route.pathname === CLOUD_ROUTES.chatRunsStart) {
1525
+ if (!directChatConnector) throw new CloudHttpError(503, 'localllm_unavailable', 'Direct LocalLLM chat is unavailable.');
1526
+ const existing = chat.getGeneration({ accountId, threadId: input.threadId, generationId: input.generationId });
1527
+ if (!existing && input.attachments !== undefined && !visionEnabled) {
1528
+ throw new CloudHttpError(503, 'vision_unavailable', 'Direct LocalLLM vision is not enabled.');
1529
+ }
1530
+ if (!existing && !visionEnabled && input.expectedRevision > 0
1531
+ && chat.getLatestVisionAttachments({
1532
+ accountId,
1533
+ threadId: input.threadId,
1534
+ sourceRevision: input.expectedRevision
1535
+ }).length > 0) {
1536
+ throw new CloudHttpError(503, 'vision_unavailable', 'Direct LocalLLM vision is not enabled.');
1537
+ }
1538
+ if (!existing && jobs.size >= limits.directChatJobs) {
1539
+ throw new CloudHttpError(503, 'chat_busy', 'Direct chat is temporarily busy.', { retryAfter: 2 });
1540
+ }
1541
+ const preparation = existing ? undefined : await contextCoordinator.prepareForTurn({
1542
+ accountId,
1543
+ threadId: input.threadId,
1544
+ expectedRevision: input.expectedRevision,
1545
+ expectedHash: input.expectedHash,
1546
+ pendingUser: Object.freeze({ messageId: input.messageId, content: input.content }),
1547
+ signal: requestSignal
1548
+ });
1549
+ const turn = chat.startTurn({
1550
+ accountId,
1551
+ threadId: input.threadId,
1552
+ messageId: input.messageId,
1553
+ content: input.content,
1554
+ generationId: input.generationId,
1555
+ assistantMessageId: input.assistantMessageId,
1556
+ expectedRevision: input.expectedRevision,
1557
+ expectedHash: input.expectedHash,
1558
+ idempotencyKey,
1559
+ ...(input.attachments === undefined ? {} : { attachments: input.attachments })
1560
+ });
1561
+ const { generation } = turn;
1562
+ if (generation.status === 'in_progress' && !scheduleGeneration(
1563
+ accountId,
1564
+ input.threadId,
1565
+ input.generationId,
1566
+ idempotencyKey,
1567
+ preparation
1568
+ )) {
1569
+ throw new CloudHttpError(503, 'chat_resume_pending', 'The persisted generation is waiting for LocalLLM capacity.', { retryAfter: 2 });
1570
+ }
1571
+ sendJson(req, res, 202, { generation: publicGeneration(generation, accountId) });
1572
+ return;
1573
+ }
1574
+ if (route.pathname === CLOUD_ROUTES.chatRunsStatus) {
1575
+ const generation = chat.getGeneration({ accountId, ...input });
1576
+ if (!generation) throw new CloudHttpError(404, 'not_found', 'The chat generation does not exist.');
1577
+ if (generation.status === 'in_progress') {
1578
+ schedulePersistedGeneration(accountId, input.threadId, input.generationId);
1579
+ }
1580
+ sendJson(req, res, 200, { generation: publicGeneration(generation, accountId) });
1581
+ return;
1582
+ }
1583
+ if (route.pathname === CLOUD_ROUTES.chatRunsCancel) {
1584
+ const generation = chat.cancelGeneration({ accountId, ...input, idempotencyKey });
1585
+ const job = jobs.get(`${input.threadId}:${input.generationId}`);
1586
+ if (job && !job.controller.signal.aborted) {
1587
+ const error = new Error('direct chat generation cancelled');
1588
+ error.code = 'generation_cancelled';
1589
+ job.controller.abort(error);
1590
+ }
1591
+ sendJson(req, res, 200, { generation: publicGeneration(generation, accountId) });
1592
+ return;
1593
+ }
1594
+ if (route.pathname === CLOUD_ROUTES.chatRunsEvents) {
1595
+ await streamChatEvents(req, res, input, accountId, requestSignal);
1596
+ return;
1597
+ }
1598
+ throw new CloudHttpError(404, 'not_found', 'The requested route does not exist.');
1599
+ }
1600
+
1601
+ async function streamChatEvents(req, res, input, accountId, signal) {
1602
+ const initial = chat.getGeneration({ accountId, threadId: input.threadId, generationId: input.generationId });
1603
+ if (!initial) throw new CloudHttpError(404, 'not_found', 'The chat generation does not exist.');
1604
+ if (initial.status === 'in_progress') {
1605
+ schedulePersistedGeneration(accountId, input.threadId, input.generationId);
1606
+ }
1607
+ writeHead(res, 200, dynamicHeaders({
1608
+ 'content-type': 'text/event-stream; charset=utf-8',
1609
+ connection: 'keep-alive',
1610
+ 'x-accel-buffering': 'no'
1611
+ }, res[RESPONSE_RELEASE_ID]));
1612
+ res.flushHeaders?.();
1613
+ let afterSequence = input.afterSequence;
1614
+ const streamDeadline = deadlineSignal(
1615
+ signal,
1616
+ limits.sseLifetimeMs,
1617
+ 'Direct Chat event stream reached its reconnect boundary'
1618
+ );
1619
+ try {
1620
+ while (!streamDeadline.signal.aborted && !res.writableEnded) {
1621
+ const page = chat.replayGeneration({
1622
+ accountId,
1623
+ threadId: input.threadId,
1624
+ generationId: input.generationId,
1625
+ afterSequence,
1626
+ limit: 200
1627
+ });
1628
+ for (const delta of page.deltas) {
1629
+ await writeSse(res, jsonSseEvent({
1630
+ event: 'delta',
1631
+ id: delta.sequence,
1632
+ data: publicDelta(delta, accountId)
1633
+ }), streamDeadline.signal);
1634
+ afterSequence = delta.sequence;
1635
+ }
1636
+ if (page.generation.terminal) {
1637
+ await writeSse(res, jsonSseEvent({
1638
+ event: 'generation',
1639
+ data: publicGeneration(page.generation, accountId)
1640
+ }), streamDeadline.signal);
1641
+ res.end();
1642
+ return;
1643
+ }
1644
+ if (page.deltas.length === 0) {
1645
+ await delay(limits.ssePollMs, streamDeadline.signal);
1646
+ }
1647
+ }
1648
+ } catch (error) {
1649
+ if (!streamDeadline.signal.aborted || signal.aborted) throw error;
1650
+ } finally {
1651
+ streamDeadline.cleanup();
1652
+ }
1653
+ if (!signal.aborted && !res.writableEnded && !res.destroyed) {
1654
+ // Give a healthy reader an authenticated cursor before the bounded
1655
+ // reconnect. A stalled reader has already applied backpressure, so keep
1656
+ // the hard socket cutoff instead of buffering more data for it.
1657
+ if (res.writableNeedDrain) {
1658
+ res.destroy();
1659
+ } else {
1660
+ res.end(jsonSseEvent({ event: 'reconnect', data: { afterSequence } }));
1661
+ }
1662
+ }
1663
+ }
1664
+
1665
+ async function handleAgent(req, res, route, body, session, requestSignal) {
1666
+ const nativePath = route.nativeAgentPath;
1667
+ const input = validateTransportAgentRequest(nativePath, body);
1668
+ const mutation = routeRequiresIdempotency(route.pathname, nativePath);
1669
+ const idempotencyKey = mutation ? requestIdempotency(req, true) : undefined;
1670
+ if (!agintiAdapter) {
1671
+ if (nativePath === AGINTI_RPC_PATHS.capabilities) {
1672
+ sendJson(req, res, 200, FAIL_CLOSED_AGENT_CAPABILITIES);
1673
+ return;
1674
+ }
1675
+ throw new CloudHttpError(503, 'agent_unavailable', 'AgInTi Agent is unavailable.');
1676
+ }
1677
+ const contextFor = (signal) => Object.freeze({
1678
+ principalId: configuredAccount.principalId,
1679
+ browserSession: session.browserSession,
1680
+ ...(mutation ? { idempotencyKey } : {}),
1681
+ signal
1682
+ });
1683
+ const readContextFor = (signal) => Object.freeze({
1684
+ principalId: configuredAccount.principalId,
1685
+ browserSession: session.browserSession,
1686
+ signal
1687
+ });
1688
+ const requestedSearch = input.input?.search;
1689
+ if (requestedSearch !== undefined) {
1690
+ const proof = await withTimeout(
1691
+ (signal) => agintiAdapter.capabilities(readContextFor(signal)),
1692
+ { signal: requestSignal, milliseconds: limits.dependencyTimeoutMs, timeoutMessage: 'AgInTi capability check timed out' }
1693
+ );
1694
+ let capability;
1695
+ try { capability = validateAgentResponse(AGINTI_RPC_PATHS.capabilities, proof); }
1696
+ catch (error) {
1697
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid capability response.', { cause: error });
1698
+ }
1699
+ if (capability.enabled !== true || capability.search?.enabled !== true
1700
+ || !capability.search.modes.includes(requestedSearch.mode)
1701
+ || requestedSearch.limit > capability.search.maximumSources) {
1702
+ throw new CloudHttpError(409, 'agent_search_unavailable', 'AgInTi Search is not enabled for this session.');
1703
+ }
1704
+ }
1705
+ if (nativePath === AGINTI_RPC_PATHS.runsEvents) {
1706
+ const streamDeadline = deadlineSignal(requestSignal, limits.sseLifetimeMs, 'Agent event stream reached its reconnect boundary');
1707
+ try {
1708
+ const events = await valueWithAbort(
1709
+ agintiAdapter.rpc(nativePath, input, contextFor(streamDeadline.signal)),
1710
+ streamDeadline.signal
1711
+ );
1712
+ if (!events || typeof events[Symbol.asyncIterator] !== 'function') {
1713
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid event stream.');
1714
+ }
1715
+ writeHead(res, 200, dynamicHeaders({
1716
+ 'content-type': 'text/event-stream; charset=utf-8',
1717
+ connection: 'keep-alive',
1718
+ 'x-accel-buffering': 'no'
1719
+ }, res[RESPONSE_RELEASE_ID]));
1720
+ res.flushHeaders?.();
1721
+ for await (const rawEvent of abortableAsyncIterable(events, streamDeadline.signal)) {
1722
+ let event;
1723
+ try { event = validateEventEnvelope(rawEvent); }
1724
+ catch (error) { throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid event.', { cause: error }); }
1725
+ if (event.runId !== input.runId) {
1726
+ throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an event for a different run.');
1727
+ }
1728
+ await writeSse(res, jsonSseEvent({ event: event.type, id: event.id, data: event }), streamDeadline.signal);
1729
+ }
1730
+ } catch (error) {
1731
+ if (!streamDeadline.signal.aborted || requestSignal.aborted) throw error;
1732
+ } finally {
1733
+ streamDeadline.cleanup();
1734
+ }
1735
+ if (!requestSignal.aborted && !res.writableEnded) res.end();
1736
+ return;
1737
+ }
1738
+ try {
1739
+ const response = await withTimeout(
1740
+ (signal) => nativePath === AGINTI_RPC_PATHS.capabilities
1741
+ ? agintiAdapter.capabilities(contextFor(signal))
1742
+ : agintiAdapter.rpc(nativePath, input, contextFor(signal)),
1743
+ { signal: requestSignal, milliseconds: limits.dependencyTimeoutMs, timeoutMessage: 'AgInTi request timed out' }
1744
+ );
1745
+ let validated;
1746
+ try { validated = validateAgentResponse(nativePath, response); }
1747
+ catch (error) { throw new CloudHttpError(502, 'invalid_agent_response', 'AgInTi returned an invalid response.', { cause: error }); }
1748
+ requireAgentResponseCorrelation(nativePath, input, validated);
1749
+ sendJson(req, res, 200, validated);
1750
+ } catch (error) {
1751
+ if (nativePath === AGINTI_RPC_PATHS.capabilities) {
1752
+ sendJson(req, res, 200, FAIL_CLOSED_AGENT_CAPABILITIES);
1753
+ return;
1754
+ }
1755
+ throw error;
1756
+ }
1757
+ }
1758
+
1759
+ async function handleAgentArtifactContent(req, res, route, session, requestSignal) {
1760
+ if (!agintiAdapter || typeof agintiAdapter.artifactContent !== 'function') {
1761
+ throw new CloudHttpError(503, 'agent_unavailable', 'AgInTi Agent file artifacts are unavailable.');
1762
+ }
1763
+ const range = artifactByteRange(req);
1764
+ const metadataOnly = req.method === 'HEAD';
1765
+ const input = Object.freeze({
1766
+ artifactId: route.artifactId,
1767
+ ...(metadataOnly ? { metadataOnly: true } : {}),
1768
+ ...(range === undefined ? {} : { range })
1769
+ });
1770
+ const contextFor = (signal) => Object.freeze({
1771
+ principalId: configuredAccount.principalId,
1772
+ browserSession: session.browserSession,
1773
+ signal
1774
+ });
1775
+ const raw = await withTimeout(
1776
+ (signal) => agintiAdapter.artifactContent(input, contextFor(signal)),
1777
+ { signal: requestSignal, milliseconds: limits.dependencyTimeoutMs, timeoutMessage: 'AgInTi artifact request timed out' }
1778
+ );
1779
+ if (raw?.status === 404) {
1780
+ sendJson(req, res, 404, { error: { code: 'not_found', message: 'The requested artifact does not exist.' } }, {
1781
+ 'cache-control': 'no-store, private'
1782
+ });
1783
+ return;
1784
+ }
1785
+ if (raw?.status === 410) {
1786
+ sendJson(req, res, 410, {
1787
+ error: { code: 'artifact_content_gone', message: 'This local artifact file has been removed.' }
1788
+ }, { 'cache-control': 'no-store, private' });
1789
+ return;
1790
+ }
1791
+ if (raw?.status === 416) {
1792
+ sendJson(req, res, 416, {
1793
+ error: { code: 'range_not_satisfiable', message: 'The requested artifact byte range is not available.' }
1794
+ }, { 'cache-control': 'no-store, private' });
1795
+ return;
1796
+ }
1797
+ const result = validateArtifactContentResult(raw, input, metadataOnly);
1798
+ writeHead(res, result.status, dynamicHeaders({
1799
+ 'accept-ranges': 'bytes',
1800
+ 'cache-control': 'no-store, private',
1801
+ pragma: 'no-cache',
1802
+ expires: '0',
1803
+ 'content-type': result.mime,
1804
+ 'content-length': String(result.selectedBytes),
1805
+ 'content-disposition': artifactContentDisposition(result.filename, route.download),
1806
+ etag: `"${result.sha256}"`,
1807
+ 'x-artifact-content-length': String(result.selectedBytes),
1808
+ 'content-security-policy': "sandbox; default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
1809
+ 'referrer-policy': 'no-referrer',
1810
+ ...(result.contentRange === undefined ? {} : { 'content-range': result.contentRange })
1811
+ }, res[RESPONSE_RELEASE_ID]));
1812
+ if (metadataOnly) {
1813
+ res.end();
1814
+ return;
1815
+ }
1816
+ const streamDeadline = deadlineSignal(
1817
+ requestSignal,
1818
+ limits.visionBodyTimeoutMs,
1819
+ 'Agent artifact stream exceeded its delivery deadline'
1820
+ );
1821
+ try {
1822
+ await streamArtifactBytes(res, result.body, result.selectedBytes, streamDeadline.signal);
1823
+ } finally {
1824
+ streamDeadline.cleanup();
1825
+ }
1826
+ }
1827
+
1828
+ const handler = async (req, res) => {
1829
+ const disconnect = requestAbortController(req, res);
1830
+ res[RESPONSE_RELEASE_ID] = assets.releaseId;
1831
+ let releaseBody = null;
1832
+ let releaseStream = null;
1833
+ let outcomeRoute = 'unknown';
1834
+ let outcomeFetchMetadata = 'unchecked';
1835
+ let outcomeRelease = 'unchecked';
1836
+ let outcomeErrorCode = null;
1837
+ try {
1838
+ requirePublicAuthority(req, publicHost);
1839
+ const clientAddress = resolveTrustedClientAddress(req);
1840
+ if (req.method !== 'POST') rejectRequestBodyFraming(req);
1841
+ const route = classifyRequestTarget(req.url, assets);
1842
+ outcomeRoute = route.kind === 'chat' ? 'chat'
1843
+ : (route.kind === 'agent' ? 'agent'
1844
+ : (route.kind === 'agent_artifact' ? 'agent_artifact'
1845
+ : (route.pathname === CLOUD_ROUTES.login ? 'login'
1846
+ : (route.pathname === CLOUD_ROUTES.session ? 'session'
1847
+ : (route.pathname === CLOUD_ROUTES.logout ? 'logout'
1848
+ : (route.kind === 'asset' ? 'asset' : 'unknown'))))));
1849
+ if (route.kind === 'invalid' || route.kind === 'not_found') {
1850
+ if (req.method === 'POST' && hasRequestBodyFraming(req)) req.shouldKeepAlive = false;
1851
+ if (route.kind === 'invalid') throw new CloudHttpError(400, 'invalid_target', 'The request target is not normalized.');
1852
+ throw new CloudHttpError(404, 'not_found', 'The requested route does not exist.');
1853
+ }
1854
+ if (route.kind === 'asset') {
1855
+ if (!['GET', 'HEAD'].includes(req.method)) {
1856
+ if (hasRequestBodyFraming(req)) req.shouldKeepAlive = false;
1857
+ methodNotAllowed(req, res, 'GET, HEAD');
1858
+ return;
1859
+ }
1860
+ const asset = assets.get(route.target);
1861
+ const cacheControl = route.target === '/sw.js'
1862
+ ? 'no-store, no-cache, must-revalidate'
1863
+ : (assets.isImmutable(route.target) ? IMMUTABLE_CACHE_CONTROL : DYNAMIC_CACHE_CONTROL);
1864
+ sendBuffer(req, res, 200, asset.body, {
1865
+ ...commonSecurityHeaders(),
1866
+ ...asset.headers,
1867
+ 'content-type': asset.contentType,
1868
+ 'cache-control': cacheControl,
1869
+ ...(route.target === '/' ? {
1870
+ 'content-security-policy': "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' blob:; connect-src 'self'; font-src 'none'; manifest-src 'self'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; object-src 'none'; worker-src 'self'"
1871
+ } : {}),
1872
+ ...(route.target === '/sw.js' ? { pragma: 'no-cache', expires: '0', 'service-worker-allowed': '/' } : {})
1873
+ });
1874
+ return;
1875
+ }
1876
+ if (route.kind === 'agent_artifact') {
1877
+ if (!['GET', 'HEAD'].includes(req.method)) {
1878
+ if (hasRequestBodyFraming(req)) req.shouldKeepAlive = false;
1879
+ methodNotAllowed(req, res, 'GET, HEAD');
1880
+ return;
1881
+ }
1882
+ outcomeFetchMetadata = artifactFetchMetadataState(req);
1883
+ if (outcomeFetchMetadata === 'invalid') rejectFetchMetadata();
1884
+ outcomeRelease = artifactClientReleaseState(req, route.releaseId, assets.releaseId);
1885
+ if (outcomeRelease !== 'match') rejectClientRelease();
1886
+ const authenticated = requireAuthentication(await authenticate(req, { csrf: false }));
1887
+ releaseStream = streamGate.enter(authenticated.browserSession);
1888
+ if (!releaseStream) {
1889
+ throw new CloudHttpError(429, 'stream_rate_limited', 'Too many artifact streams are active.', { retryAfter: 2 });
1890
+ }
1891
+ await handleAgentArtifactContent(req, res, route, authenticated, disconnect.controller.signal);
1892
+ return;
1893
+ }
1894
+ if (req.method !== 'POST') {
1895
+ methodNotAllowed(req, res, 'POST');
1896
+ return;
1897
+ }
1898
+ requireOrigin(req, origin);
1899
+ outcomeFetchMetadata = fetchMetadataState(req);
1900
+ if (outcomeFetchMetadata === 'invalid') rejectFetchMetadata();
1901
+ let metadataAuthenticated = null;
1902
+ if (outcomeFetchMetadata !== 'complete' && (route.kind === 'chat' || route.kind === 'agent')) {
1903
+ try {
1904
+ metadataAuthenticated = await authenticate(req);
1905
+ } catch (error) {
1906
+ if (error instanceof CloudHttpError && error.code === 'csrf_rejected') rejectFetchMetadata();
1907
+ throw error;
1908
+ }
1909
+ if (!metadataAuthenticated) rejectFetchMetadata();
1910
+ }
1911
+ outcomeRelease = clientReleaseState(req, assets.releaseId);
1912
+ if (outcomeRelease === 'mismatch') rejectClientRelease();
1913
+ const preauthenticated = route.kind === 'chat' || route.kind === 'agent'
1914
+ ? requireAuthentication(metadataAuthenticated ?? await authenticate(req))
1915
+ : null;
1916
+ const admissionKey = preauthenticated
1917
+ ? `${clientAddress}\u0000${configuredAccount.principalId}`
1918
+ : clientAddress;
1919
+ releaseBody = bodyGate.enter(admissionKey);
1920
+ if (!releaseBody) throw new CloudHttpError(503, 'request_busy', 'The request service is temporarily busy.', { retryAfter: 1 });
1921
+ const body = await readJsonBody(
1922
+ req,
1923
+ bodyLimitForRoute(route.pathname),
1924
+ route.pathname === CLOUD_ROUTES.chatRunsStart
1925
+ ? limits.visionBodyTimeoutMs
1926
+ : limits.bodyTimeoutMs
1927
+ );
1928
+ releaseBody();
1929
+ releaseBody = null;
1930
+ const streamRoute = route.pathname === CLOUD_ROUTES.chatRunsEvents
1931
+ || (route.kind === 'agent' && route.nativeAgentPath === AGINTI_RPC_PATHS.runsEvents);
1932
+ if (streamRoute) {
1933
+ releaseStream = streamGate.enter(preauthenticated.browserSession);
1934
+ if (!releaseStream) {
1935
+ throw new CloudHttpError(429, 'stream_rate_limited', 'Too many event streams are active.', { retryAfter: 2 });
1936
+ }
1937
+ }
1938
+ if (route.pathname === CLOUD_ROUTES.login) {
1939
+ await handleLogin(req, res, validateLoginBody(body), disconnect.controller.signal, clientAddress);
1940
+ return;
1941
+ }
1942
+ if (route.pathname === CLOUD_ROUTES.session) {
1943
+ validateEmptyBody(body, 'session request');
1944
+ await handleSession(req, res);
1945
+ return;
1946
+ }
1947
+ if (route.pathname === CLOUD_ROUTES.logout) {
1948
+ validateEmptyBody(body, 'logout request');
1949
+ await handleLogout(req, res);
1950
+ return;
1951
+ }
1952
+ if (route.kind === 'chat') {
1953
+ await handleChat(req, res, route, body, preauthenticated, disconnect.controller.signal);
1954
+ return;
1955
+ }
1956
+ if (route.kind === 'agent') {
1957
+ await handleAgent(req, res, route, body, preauthenticated, disconnect.controller.signal);
1958
+ return;
1959
+ }
1960
+ throw new CloudHttpError(404, 'not_found', 'The requested route does not exist.');
1961
+ } catch (error) {
1962
+ const safe = publicError(error);
1963
+ outcomeErrorCode = safe.code;
1964
+ sendError(req, res, safe);
1965
+ } finally {
1966
+ releaseBody?.();
1967
+ releaseStream?.();
1968
+ disconnect.cleanup();
1969
+ if (requestOutcomeObserver !== undefined && req.method === 'POST') {
1970
+ try {
1971
+ const status = Number.isSafeInteger(res.statusCode) && res.statusCode >= 100 && res.statusCode <= 599
1972
+ ? res.statusCode
1973
+ : 500;
1974
+ requestOutcomeObserver(Object.freeze({
1975
+ schemaVersion: 1,
1976
+ timestamp: new Date(epochMilliseconds(clock)).toISOString(),
1977
+ route: outcomeRoute,
1978
+ status,
1979
+ result: status < 400 ? 'accepted' : 'rejected',
1980
+ errorCode: outcomeErrorCode,
1981
+ fetchMetadata: outcomeFetchMetadata,
1982
+ release: outcomeRelease
1983
+ }));
1984
+ } catch { /* Telemetry is bounded, best-effort, and never affects a request. */ }
1985
+ }
1986
+ }
1987
+ };
1988
+ Object.defineProperties(handler, {
1989
+ releaseId: { value: assets.releaseId, enumerable: true },
1990
+ closeBackgroundJobs: { value: closeGenerationJobs, enumerable: true },
1991
+ activeDirectChatJobs: { get: () => jobs.size, enumerable: true },
1992
+ activeStreams: { get: () => streamGate.active, enumerable: true },
1993
+ activeEphemeralSessions: { get: () => ephemeralSessions.size, enumerable: true },
1994
+ maximumBodyReadTimeoutMs: {
1995
+ value: Math.max(limits.bodyTimeoutMs, limits.visionBodyTimeoutMs)
1996
+ }
1997
+ });
1998
+ return handler;
1999
+ }
2000
+
2001
+ export function createCloudServer(options) {
2002
+ const handler = createCloudRequestHandler(options);
2003
+ const server = createNodeServer({
2004
+ connectionsCheckingInterval: 1_000,
2005
+ highWaterMark: 64 * 1024,
2006
+ insecureHTTPParser: false,
2007
+ joinDuplicateHeaders: false,
2008
+ keepAlive: true,
2009
+ keepAliveInitialDelay: 1_000,
2010
+ maxHeaderSize: 16 * 1024,
2011
+ noDelay: true,
2012
+ requireHostHeader: true,
2013
+ uniqueHeaders: ['content-length', 'content-type', 'cache-control']
2014
+ }, handler);
2015
+ server.headersTimeout = 10_000;
2016
+ // Node's outer request deadline begins before the route-level body reader.
2017
+ // Keep a bounded allowance for headers/authentication ahead of the longest
2018
+ // accepted body window so mobile vision uploads reach the stricter reader.
2019
+ server.requestTimeout = handler.maximumBodyReadTimeoutMs + REQUEST_BODY_TIMEOUT_MARGIN_MS;
2020
+ server.keepAliveTimeout = 5_000;
2021
+ server.maxHeadersCount = 64;
2022
+ server.on('close', () => { void handler.closeBackgroundJobs(); });
2023
+ let shutdownPromise = null;
2024
+ Object.defineProperty(server, 'shutdown', {
2025
+ enumerable: true,
2026
+ value() {
2027
+ if (shutdownPromise) return shutdownPromise;
2028
+ const drained = handler.closeBackgroundJobs();
2029
+ shutdownPromise = new Promise((resolve, reject) => {
2030
+ if (!server.listening) {
2031
+ resolve();
2032
+ return;
2033
+ }
2034
+ server.close((error) => error ? reject(error) : resolve());
2035
+ server.closeAllConnections?.();
2036
+ }).then(() => drained);
2037
+ return shutdownPromise;
2038
+ }
2039
+ });
2040
+ return server;
2041
+ }
2042
+
2043
+ export const CLOUD_AGENT_PUBLIC_ROUTES = Object.freeze(Object.keys(AGENT_ROUTE_MAP));