agent-shim 8.8.0 → 8.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.cjs +5927 -2018
- package/dist/index.cjs +1919 -1292
- package/dist/index.mjs +1908 -1294
- package/dist/types/index.d.ts +365 -21
- package/package.json +3 -1
package/dist/index.cjs
CHANGED
|
@@ -77,11 +77,16 @@ __export(index_exports, {
|
|
|
77
77
|
ProviderNotFoundError: () => ProviderNotFoundError,
|
|
78
78
|
ProviderSchema: () => ProviderSchema,
|
|
79
79
|
RC_IDLE_EXPIRY_MS: () => RC_IDLE_EXPIRY_MS,
|
|
80
|
+
RC_ORPC_PATH_PREFIX: () => RC_ORPC_PATH_PREFIX,
|
|
80
81
|
RC_PENDING_DEADLINE_MS: () => RC_PENDING_DEADLINE_MS,
|
|
81
82
|
RC_PERMISSION_MODES: () => RC_PERMISSION_MODES,
|
|
82
83
|
RC_REQUEST_PARSE_CAP_BYTES: () => RC_REQUEST_PARSE_CAP_BYTES,
|
|
83
84
|
RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
|
|
85
|
+
RC_STREAM_BACKOFF_MS: () => RC_STREAM_BACKOFF_MS,
|
|
84
86
|
ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
|
|
87
|
+
RcPermissionModeSchema: () => RcPermissionModeSchema,
|
|
88
|
+
RcStreamEnvelopeSchema: () => RcStreamEnvelopeSchema,
|
|
89
|
+
RcStreamEventSchema: () => RcStreamEventSchema,
|
|
85
90
|
UsageError: () => UsageError,
|
|
86
91
|
UsageSnapshotError: () => UsageSnapshotError,
|
|
87
92
|
UsageSnapshotSchema: () => UsageSnapshotSchema,
|
|
@@ -107,8 +112,13 @@ __export(index_exports, {
|
|
|
107
112
|
collectPoolPick: () => collectPoolPick,
|
|
108
113
|
createLeafCache: () => createLeafCache,
|
|
109
114
|
createProfile: () => createProfile,
|
|
115
|
+
createRcApiNodeHandler: () => createRcApiNodeHandler,
|
|
116
|
+
createRcApiRouter: () => createRcApiRouter,
|
|
110
117
|
createRcControlHandler: () => createRcControlHandler,
|
|
118
|
+
createRcEventFanout: () => createRcEventFanout,
|
|
111
119
|
createRcSessionTracker: () => createRcSessionTracker,
|
|
120
|
+
createRcStreamHub: () => createRcStreamHub,
|
|
121
|
+
createSseParser: () => createSseParser,
|
|
112
122
|
describeProviderEndpoint: () => describeProviderEndpoint,
|
|
113
123
|
detectAmbientCredential: () => detectAmbientCredential,
|
|
114
124
|
effectiveWindow: () => effectiveWindow,
|
|
@@ -121,6 +131,7 @@ __export(index_exports, {
|
|
|
121
131
|
formatCheckReport: () => formatCheckReport,
|
|
122
132
|
formatDoctorReport: () => formatDoctorReport,
|
|
123
133
|
forwardableHeaders: () => forwardableHeaders,
|
|
134
|
+
frontDoorRcApiClient: () => frontDoorRcApiClient,
|
|
124
135
|
frontDoorRcControl: () => frontDoorRcControl,
|
|
125
136
|
generateCa: () => generateCa,
|
|
126
137
|
identityExists: () => identityExists,
|
|
@@ -138,6 +149,7 @@ __export(index_exports, {
|
|
|
138
149
|
mintLeaf: () => mintLeaf,
|
|
139
150
|
observingRoutedRoute: () => observingRoutedRoute,
|
|
140
151
|
parseConnectTarget: () => parseConnectTarget,
|
|
152
|
+
parseRcStreamEnvelope: () => parseRcStreamEnvelope,
|
|
141
153
|
planOf: () => planOf,
|
|
142
154
|
prepareClaudeLaunch: () => prepareClaudeLaunch,
|
|
143
155
|
prepareLaunch: () => prepareLaunch,
|
|
@@ -159,6 +171,7 @@ __export(index_exports, {
|
|
|
159
171
|
realConnectEffects: () => realConnectEffects,
|
|
160
172
|
realRcControlTransport: () => realRcControlTransport,
|
|
161
173
|
realRcEventDial: () => realRcEventDial,
|
|
174
|
+
realRcStreamDial: () => realRcStreamDial,
|
|
162
175
|
recoverFarm: () => recoverFarm,
|
|
163
176
|
recoveryDiagnostics: () => recoveryDiagnostics,
|
|
164
177
|
removeDirectoryRule: () => removeDirectoryRule,
|
|
@@ -3267,1326 +3280,1916 @@ function realConnectCertStore(paths) {
|
|
|
3267
3280
|
// src/frontdoor/connectEffects.ts
|
|
3268
3281
|
var import_node_dns = __toESM(require("node:dns"), 1);
|
|
3269
3282
|
var http = __toESM(require("node:http"), 1);
|
|
3270
|
-
var
|
|
3283
|
+
var https2 = __toESM(require("node:https"), 1);
|
|
3271
3284
|
var net2 = __toESM(require("node:net"), 1);
|
|
3272
3285
|
var tls = __toESM(require("node:tls"), 1);
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
var
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3286
|
+
|
|
3287
|
+
// src/codex/http.ts
|
|
3288
|
+
var HTTP_STATUS = {
|
|
3289
|
+
ok: 200,
|
|
3290
|
+
badRequest: 400,
|
|
3291
|
+
unauthorized: 401,
|
|
3292
|
+
forbidden: 403,
|
|
3293
|
+
notFound: 404,
|
|
3294
|
+
methodNotAllowed: 405,
|
|
3295
|
+
payloadTooLarge: 413,
|
|
3296
|
+
tooManyRequests: 429,
|
|
3297
|
+
/** Not sent to anyone: the client is gone. Recorded for a response abandoned because the client disconnected, following the convention nginx uses for the same case. */
|
|
3298
|
+
clientClosedRequest: 499,
|
|
3299
|
+
internalServerError: 500,
|
|
3300
|
+
badGateway: 502
|
|
3301
|
+
};
|
|
3302
|
+
|
|
3303
|
+
// src/frontdoor/rcControl.ts
|
|
3304
|
+
var https = __toESM(require("node:https"), 1);
|
|
3305
|
+
|
|
3306
|
+
// src/frontdoor/rcSessions.ts
|
|
3307
|
+
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3308
|
+
var RC_SESSION_ID_PREFIX = "cse_";
|
|
3309
|
+
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3310
|
+
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3311
|
+
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3312
|
+
var RC_STREAM_WORKER_SOURCE = "worker";
|
|
3313
|
+
var RC_PENDING_DEADLINE_MS = 14e3;
|
|
3314
|
+
var RC_REQUEST_PARSE_CAP_BYTES = 262144;
|
|
3315
|
+
var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
|
|
3316
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3317
|
+
function isSuccessful(status) {
|
|
3318
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3281
3319
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3320
|
+
function singleHeader(headers, name) {
|
|
3321
|
+
const value = headers[name];
|
|
3322
|
+
return typeof value === "string" ? value : void 0;
|
|
3323
|
+
}
|
|
3324
|
+
var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
|
|
3325
|
+
function oauthAuthorizationOf(headers) {
|
|
3326
|
+
const value = singleHeader(headers, "authorization");
|
|
3327
|
+
return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
|
|
3328
|
+
}
|
|
3329
|
+
function sessionCreateId(body) {
|
|
3330
|
+
let parsed;
|
|
3331
|
+
try {
|
|
3332
|
+
parsed = JSON.parse(body);
|
|
3333
|
+
} catch {
|
|
3334
|
+
return void 0;
|
|
3286
3335
|
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
}
|
|
3297
|
-
callback(null, [{ address: first, family: 4 }]);
|
|
3298
|
-
});
|
|
3299
|
-
};
|
|
3300
|
-
var HTTP_PORT = 80;
|
|
3301
|
-
var HELD_PORT_ERRNOS = /* @__PURE__ */ new Set(["EADDRINUSE", "EADDRNOTAVAIL"]);
|
|
3302
|
-
async function dialOnce(connectOptions) {
|
|
3303
|
-
return await new Promise((resolve) => {
|
|
3304
|
-
const socket = net2.connect(connectOptions);
|
|
3305
|
-
socket.once("connect", () => {
|
|
3306
|
-
resolve({ error: null, socket });
|
|
3307
|
-
});
|
|
3308
|
-
socket.once("error", (error) => {
|
|
3309
|
-
resolve({ error, socket });
|
|
3310
|
-
});
|
|
3311
|
-
});
|
|
3336
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3337
|
+
return void 0;
|
|
3338
|
+
}
|
|
3339
|
+
const session = parsed.session;
|
|
3340
|
+
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3341
|
+
return void 0;
|
|
3342
|
+
}
|
|
3343
|
+
const id = session.id;
|
|
3344
|
+
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3312
3345
|
}
|
|
3313
|
-
function
|
|
3314
|
-
return
|
|
3346
|
+
function isRecord2(value) {
|
|
3347
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3315
3348
|
}
|
|
3316
|
-
function
|
|
3317
|
-
|
|
3349
|
+
function parseJsonObject(body) {
|
|
3350
|
+
let parsed;
|
|
3351
|
+
try {
|
|
3352
|
+
parsed = JSON.parse(body);
|
|
3353
|
+
} catch {
|
|
3354
|
+
return void 0;
|
|
3355
|
+
}
|
|
3356
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
3318
3357
|
}
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3358
|
+
function nonEmptyString(record, field) {
|
|
3359
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3360
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3361
|
+
}
|
|
3362
|
+
function finiteNumber(record, field) {
|
|
3363
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3364
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3365
|
+
}
|
|
3366
|
+
function excerpt(text) {
|
|
3367
|
+
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
3368
|
+
}
|
|
3369
|
+
function payloadEventsOf(body) {
|
|
3370
|
+
const parsed = parseJsonObject(body);
|
|
3371
|
+
const events = parsed === void 0 ? void 0 : parsed.events;
|
|
3372
|
+
if (!Array.isArray(events)) {
|
|
3373
|
+
return [];
|
|
3374
|
+
}
|
|
3375
|
+
const payloads = [];
|
|
3376
|
+
for (const event of events) {
|
|
3377
|
+
if (!isRecord2(event)) {
|
|
3378
|
+
continue;
|
|
3324
3379
|
}
|
|
3325
|
-
const
|
|
3326
|
-
if (!
|
|
3327
|
-
|
|
3380
|
+
const payload = event.payload;
|
|
3381
|
+
if (!isRecord2(payload)) {
|
|
3382
|
+
continue;
|
|
3328
3383
|
}
|
|
3384
|
+
payloads.push(payload);
|
|
3329
3385
|
}
|
|
3386
|
+
return payloads;
|
|
3330
3387
|
}
|
|
3331
|
-
function
|
|
3332
|
-
|
|
3333
|
-
}
|
|
3334
|
-
var ExemptHttpAgent = class extends http.Agent {
|
|
3335
|
-
// A reserved-port dial that has to retry cannot produce its socket synchronously, so it settles first and arrives through the callback (node's documented asynchronous createConnection form); a literal dial has nothing to retry and keeps node's own synchronous shape.
|
|
3336
|
-
createConnection(options, oncreate) {
|
|
3337
|
-
const target = dialTarget(options, HTTP_PORT);
|
|
3338
|
-
if (net2.isIP(target.host) !== 0) {
|
|
3339
|
-
return literalDial(target);
|
|
3340
|
-
}
|
|
3341
|
-
void reservedDial(target).then((outcome) => {
|
|
3342
|
-
oncreate?.(outcome.error, outcome.socket);
|
|
3343
|
-
});
|
|
3388
|
+
function pendingOfControlRequestPayload(payload) {
|
|
3389
|
+
if (payload.type !== "control_request") {
|
|
3344
3390
|
return void 0;
|
|
3345
3391
|
}
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
// The same split as the plain sibling: a literal dial wraps TLS around the socket node's own agent would have returned, a reserved dial settles (retries included) first and arrives through the callback, which is exactly where node's own agent wraps its own dials.
|
|
3349
|
-
createConnection(options, oncreate) {
|
|
3350
|
-
const target = dialTarget(options, HTTPS_PORT);
|
|
3351
|
-
if (net2.isIP(target.host) !== 0) {
|
|
3352
|
-
return tls.connect({ socket: literalDial(target), servername: requestServername(options, target.host), ...tlsFacts(options) });
|
|
3353
|
-
}
|
|
3354
|
-
void reservedDial(target).then((outcome) => {
|
|
3355
|
-
if (outcome.error !== null) {
|
|
3356
|
-
oncreate?.(outcome.error, outcome.socket);
|
|
3357
|
-
return;
|
|
3358
|
-
}
|
|
3359
|
-
oncreate?.(null, tls.connect({ socket: outcome.socket, servername: requestServername(options, target.host), ...tlsFacts(options) }));
|
|
3360
|
-
});
|
|
3392
|
+
const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3393
|
+
if (requestId === void 0) {
|
|
3361
3394
|
return void 0;
|
|
3362
3395
|
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
const
|
|
3366
|
-
|
|
3367
|
-
|
|
3396
|
+
const request4 = isRecord2(payload.request) ? payload.request : void 0;
|
|
3397
|
+
const subtype = nonEmptyString(request4, "subtype");
|
|
3398
|
+
const type = subtype ?? "control_request";
|
|
3399
|
+
let summary = "";
|
|
3400
|
+
if (subtype === "can_use_tool") {
|
|
3401
|
+
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3402
|
+
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3403
|
+
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3404
|
+
} else if (request4 !== void 0) {
|
|
3405
|
+
summary = excerpt(JSON.stringify(request4));
|
|
3368
3406
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3407
|
+
return { requestId, type, summary };
|
|
3408
|
+
}
|
|
3409
|
+
function answeredRequestIdsOfControlResponsePayload(payload) {
|
|
3410
|
+
if (payload.type !== "control_response") {
|
|
3411
|
+
return [];
|
|
3371
3412
|
}
|
|
3372
|
-
|
|
3413
|
+
const response = isRecord2(payload.response) ? payload.response : void 0;
|
|
3414
|
+
const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3415
|
+
return id === void 0 ? [] : [id];
|
|
3373
3416
|
}
|
|
3374
|
-
function
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
};
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3417
|
+
function parseRcRequestBody(kind, body) {
|
|
3418
|
+
if (kind === "heartbeat") {
|
|
3419
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
|
|
3420
|
+
}
|
|
3421
|
+
if (kind === "worker-registration") {
|
|
3422
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
|
|
3423
|
+
}
|
|
3424
|
+
const controlRequests = [];
|
|
3425
|
+
const answeredRequestIds = [];
|
|
3426
|
+
for (const payload of payloadEventsOf(body)) {
|
|
3427
|
+
const pending = pendingOfControlRequestPayload(payload);
|
|
3428
|
+
if (pending !== void 0) {
|
|
3429
|
+
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3430
|
+
continue;
|
|
3431
|
+
}
|
|
3432
|
+
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3433
|
+
}
|
|
3434
|
+
return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
|
|
3435
|
+
}
|
|
3436
|
+
function createRcSessionTracker(deps) {
|
|
3437
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3438
|
+
const expire = (id) => {
|
|
3439
|
+
entries.delete(id);
|
|
3440
|
+
};
|
|
3441
|
+
const expirePendings = (entry, now) => {
|
|
3442
|
+
for (const [requestId, pending] of entry.pending) {
|
|
3443
|
+
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3444
|
+
entry.pending.delete(requestId);
|
|
3390
3445
|
}
|
|
3391
|
-
|
|
3392
|
-
|
|
3446
|
+
}
|
|
3447
|
+
};
|
|
3448
|
+
const sweep = () => {
|
|
3449
|
+
for (const [id, entry] of entries) {
|
|
3450
|
+
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3451
|
+
expire(id);
|
|
3452
|
+
continue;
|
|
3393
3453
|
}
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3454
|
+
expirePendings(entry, deps.now());
|
|
3455
|
+
}
|
|
3456
|
+
};
|
|
3457
|
+
const timer = setInterval(sweep, deps.idleMs);
|
|
3458
|
+
timer.unref();
|
|
3459
|
+
const birth = (id, facts) => {
|
|
3460
|
+
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0, sequenceNum: void 0 };
|
|
3461
|
+
entries.set(id, record);
|
|
3462
|
+
return record;
|
|
3463
|
+
};
|
|
3464
|
+
const observeExchange = (request4) => {
|
|
3465
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
3466
|
+
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3467
|
+
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3468
|
+
return void 0;
|
|
3469
|
+
}
|
|
3470
|
+
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3471
|
+
const facts = {
|
|
3472
|
+
oauthAuthorization: oauthAuthorizationOf(request4.headers),
|
|
3473
|
+
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3474
|
+
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3475
|
+
};
|
|
3476
|
+
if (tail === "") {
|
|
3477
|
+
if (method !== "POST") {
|
|
3478
|
+
return void 0;
|
|
3479
|
+
}
|
|
3480
|
+
let accepted2 = false;
|
|
3481
|
+
let buffered2 = 0;
|
|
3482
|
+
let body = "";
|
|
3483
|
+
return {
|
|
3484
|
+
onResponse: (status) => {
|
|
3485
|
+
accepted2 = isSuccessful(status);
|
|
3486
|
+
},
|
|
3487
|
+
onBodyChunk: (chunk) => {
|
|
3488
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3489
|
+
return;
|
|
3490
|
+
}
|
|
3491
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
3492
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3493
|
+
buffered2 = body.length;
|
|
3494
|
+
},
|
|
3495
|
+
onEnd: () => {
|
|
3496
|
+
if (!accepted2) {
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
const id2 = sessionCreateId(body);
|
|
3500
|
+
if (id2 === void 0) {
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
birth(id2, facts);
|
|
3504
|
+
}
|
|
3505
|
+
};
|
|
3506
|
+
}
|
|
3507
|
+
const rest = tail.slice(1);
|
|
3508
|
+
const [id] = rest.split("/");
|
|
3509
|
+
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3510
|
+
return void 0;
|
|
3511
|
+
}
|
|
3512
|
+
const entry = entries.get(id) ?? birth(id, facts);
|
|
3513
|
+
entry.lastSeenAt = deps.now();
|
|
3514
|
+
entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
|
|
3515
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3516
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3517
|
+
const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
|
|
3518
|
+
let chunks = [];
|
|
3519
|
+
let buffered = 0;
|
|
3520
|
+
let oversized = false;
|
|
3521
|
+
let requestEnded = false;
|
|
3522
|
+
let accepted;
|
|
3523
|
+
let filed = false;
|
|
3524
|
+
const file = () => {
|
|
3525
|
+
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3526
|
+
return;
|
|
3527
|
+
}
|
|
3528
|
+
filed = true;
|
|
3529
|
+
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3530
|
+
chunks = [];
|
|
3531
|
+
const entryNow = entries.get(id);
|
|
3532
|
+
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
const now = deps.now();
|
|
3536
|
+
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3537
|
+
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3538
|
+
}
|
|
3539
|
+
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3540
|
+
entryNow.pending.delete(requestId);
|
|
3541
|
+
}
|
|
3542
|
+
if (factsOfBody.idleSeconds !== void 0) {
|
|
3543
|
+
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3544
|
+
}
|
|
3545
|
+
if (factsOfBody.workerStatus !== void 0) {
|
|
3546
|
+
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3550
|
+
return {
|
|
3551
|
+
...bodyKind === void 0 ? {} : {
|
|
3552
|
+
onRequestChunk: (chunk) => {
|
|
3553
|
+
if (oversized) {
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
chunks.push(Buffer.from(chunk));
|
|
3557
|
+
buffered += chunk.length;
|
|
3558
|
+
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3559
|
+
oversized = true;
|
|
3560
|
+
chunks = [];
|
|
3561
|
+
}
|
|
3562
|
+
},
|
|
3563
|
+
onRequestEnd: () => {
|
|
3564
|
+
requestEnded = true;
|
|
3565
|
+
file();
|
|
3566
|
+
}
|
|
3567
|
+
},
|
|
3568
|
+
onResponse: (status, headers) => {
|
|
3569
|
+
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3570
|
+
expire(id);
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
accepted = isSuccessful(status);
|
|
3574
|
+
file();
|
|
3575
|
+
},
|
|
3576
|
+
onBodyChunk: () => {
|
|
3577
|
+
},
|
|
3578
|
+
onEnd: () => {
|
|
3579
|
+
}
|
|
3580
|
+
};
|
|
3581
|
+
};
|
|
3582
|
+
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3583
|
+
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3584
|
+
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3585
|
+
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3586
|
+
return {
|
|
3587
|
+
observeExchange,
|
|
3588
|
+
list: () => {
|
|
3589
|
+
sweep();
|
|
3590
|
+
return sortedEntries().map(summaryOf);
|
|
3591
|
+
},
|
|
3592
|
+
statusOf: (sessionId) => {
|
|
3593
|
+
sweep();
|
|
3594
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3595
|
+
},
|
|
3596
|
+
pendingOf: (sessionId) => {
|
|
3597
|
+
sweep();
|
|
3598
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3599
|
+
},
|
|
3600
|
+
credentialOf: (sessionId) => {
|
|
3601
|
+
const entry = entries.get(sessionId);
|
|
3602
|
+
return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3603
|
+
},
|
|
3604
|
+
noteSequenceNums: (sessionId, sequenceNums) => {
|
|
3605
|
+
const entry = entries.get(sessionId);
|
|
3606
|
+
if (entry === void 0) {
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
for (const sequenceNum of sequenceNums) {
|
|
3610
|
+
entry.sequenceNum = entry.sequenceNum === void 0 || sequenceNum > entry.sequenceNum ? sequenceNum : entry.sequenceNum;
|
|
3611
|
+
}
|
|
3612
|
+
},
|
|
3613
|
+
sequenceNumOf: (sessionId) => entries.get(sessionId)?.sequenceNum,
|
|
3614
|
+
fileStreamEvent: (sessionId, event) => {
|
|
3615
|
+
const entry = entries.get(sessionId);
|
|
3616
|
+
if (entry === void 0) {
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
entry.sequenceNum = entry.sequenceNum === void 0 || event.sequence_num > entry.sequenceNum ? event.sequence_num : entry.sequenceNum;
|
|
3620
|
+
if (!isRecord2(event.payload)) {
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
const pending = pendingOfControlRequestPayload(event.payload);
|
|
3624
|
+
if (pending !== void 0) {
|
|
3625
|
+
if (event.source === RC_STREAM_WORKER_SOURCE) {
|
|
3626
|
+
entry.pending.set(pending.requestId, { sessionId, ...pending, observedAt: deps.now() });
|
|
3627
|
+
}
|
|
3628
|
+
return;
|
|
3629
|
+
}
|
|
3630
|
+
for (const requestId of answeredRequestIdsOfControlResponsePayload(event.payload)) {
|
|
3631
|
+
entry.pending.delete(requestId);
|
|
3632
|
+
}
|
|
3633
|
+
},
|
|
3634
|
+
completePending: (sessionId, requestId) => {
|
|
3635
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3636
|
+
},
|
|
3637
|
+
close: () => {
|
|
3638
|
+
clearInterval(timer);
|
|
3639
|
+
}
|
|
3640
|
+
};
|
|
3641
|
+
}
|
|
3642
|
+
function observingRoutedRoute(route, tracker, hooks) {
|
|
3643
|
+
return {
|
|
3644
|
+
...route,
|
|
3645
|
+
serve: async (request4, response) => {
|
|
3646
|
+
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3647
|
+
if (observed?.onRequestChunk !== void 0) {
|
|
3648
|
+
request4.body.on("data", (chunk) => {
|
|
3649
|
+
observed.onRequestChunk?.(chunk);
|
|
3398
3650
|
});
|
|
3399
|
-
|
|
3400
|
-
|
|
3651
|
+
request4.body.on("end", () => {
|
|
3652
|
+
observed.onRequestEnd?.();
|
|
3401
3653
|
});
|
|
3402
|
-
}
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
})
|
|
3654
|
+
}
|
|
3655
|
+
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed, hooks?.onExchangeSettled));
|
|
3656
|
+
}
|
|
3406
3657
|
};
|
|
3407
3658
|
}
|
|
3408
|
-
function
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
}
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3659
|
+
function teeRoutedResponse(response, observer, onSettled) {
|
|
3660
|
+
return {
|
|
3661
|
+
get headersSent() {
|
|
3662
|
+
return response.headersSent;
|
|
3663
|
+
},
|
|
3664
|
+
start: (status, headers) => {
|
|
3665
|
+
observer.onResponse(status, headers);
|
|
3666
|
+
response.start(status, headers);
|
|
3667
|
+
},
|
|
3668
|
+
flush: () => {
|
|
3669
|
+
response.flush();
|
|
3670
|
+
},
|
|
3671
|
+
write: async (chunk) => {
|
|
3672
|
+
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3673
|
+
await response.write(chunk);
|
|
3674
|
+
},
|
|
3675
|
+
end: () => {
|
|
3676
|
+
response.end();
|
|
3677
|
+
observer.onEnd();
|
|
3678
|
+
onSettled?.();
|
|
3679
|
+
},
|
|
3680
|
+
destroy: () => {
|
|
3681
|
+
response.destroy();
|
|
3682
|
+
observer.onEnd();
|
|
3683
|
+
onSettled?.();
|
|
3684
|
+
}
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
function rcObserverAsPassthrough(observer) {
|
|
3688
|
+
return {
|
|
3689
|
+
onRequestChunk: (chunk) => {
|
|
3690
|
+
observer.onRequestChunk?.(chunk);
|
|
3691
|
+
},
|
|
3692
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3693
|
+
onRequestEnd: () => {
|
|
3694
|
+
observer.onRequestEnd?.();
|
|
3695
|
+
}
|
|
3696
|
+
},
|
|
3697
|
+
onResponse: (status, headers) => {
|
|
3698
|
+
observer.onResponse(status, headers);
|
|
3699
|
+
},
|
|
3700
|
+
onResponseChunk: (chunk) => {
|
|
3701
|
+
observer.onBodyChunk(chunk);
|
|
3702
|
+
},
|
|
3703
|
+
onEnd: () => {
|
|
3704
|
+
observer.onEnd();
|
|
3705
|
+
}
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3709
|
+
return {
|
|
3710
|
+
type: "user",
|
|
3711
|
+
uuid,
|
|
3712
|
+
session_id: sessionId,
|
|
3713
|
+
parent_tool_use_id: null,
|
|
3714
|
+
message: { role: "user", content: text }
|
|
3715
|
+
};
|
|
3716
|
+
}
|
|
3717
|
+
function buildRcEventWriteBody(payload) {
|
|
3718
|
+
return { events: [{ payload }] };
|
|
3719
|
+
}
|
|
3720
|
+
function sequenceNumsOf(body) {
|
|
3721
|
+
let parsed;
|
|
3722
|
+
try {
|
|
3723
|
+
parsed = JSON.parse(body);
|
|
3724
|
+
} catch {
|
|
3725
|
+
return void 0;
|
|
3726
|
+
}
|
|
3727
|
+
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3728
|
+
return void 0;
|
|
3729
|
+
}
|
|
3730
|
+
const sequenceNums = [];
|
|
3731
|
+
const results = parsed.results;
|
|
3732
|
+
for (const result of results) {
|
|
3733
|
+
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3734
|
+
return void 0;
|
|
3735
|
+
}
|
|
3736
|
+
const sequenceNum = result.sequence_num;
|
|
3737
|
+
if (typeof sequenceNum === "number") {
|
|
3738
|
+
sequenceNums.push(sequenceNum);
|
|
3739
|
+
} else if (typeof sequenceNum === "string" && /^[0-9]+$/.test(sequenceNum)) {
|
|
3740
|
+
sequenceNums.push(Number(sequenceNum));
|
|
3741
|
+
} else {
|
|
3742
|
+
return void 0;
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3746
|
+
}
|
|
3747
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3748
|
+
if (!isSuccessful(answer.status)) {
|
|
3749
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3750
|
+
return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
|
|
3751
|
+
}
|
|
3752
|
+
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3753
|
+
}
|
|
3754
|
+
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3755
|
+
if (sequenceNums === void 0) {
|
|
3756
|
+
return { ok: false, message: `the API accepted the injected event but its answer named no results[].sequence_num, so delivery cannot be confirmed: ${answer.body}` };
|
|
3757
|
+
}
|
|
3758
|
+
return { ok: true, sequenceNums };
|
|
3759
|
+
}
|
|
3760
|
+
function noOAuthCredentialResult(sessionId) {
|
|
3761
|
+
return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
|
|
3762
|
+
}
|
|
3763
|
+
function rcSessionNotObservedMessage(sessionId) {
|
|
3764
|
+
return `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)`;
|
|
3765
|
+
}
|
|
3766
|
+
function rcWriteHeaders(credential) {
|
|
3767
|
+
const headers = { "content-type": "application/json" };
|
|
3768
|
+
if (credential.authorization !== void 0) {
|
|
3769
|
+
headers.authorization = credential.authorization;
|
|
3770
|
+
}
|
|
3771
|
+
if (credential.anthropicVersion !== void 0) {
|
|
3772
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3773
|
+
}
|
|
3774
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
3775
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3776
|
+
}
|
|
3777
|
+
return headers;
|
|
3778
|
+
}
|
|
3779
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3780
|
+
const credential = deps.credentialOf(sessionId);
|
|
3781
|
+
if (credential === void 0) {
|
|
3782
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3783
|
+
}
|
|
3784
|
+
if (credential.authorization === void 0) {
|
|
3785
|
+
return noOAuthCredentialResult(sessionId);
|
|
3786
|
+
}
|
|
3787
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3788
|
+
let answer;
|
|
3789
|
+
try {
|
|
3790
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3791
|
+
} catch (error) {
|
|
3792
|
+
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3793
|
+
}
|
|
3794
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3795
|
+
if (result.ok) {
|
|
3796
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3797
|
+
}
|
|
3798
|
+
return result;
|
|
3799
|
+
}
|
|
3800
|
+
function buildRcControlResponsePayload(requestId, decision) {
|
|
3801
|
+
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
3802
|
+
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
3803
|
+
}
|
|
3804
|
+
var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
|
|
3805
|
+
function isRcPermissionMode(value) {
|
|
3806
|
+
return RC_PERMISSION_MODES.some((mode) => mode === value);
|
|
3807
|
+
}
|
|
3808
|
+
function buildRcInterruptPayload(requestId) {
|
|
3809
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
|
|
3810
|
+
}
|
|
3811
|
+
function buildRcSetModelPayload(requestId, model) {
|
|
3812
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
|
|
3813
|
+
}
|
|
3814
|
+
function buildRcSetPermissionModePayload(requestId, mode) {
|
|
3815
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
|
|
3816
|
+
}
|
|
3817
|
+
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
3818
|
+
const credential = deps.credentialOf(sessionId);
|
|
3819
|
+
if (credential === void 0) {
|
|
3820
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3821
|
+
}
|
|
3822
|
+
if (credential.authorization === void 0) {
|
|
3823
|
+
return noOAuthCredentialResult(sessionId);
|
|
3824
|
+
}
|
|
3825
|
+
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
3826
|
+
return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
|
|
3827
|
+
}
|
|
3828
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
3829
|
+
let answer;
|
|
3830
|
+
try {
|
|
3831
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3832
|
+
} catch (error) {
|
|
3833
|
+
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3834
|
+
}
|
|
3835
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3836
|
+
if (result.ok) {
|
|
3837
|
+
deps.completePending(sessionId, requestId);
|
|
3838
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3839
|
+
}
|
|
3840
|
+
return result;
|
|
3841
|
+
}
|
|
3842
|
+
async function interruptRcSession(deps, sessionId) {
|
|
3843
|
+
const credential = deps.credentialOf(sessionId);
|
|
3844
|
+
if (credential === void 0) {
|
|
3845
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3846
|
+
}
|
|
3847
|
+
if (credential.authorization === void 0) {
|
|
3848
|
+
return noOAuthCredentialResult(sessionId);
|
|
3849
|
+
}
|
|
3850
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
|
|
3851
|
+
let answer;
|
|
3852
|
+
try {
|
|
3853
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3854
|
+
} catch (error) {
|
|
3855
|
+
return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3856
|
+
}
|
|
3857
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3858
|
+
if (result.ok) {
|
|
3859
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3860
|
+
}
|
|
3861
|
+
return result;
|
|
3862
|
+
}
|
|
3863
|
+
async function setRcSessionModel(deps, sessionId, model) {
|
|
3864
|
+
if (model === "") {
|
|
3865
|
+
return { ok: false, message: "a set-model write needs a non-empty model id: the SDK's own field treats an omitted or null value as a reset to the session default, which this operation never sends, and an empty string is neither" };
|
|
3866
|
+
}
|
|
3867
|
+
const credential = deps.credentialOf(sessionId);
|
|
3868
|
+
if (credential === void 0) {
|
|
3869
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3870
|
+
}
|
|
3871
|
+
if (credential.authorization === void 0) {
|
|
3872
|
+
return noOAuthCredentialResult(sessionId);
|
|
3873
|
+
}
|
|
3874
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
|
|
3875
|
+
let answer;
|
|
3876
|
+
try {
|
|
3877
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3878
|
+
} catch (error) {
|
|
3879
|
+
return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3880
|
+
}
|
|
3881
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3882
|
+
if (result.ok) {
|
|
3883
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3884
|
+
}
|
|
3885
|
+
return result;
|
|
3886
|
+
}
|
|
3887
|
+
async function setRcSessionPermissionMode(deps, sessionId, mode) {
|
|
3888
|
+
const credential = deps.credentialOf(sessionId);
|
|
3889
|
+
if (credential === void 0) {
|
|
3890
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3891
|
+
}
|
|
3892
|
+
if (credential.authorization === void 0) {
|
|
3893
|
+
return noOAuthCredentialResult(sessionId);
|
|
3894
|
+
}
|
|
3895
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
|
|
3896
|
+
let answer;
|
|
3897
|
+
try {
|
|
3898
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3899
|
+
} catch (error) {
|
|
3900
|
+
return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3901
|
+
}
|
|
3902
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3903
|
+
if (result.ok) {
|
|
3904
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3905
|
+
}
|
|
3906
|
+
return result;
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
// src/frontdoor/rcControl.ts
|
|
3910
|
+
var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
|
|
3911
|
+
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3912
|
+
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
3913
|
+
function answerJson(response, status, body) {
|
|
3914
|
+
const text = JSON.stringify(body);
|
|
3915
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3916
|
+
response.end(text);
|
|
3917
|
+
}
|
|
3918
|
+
async function readBody(request4) {
|
|
3919
|
+
return await new Promise((resolve, reject) => {
|
|
3920
|
+
const chunks = [];
|
|
3921
|
+
let total = 0;
|
|
3922
|
+
request4.on("data", (chunk) => {
|
|
3923
|
+
total += chunk.length;
|
|
3924
|
+
if (total > CONTROL_BODY_CAP_BYTES) {
|
|
3925
|
+
request4.destroy();
|
|
3926
|
+
reject(new Error("too large"));
|
|
3426
3927
|
return;
|
|
3427
3928
|
}
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
for (const socket of accepted) {
|
|
3436
|
-
socket.destroy();
|
|
3437
|
-
}
|
|
3438
|
-
});
|
|
3439
|
-
}
|
|
3440
|
-
});
|
|
3929
|
+
chunks.push(chunk);
|
|
3930
|
+
});
|
|
3931
|
+
request4.on("end", () => {
|
|
3932
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
3933
|
+
});
|
|
3934
|
+
request4.on("error", () => {
|
|
3935
|
+
reject(new Error("the request body ended early"));
|
|
3441
3936
|
});
|
|
3442
3937
|
});
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3938
|
+
}
|
|
3939
|
+
function createRcControlHandler(deps) {
|
|
3940
|
+
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
3941
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
3942
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3943
|
+
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3944
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3945
|
+
const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
|
|
3946
|
+
const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
|
|
3947
|
+
const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
|
|
3948
|
+
return (request4, response) => {
|
|
3949
|
+
const presented = request4.headers.authorization;
|
|
3950
|
+
const bearerPrefix = "bearer ".length;
|
|
3951
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
3952
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
3953
|
+
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
3957
|
+
const handle = async () => {
|
|
3958
|
+
if (url.pathname === sessionsPath) {
|
|
3959
|
+
if (request4.method !== "GET") {
|
|
3960
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3964
|
+
return;
|
|
3965
|
+
}
|
|
3966
|
+
if (url.pathname === statusPath || url.pathname === pendingPath) {
|
|
3967
|
+
if (request4.method !== "GET") {
|
|
3968
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
3969
|
+
return;
|
|
3970
|
+
}
|
|
3971
|
+
const session = url.searchParams.get("session") ?? void 0;
|
|
3972
|
+
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
3973
|
+
answerJson(response, HTTP_STATUS.notFound, { error: rcSessionNotObservedMessage(session) });
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
3977
|
+
return;
|
|
3978
|
+
}
|
|
3979
|
+
if (url.pathname === injectPath) {
|
|
3980
|
+
if (request4.method !== "POST") {
|
|
3981
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
let parsed;
|
|
3446
3985
|
try {
|
|
3447
|
-
|
|
3986
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3448
3987
|
} catch {
|
|
3988
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
3992
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
const result = await deps.inject(parsed.session, parsed.text);
|
|
3996
|
+
if (result.ok) {
|
|
3997
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3998
|
+
return;
|
|
3449
3999
|
}
|
|
4000
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4001
|
+
return;
|
|
3450
4002
|
}
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
const server = tls.createServer(
|
|
3456
|
-
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
3457
|
-
(secure) => {
|
|
3458
|
-
onSecure(secure, pendingContext);
|
|
4003
|
+
if (url.pathname === answerPath) {
|
|
4004
|
+
if (request4.method !== "POST") {
|
|
4005
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
|
|
4006
|
+
return;
|
|
3459
4007
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
accept: (socket, context) => {
|
|
3467
|
-
pendingContext = context;
|
|
3468
|
-
server.emit("connection", socket);
|
|
3469
|
-
},
|
|
3470
|
-
close: () => {
|
|
3471
|
-
server.close();
|
|
4008
|
+
let parsed;
|
|
4009
|
+
try {
|
|
4010
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4011
|
+
} catch {
|
|
4012
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
|
|
4013
|
+
return;
|
|
3472
4014
|
}
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
const contexts = new Map(
|
|
3477
|
-
hosts.map((host) => {
|
|
3478
|
-
const leaf = leafFor(host);
|
|
3479
|
-
return [host, tls.createSecureContext({ key: leaf.keyPem, cert: leaf.certPem })];
|
|
3480
|
-
})
|
|
3481
|
-
);
|
|
3482
|
-
const server = tls.createServer(
|
|
3483
|
-
{
|
|
3484
|
-
// No default key or cert exists, so the SNI selection is the only source of credentials: an unserved name fails its handshake here, and a client that sent no SNI at all never reaches the selection and fails for want of any certificate, which is exactly the fail-closed shape this surface wants.
|
|
3485
|
-
SNICallback: (servername, callback) => {
|
|
3486
|
-
const context = contexts.get(servername);
|
|
3487
|
-
callback(context === void 0 ? new Error(`the transparent surface serves no host named ${servername}`) : null, context);
|
|
3488
|
-
}
|
|
3489
|
-
},
|
|
3490
|
-
(secure) => {
|
|
3491
|
-
onSecure(secure, typeof secure.servername === "string" ? secure.servername : "");
|
|
4015
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("request" in parsed) || !("approve" in parsed) || typeof parsed.session !== "string" || typeof parsed.request !== "string" || typeof parsed.approve !== "boolean" || parsed.session === "" || parsed.request === "") {
|
|
4016
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
|
|
4017
|
+
return;
|
|
3492
4018
|
}
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
return {
|
|
3498
|
-
// The same hand-off the single-leaf acceptor uses: emitting the connection event runs the TLS server's own connection listener on a socket this process already owns (the redirect half of the connection) rather than one the server accepted itself.
|
|
3499
|
-
accept: (socket) => {
|
|
3500
|
-
server.emit("connection", socket);
|
|
3501
|
-
},
|
|
3502
|
-
close: () => {
|
|
3503
|
-
server.close();
|
|
4019
|
+
const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
|
|
4020
|
+
if (parsed.approve && text !== void 0 && text !== "") {
|
|
4021
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
|
|
4022
|
+
return;
|
|
3504
4023
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
if (onUpgrade !== void 0) {
|
|
3513
|
-
server.on("upgrade", (request4, socket, head) => {
|
|
3514
|
-
onUpgrade(request4, socket, head);
|
|
3515
|
-
});
|
|
4024
|
+
const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
|
|
4025
|
+
if (result.ok) {
|
|
4026
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
4027
|
+
return;
|
|
4028
|
+
}
|
|
4029
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4030
|
+
return;
|
|
3516
4031
|
}
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
close: () => {
|
|
3522
|
-
server.close();
|
|
3523
|
-
server.closeAllConnections();
|
|
4032
|
+
if (url.pathname === interruptPath) {
|
|
4033
|
+
if (request4.method !== "POST") {
|
|
4034
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
|
|
4035
|
+
return;
|
|
3524
4036
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
4037
|
+
let parsed;
|
|
4038
|
+
try {
|
|
4039
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4040
|
+
} catch {
|
|
4041
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
|
|
4042
|
+
return;
|
|
4043
|
+
}
|
|
4044
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
|
|
4045
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
|
|
4046
|
+
return;
|
|
4047
|
+
}
|
|
4048
|
+
const result = await deps.interrupt(parsed.session);
|
|
4049
|
+
if (result.ok) {
|
|
4050
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4054
|
+
return;
|
|
3531
4055
|
}
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
});
|
|
3537
|
-
secure.once("error", reject);
|
|
3538
|
-
});
|
|
3539
|
-
},
|
|
3540
|
-
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
3541
|
-
const socket = net2.connect({ host, port });
|
|
3542
|
-
socket.once("connect", () => {
|
|
3543
|
-
resolve(socket);
|
|
3544
|
-
});
|
|
3545
|
-
socket.once("error", reject);
|
|
3546
|
-
}),
|
|
3547
|
-
forwardHttp: (target, request4, response, observer) => {
|
|
3548
|
-
const headers = forwardableHeaders(request4.headers);
|
|
3549
|
-
const options = {
|
|
3550
|
-
host: target.host,
|
|
3551
|
-
port: target.port,
|
|
3552
|
-
lookup: realAddressLookup,
|
|
3553
|
-
method: request4.method,
|
|
3554
|
-
path: request4.url,
|
|
3555
|
-
headers,
|
|
3556
|
-
agent: target.tls ? tlsAgent : plainAgent
|
|
3557
|
-
};
|
|
3558
|
-
if (target.tls) {
|
|
3559
|
-
options.servername = target.servername ?? target.host;
|
|
3560
|
-
if (target.ca !== void 0) {
|
|
3561
|
-
options.ca = [...target.ca];
|
|
4056
|
+
if (url.pathname === setModelPath) {
|
|
4057
|
+
if (request4.method !== "POST") {
|
|
4058
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
|
|
4059
|
+
return;
|
|
3562
4060
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
4061
|
+
let parsed;
|
|
4062
|
+
try {
|
|
4063
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4064
|
+
} catch {
|
|
4065
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
|
|
4069
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
const result = await deps.setModel(parsed.session, parsed.model);
|
|
4073
|
+
if (result.ok) {
|
|
4074
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
|
|
4075
|
+
return;
|
|
3565
4076
|
}
|
|
4077
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4078
|
+
return;
|
|
3566
4079
|
}
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
if (observer !== void 0) {
|
|
3572
|
-
upstreamResponse.on("data", (chunk) => {
|
|
3573
|
-
observer.onResponseChunk(chunk);
|
|
3574
|
-
});
|
|
4080
|
+
if (url.pathname === setPermissionModePath) {
|
|
4081
|
+
if (request4.method !== "POST") {
|
|
4082
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
|
|
4083
|
+
return;
|
|
3575
4084
|
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
response.destroy(error);
|
|
4085
|
+
let parsed;
|
|
4086
|
+
try {
|
|
4087
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4088
|
+
} catch {
|
|
4089
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
|
|
3582
4090
|
return;
|
|
3583
4091
|
}
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
});
|
|
3588
|
-
if (observer !== void 0) {
|
|
3589
|
-
request4.on("data", (chunk) => {
|
|
3590
|
-
observer.onRequestChunk(chunk);
|
|
3591
|
-
});
|
|
3592
|
-
if (observer.onRequestEnd !== void 0) {
|
|
3593
|
-
request4.on("end", () => {
|
|
3594
|
-
observer.onRequestEnd?.();
|
|
3595
|
-
});
|
|
4092
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
|
|
4093
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` });
|
|
4094
|
+
return;
|
|
3596
4095
|
}
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
4096
|
+
const result = await deps.setPermissionMode(parsed.session, parsed.mode);
|
|
4097
|
+
if (result.ok) {
|
|
4098
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4102
|
+
return;
|
|
3600
4103
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
}
|
|
4104
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
|
|
4105
|
+
};
|
|
4106
|
+
handle().catch((error) => {
|
|
4107
|
+
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
4108
|
+
});
|
|
3607
4109
|
};
|
|
3608
4110
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
const value = headers[name];
|
|
3641
|
-
return typeof value === "string" ? value : void 0;
|
|
3642
|
-
}
|
|
3643
|
-
var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
|
|
3644
|
-
function oauthAuthorizationOf(headers) {
|
|
3645
|
-
const value = singleHeader(headers, "authorization");
|
|
3646
|
-
return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
|
|
4111
|
+
function realRcControlTransport(port, ca) {
|
|
4112
|
+
return {
|
|
4113
|
+
request: async (options) => await new Promise((resolve, reject) => {
|
|
4114
|
+
const request4 = https.request(
|
|
4115
|
+
{
|
|
4116
|
+
host: "127.0.0.1",
|
|
4117
|
+
port,
|
|
4118
|
+
method: options.method,
|
|
4119
|
+
path: options.path,
|
|
4120
|
+
headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
|
|
4121
|
+
agent: false,
|
|
4122
|
+
ca: [ca]
|
|
4123
|
+
},
|
|
4124
|
+
(response) => {
|
|
4125
|
+
const chunks = [];
|
|
4126
|
+
response.on("data", (chunk) => {
|
|
4127
|
+
chunks.push(chunk);
|
|
4128
|
+
});
|
|
4129
|
+
response.on("end", () => {
|
|
4130
|
+
resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
|
|
4131
|
+
});
|
|
4132
|
+
}
|
|
4133
|
+
);
|
|
4134
|
+
request4.once("error", reject);
|
|
4135
|
+
if (options.body === void 0) {
|
|
4136
|
+
request4.end();
|
|
4137
|
+
} else {
|
|
4138
|
+
request4.end(options.body);
|
|
4139
|
+
}
|
|
4140
|
+
})
|
|
4141
|
+
};
|
|
3647
4142
|
}
|
|
3648
|
-
function
|
|
3649
|
-
let parsed;
|
|
4143
|
+
function controlErrorOf(body) {
|
|
3650
4144
|
try {
|
|
3651
|
-
parsed = JSON.parse(body);
|
|
4145
|
+
const parsed = JSON.parse(body);
|
|
4146
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
|
|
4147
|
+
return parsed.error;
|
|
4148
|
+
}
|
|
3652
4149
|
} catch {
|
|
3653
|
-
return
|
|
3654
|
-
}
|
|
3655
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3656
|
-
return void 0;
|
|
3657
|
-
}
|
|
3658
|
-
const session = parsed.session;
|
|
3659
|
-
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3660
|
-
return void 0;
|
|
4150
|
+
return body;
|
|
3661
4151
|
}
|
|
3662
|
-
|
|
3663
|
-
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3664
|
-
}
|
|
3665
|
-
function isRecord2(value) {
|
|
3666
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4152
|
+
return void 0;
|
|
3667
4153
|
}
|
|
3668
|
-
function
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
parsed = JSON.parse(body);
|
|
3672
|
-
} catch {
|
|
3673
|
-
return void 0;
|
|
4154
|
+
function isRcSessionSummary(value) {
|
|
4155
|
+
if (typeof value !== "object" || value === null) {
|
|
4156
|
+
return false;
|
|
3674
4157
|
}
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3680
|
-
}
|
|
3681
|
-
function finiteNumber(record, field) {
|
|
3682
|
-
const value = record === void 0 ? void 0 : record[field];
|
|
3683
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3684
|
-
}
|
|
3685
|
-
function excerpt(text) {
|
|
3686
|
-
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
4158
|
+
if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
|
|
4159
|
+
return false;
|
|
4160
|
+
}
|
|
4161
|
+
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
3687
4162
|
}
|
|
3688
|
-
function
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
if (!Array.isArray(events)) {
|
|
3692
|
-
return [];
|
|
4163
|
+
function isRcWorkerFact(value, valueIs) {
|
|
4164
|
+
if (typeof value !== "object" || value === null) {
|
|
4165
|
+
return false;
|
|
3693
4166
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
if (!isRecord2(event)) {
|
|
3697
|
-
continue;
|
|
3698
|
-
}
|
|
3699
|
-
const payload = event.payload;
|
|
3700
|
-
if (!isRecord2(payload)) {
|
|
3701
|
-
continue;
|
|
3702
|
-
}
|
|
3703
|
-
payloads.push(payload);
|
|
4167
|
+
if (!("value" in value) || !("observedAt" in value)) {
|
|
4168
|
+
return false;
|
|
3704
4169
|
}
|
|
3705
|
-
return
|
|
4170
|
+
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
3706
4171
|
}
|
|
3707
|
-
function
|
|
3708
|
-
if (
|
|
3709
|
-
return
|
|
4172
|
+
function isRcSessionStatus(value) {
|
|
4173
|
+
if (!isRcSessionSummary(value)) {
|
|
4174
|
+
return false;
|
|
3710
4175
|
}
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
return void 0;
|
|
4176
|
+
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4177
|
+
return false;
|
|
3714
4178
|
}
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
const type = subtype ?? "control_request";
|
|
3718
|
-
let summary = "";
|
|
3719
|
-
if (subtype === "can_use_tool") {
|
|
3720
|
-
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3721
|
-
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3722
|
-
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3723
|
-
} else if (request4 !== void 0) {
|
|
3724
|
-
summary = excerpt(JSON.stringify(request4));
|
|
4179
|
+
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4180
|
+
return false;
|
|
3725
4181
|
}
|
|
3726
|
-
|
|
4182
|
+
if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
|
|
4183
|
+
return false;
|
|
4184
|
+
}
|
|
4185
|
+
return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
|
|
3727
4186
|
}
|
|
3728
|
-
function
|
|
3729
|
-
if (
|
|
3730
|
-
return
|
|
4187
|
+
function isRcPendingRequestSummary(value) {
|
|
4188
|
+
if (typeof value !== "object" || value === null) {
|
|
4189
|
+
return false;
|
|
3731
4190
|
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
4191
|
+
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4192
|
+
return false;
|
|
4193
|
+
}
|
|
4194
|
+
return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
|
|
3735
4195
|
}
|
|
3736
|
-
function
|
|
3737
|
-
|
|
3738
|
-
|
|
4196
|
+
function sequenceNumsOfBody(body) {
|
|
4197
|
+
let parsed;
|
|
4198
|
+
try {
|
|
4199
|
+
parsed = JSON.parse(body);
|
|
4200
|
+
} catch {
|
|
4201
|
+
return void 0;
|
|
3739
4202
|
}
|
|
3740
|
-
if (
|
|
3741
|
-
return
|
|
4203
|
+
if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
|
|
4204
|
+
return void 0;
|
|
3742
4205
|
}
|
|
3743
|
-
const
|
|
3744
|
-
const
|
|
3745
|
-
for (const
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3749
|
-
continue;
|
|
4206
|
+
const sequenceNums = [];
|
|
4207
|
+
const values = parsed.sequenceNums;
|
|
4208
|
+
for (const value of values) {
|
|
4209
|
+
if (typeof value !== "number") {
|
|
4210
|
+
return void 0;
|
|
3750
4211
|
}
|
|
3751
|
-
|
|
4212
|
+
sequenceNums.push(value);
|
|
3752
4213
|
}
|
|
3753
|
-
return
|
|
4214
|
+
return sequenceNums;
|
|
3754
4215
|
}
|
|
3755
|
-
function
|
|
3756
|
-
const
|
|
3757
|
-
const
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3763
|
-
entry.pending.delete(requestId);
|
|
3764
|
-
}
|
|
4216
|
+
function frontDoorRcControl(transport, token) {
|
|
4217
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
4218
|
+
const listAnswerOf = async (path29, what) => {
|
|
4219
|
+
try {
|
|
4220
|
+
return await transport.request({ method: "GET", path: path29, headers });
|
|
4221
|
+
} catch (error) {
|
|
4222
|
+
throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
3765
4223
|
}
|
|
3766
4224
|
};
|
|
3767
|
-
const
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
4225
|
+
const sessionsOf = (answer, what) => {
|
|
4226
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4227
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4228
|
+
}
|
|
4229
|
+
let parsed;
|
|
4230
|
+
try {
|
|
4231
|
+
parsed = JSON.parse(answer.body);
|
|
4232
|
+
} catch {
|
|
4233
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4234
|
+
}
|
|
4235
|
+
if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
|
|
4236
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4237
|
+
}
|
|
4238
|
+
const candidates = parsed.sessions;
|
|
4239
|
+
const sessions = [];
|
|
4240
|
+
for (const value of candidates) {
|
|
4241
|
+
if (!isRcSessionSummary(value)) {
|
|
4242
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
3772
4243
|
}
|
|
3773
|
-
|
|
4244
|
+
sessions.push(value);
|
|
3774
4245
|
}
|
|
4246
|
+
return sessions;
|
|
3775
4247
|
};
|
|
3776
|
-
const
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
|
|
3780
|
-
entries.set(id, record);
|
|
3781
|
-
return record;
|
|
3782
|
-
};
|
|
3783
|
-
const observeExchange = (request4) => {
|
|
3784
|
-
const method = (request4.method ?? "").toUpperCase();
|
|
3785
|
-
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3786
|
-
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3787
|
-
return void 0;
|
|
4248
|
+
const statusesOf = (answer, what) => {
|
|
4249
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4250
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
3788
4251
|
}
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
};
|
|
3795
|
-
if (tail === "") {
|
|
3796
|
-
if (method !== "POST") {
|
|
3797
|
-
return void 0;
|
|
3798
|
-
}
|
|
3799
|
-
let accepted2 = false;
|
|
3800
|
-
let buffered2 = 0;
|
|
3801
|
-
let body = "";
|
|
3802
|
-
return {
|
|
3803
|
-
onResponse: (status) => {
|
|
3804
|
-
accepted2 = isSuccessful(status);
|
|
3805
|
-
},
|
|
3806
|
-
onBodyChunk: (chunk) => {
|
|
3807
|
-
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3808
|
-
return;
|
|
3809
|
-
}
|
|
3810
|
-
const text = Buffer.from(chunk).toString("utf8");
|
|
3811
|
-
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3812
|
-
buffered2 = body.length;
|
|
3813
|
-
},
|
|
3814
|
-
onEnd: () => {
|
|
3815
|
-
if (!accepted2) {
|
|
3816
|
-
return;
|
|
3817
|
-
}
|
|
3818
|
-
const id2 = sessionCreateId(body);
|
|
3819
|
-
if (id2 === void 0) {
|
|
3820
|
-
return;
|
|
3821
|
-
}
|
|
3822
|
-
birth(id2, facts);
|
|
3823
|
-
}
|
|
3824
|
-
};
|
|
4252
|
+
let parsed;
|
|
4253
|
+
try {
|
|
4254
|
+
parsed = JSON.parse(answer.body);
|
|
4255
|
+
} catch {
|
|
4256
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
3825
4257
|
}
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3829
|
-
return void 0;
|
|
4258
|
+
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4259
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
3830
4260
|
}
|
|
3831
|
-
const
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3836
|
-
const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
|
|
3837
|
-
let chunks = [];
|
|
3838
|
-
let buffered = 0;
|
|
3839
|
-
let oversized = false;
|
|
3840
|
-
let requestEnded = false;
|
|
3841
|
-
let accepted;
|
|
3842
|
-
let filed = false;
|
|
3843
|
-
const file = () => {
|
|
3844
|
-
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3845
|
-
return;
|
|
3846
|
-
}
|
|
3847
|
-
filed = true;
|
|
3848
|
-
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3849
|
-
chunks = [];
|
|
3850
|
-
const entryNow = entries.get(id);
|
|
3851
|
-
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3852
|
-
return;
|
|
3853
|
-
}
|
|
3854
|
-
const now = deps.now();
|
|
3855
|
-
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3856
|
-
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3857
|
-
}
|
|
3858
|
-
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3859
|
-
entryNow.pending.delete(requestId);
|
|
3860
|
-
}
|
|
3861
|
-
if (factsOfBody.idleSeconds !== void 0) {
|
|
3862
|
-
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3863
|
-
}
|
|
3864
|
-
if (factsOfBody.workerStatus !== void 0) {
|
|
3865
|
-
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3866
|
-
}
|
|
3867
|
-
};
|
|
3868
|
-
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3869
|
-
return {
|
|
3870
|
-
...bodyKind === void 0 ? {} : {
|
|
3871
|
-
onRequestChunk: (chunk) => {
|
|
3872
|
-
if (oversized) {
|
|
3873
|
-
return;
|
|
3874
|
-
}
|
|
3875
|
-
chunks.push(Buffer.from(chunk));
|
|
3876
|
-
buffered += chunk.length;
|
|
3877
|
-
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3878
|
-
oversized = true;
|
|
3879
|
-
chunks = [];
|
|
3880
|
-
}
|
|
3881
|
-
},
|
|
3882
|
-
onRequestEnd: () => {
|
|
3883
|
-
requestEnded = true;
|
|
3884
|
-
file();
|
|
3885
|
-
}
|
|
3886
|
-
},
|
|
3887
|
-
onResponse: (status, headers) => {
|
|
3888
|
-
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3889
|
-
expire(id);
|
|
3890
|
-
return;
|
|
3891
|
-
}
|
|
3892
|
-
accepted = isSuccessful(status);
|
|
3893
|
-
file();
|
|
3894
|
-
},
|
|
3895
|
-
onBodyChunk: () => {
|
|
3896
|
-
},
|
|
3897
|
-
onEnd: () => {
|
|
4261
|
+
const statuses = [];
|
|
4262
|
+
for (const value of parsed.statuses) {
|
|
4263
|
+
if (!isRcSessionStatus(value)) {
|
|
4264
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
3898
4265
|
}
|
|
3899
|
-
|
|
3900
|
-
};
|
|
3901
|
-
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3902
|
-
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3903
|
-
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3904
|
-
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3905
|
-
return {
|
|
3906
|
-
observeExchange,
|
|
3907
|
-
list: () => {
|
|
3908
|
-
sweep();
|
|
3909
|
-
return sortedEntries().map(summaryOf);
|
|
3910
|
-
},
|
|
3911
|
-
statusOf: (sessionId) => {
|
|
3912
|
-
sweep();
|
|
3913
|
-
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3914
|
-
},
|
|
3915
|
-
pendingOf: (sessionId) => {
|
|
3916
|
-
sweep();
|
|
3917
|
-
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3918
|
-
},
|
|
3919
|
-
credentialOf: (sessionId) => {
|
|
3920
|
-
const entry = entries.get(sessionId);
|
|
3921
|
-
return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3922
|
-
},
|
|
3923
|
-
completePending: (sessionId, requestId) => {
|
|
3924
|
-
entries.get(sessionId)?.pending.delete(requestId);
|
|
3925
|
-
},
|
|
3926
|
-
close: () => {
|
|
3927
|
-
clearInterval(timer);
|
|
4266
|
+
statuses.push(value);
|
|
3928
4267
|
}
|
|
4268
|
+
return statuses;
|
|
3929
4269
|
};
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
4270
|
+
const pendingOfAnswer = (answer, what) => {
|
|
4271
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4272
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4273
|
+
}
|
|
4274
|
+
let parsed;
|
|
4275
|
+
try {
|
|
4276
|
+
parsed = JSON.parse(answer.body);
|
|
4277
|
+
} catch {
|
|
4278
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4279
|
+
}
|
|
4280
|
+
if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
|
|
4281
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4282
|
+
}
|
|
4283
|
+
const pending = [];
|
|
4284
|
+
for (const value of parsed.pending) {
|
|
4285
|
+
if (!isRcPendingRequestSummary(value)) {
|
|
4286
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
3943
4287
|
}
|
|
3944
|
-
|
|
4288
|
+
pending.push(value);
|
|
3945
4289
|
}
|
|
4290
|
+
return pending;
|
|
3946
4291
|
};
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
start: (status, headers) => {
|
|
3954
|
-
observer.onResponse(status, headers);
|
|
3955
|
-
response.start(status, headers);
|
|
3956
|
-
},
|
|
3957
|
-
flush: () => {
|
|
3958
|
-
response.flush();
|
|
3959
|
-
},
|
|
3960
|
-
write: async (chunk) => {
|
|
3961
|
-
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3962
|
-
await response.write(chunk);
|
|
3963
|
-
},
|
|
3964
|
-
end: () => {
|
|
3965
|
-
response.end();
|
|
3966
|
-
observer.onEnd();
|
|
3967
|
-
},
|
|
3968
|
-
destroy: () => {
|
|
3969
|
-
response.destroy();
|
|
3970
|
-
observer.onEnd();
|
|
4292
|
+
const writeOf = async (path29, body, describeFailure) => {
|
|
4293
|
+
let answer;
|
|
4294
|
+
try {
|
|
4295
|
+
answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
3971
4298
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
onRequestChunk: (chunk) => {
|
|
3977
|
-
observer.onRequestChunk?.(chunk);
|
|
3978
|
-
},
|
|
3979
|
-
...observer.onRequestEnd === void 0 ? {} : {
|
|
3980
|
-
onRequestEnd: () => {
|
|
3981
|
-
observer.onRequestEnd?.();
|
|
4299
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4300
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4301
|
+
if (sequenceNums === void 0) {
|
|
4302
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
3982
4303
|
}
|
|
3983
|
-
|
|
3984
|
-
onResponse: (status, headers) => {
|
|
3985
|
-
observer.onResponse(status, headers);
|
|
3986
|
-
},
|
|
3987
|
-
onResponseChunk: (chunk) => {
|
|
3988
|
-
observer.onBodyChunk(chunk);
|
|
3989
|
-
},
|
|
3990
|
-
onEnd: () => {
|
|
3991
|
-
observer.onEnd();
|
|
4304
|
+
return { ok: true, sequenceNums };
|
|
3992
4305
|
}
|
|
4306
|
+
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
3993
4307
|
};
|
|
3994
|
-
}
|
|
3995
|
-
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
4308
|
+
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
3996
4309
|
return {
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4310
|
+
listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
|
|
4311
|
+
statusOf: async (sessionId) => statusesOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/status${sessionQuery(sessionId)}`, "reading the door's Remote Control session status"), "reading the door's Remote Control session status"),
|
|
4312
|
+
pendingOf: async (sessionId) => pendingOfAnswer(await listAnswerOf(`${CONTROL_PATH_PREFIX}/pending${sessionQuery(sessionId)}`, "listing the door's pending Remote Control control requests"), "listing the door's pending Remote Control control requests"),
|
|
4313
|
+
sendPrompt: async (sessionId, text) => await writeOf(`${CONTROL_PATH_PREFIX}/inject`, JSON.stringify({ session: sessionId, text }), (status, message) => `injecting into ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4314
|
+
answerRequest: async (sessionId, requestId, decision) => await writeOf(
|
|
4315
|
+
`${CONTROL_PATH_PREFIX}/answer`,
|
|
4316
|
+
JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
|
|
4317
|
+
(status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4318
|
+
),
|
|
4319
|
+
interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4320
|
+
setModel: async (sessionId, model) => await writeOf(`${CONTROL_PATH_PREFIX}/set-model`, JSON.stringify({ session: sessionId, model }), (status, message) => `setting the model on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4321
|
+
setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`)
|
|
4002
4322
|
};
|
|
4003
4323
|
}
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
} catch {
|
|
4012
|
-
return void 0;
|
|
4013
|
-
}
|
|
4014
|
-
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
4015
|
-
return void 0;
|
|
4016
|
-
}
|
|
4017
|
-
const sequenceNums = [];
|
|
4018
|
-
const results = parsed.results;
|
|
4019
|
-
for (const result of results) {
|
|
4020
|
-
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
4021
|
-
return void 0;
|
|
4022
|
-
}
|
|
4023
|
-
const sequenceNum = result.sequence_num;
|
|
4024
|
-
if (typeof sequenceNum === "number") {
|
|
4025
|
-
sequenceNums.push(sequenceNum);
|
|
4026
|
-
} else if (typeof sequenceNum === "string" && /^[0-9]+$/.test(sequenceNum)) {
|
|
4027
|
-
sequenceNums.push(Number(sequenceNum));
|
|
4028
|
-
} else {
|
|
4029
|
-
return void 0;
|
|
4324
|
+
|
|
4325
|
+
// src/frontdoor/server.ts
|
|
4326
|
+
async function* upstreamChunks(stream) {
|
|
4327
|
+
for await (const chunk of stream) {
|
|
4328
|
+
if (typeof chunk === "string") {
|
|
4329
|
+
yield Buffer.from(chunk, "utf8");
|
|
4330
|
+
continue;
|
|
4030
4331
|
}
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
}
|
|
4034
|
-
function rcEventWriteResultFromAnswer(answer) {
|
|
4035
|
-
if (!isSuccessful(answer.status)) {
|
|
4036
|
-
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
4037
|
-
return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
|
|
4332
|
+
if (chunk instanceof Uint8Array) {
|
|
4333
|
+
yield chunk;
|
|
4038
4334
|
}
|
|
4039
|
-
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
4040
|
-
}
|
|
4041
|
-
const sequenceNums = sequenceNumsOf(answer.body);
|
|
4042
|
-
if (sequenceNums === void 0) {
|
|
4043
|
-
return { ok: false, message: `the API accepted the injected event but its answer named no results[].sequence_num, so delivery cannot be confirmed: ${answer.body}` };
|
|
4044
4335
|
}
|
|
4045
|
-
return { ok: true, sequenceNums };
|
|
4046
4336
|
}
|
|
4047
|
-
|
|
4048
|
-
|
|
4337
|
+
|
|
4338
|
+
// src/frontdoor/connectEffects.ts
|
|
4339
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE2 = 300;
|
|
4340
|
+
function isSuccessful2(status) {
|
|
4341
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE2;
|
|
4049
4342
|
}
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
if (credential.anthropicClientPlatform !== void 0) {
|
|
4059
|
-
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
4060
|
-
}
|
|
4061
|
-
return headers;
|
|
4343
|
+
var UPSTREAM_LOCAL_PORT_START = 47900;
|
|
4344
|
+
var UPSTREAM_LOCAL_PORT_END = 47919;
|
|
4345
|
+
var UPSTREAM_LOCAL_PORT_COUNT = UPSTREAM_LOCAL_PORT_END - UPSTREAM_LOCAL_PORT_START + 1;
|
|
4346
|
+
var nextUpstreamLocalPort = UPSTREAM_LOCAL_PORT_START;
|
|
4347
|
+
function upstreamLocalPort() {
|
|
4348
|
+
const port = nextUpstreamLocalPort;
|
|
4349
|
+
nextUpstreamLocalPort = nextUpstreamLocalPort === UPSTREAM_LOCAL_PORT_END ? UPSTREAM_LOCAL_PORT_START : nextUpstreamLocalPort + 1;
|
|
4350
|
+
return port;
|
|
4062
4351
|
}
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
return
|
|
4067
|
-
}
|
|
4068
|
-
if (credential.authorization === void 0) {
|
|
4069
|
-
return noOAuthCredentialResult(sessionId);
|
|
4070
|
-
}
|
|
4071
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
4072
|
-
let answer;
|
|
4073
|
-
try {
|
|
4074
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4075
|
-
} catch (error) {
|
|
4076
|
-
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4352
|
+
var realAddressLookup = (host, _options, callback) => {
|
|
4353
|
+
if (net2.isIP(host) !== 0) {
|
|
4354
|
+
callback(null, [{ address: host, family: net2.isIP(host) }]);
|
|
4355
|
+
return;
|
|
4077
4356
|
}
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
}
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
}
|
|
4091
|
-
|
|
4092
|
-
|
|
4357
|
+
import_node_dns.default.resolve4(host, (error, addresses) => {
|
|
4358
|
+
if (error !== null) {
|
|
4359
|
+
callback(error, [{ address: "", family: 4 }]);
|
|
4360
|
+
return;
|
|
4361
|
+
}
|
|
4362
|
+
const first = addresses[0];
|
|
4363
|
+
if (first === void 0) {
|
|
4364
|
+
callback(new Error(`no A record for ${host}`), [{ address: "", family: 4 }]);
|
|
4365
|
+
return;
|
|
4366
|
+
}
|
|
4367
|
+
callback(null, [{ address: first, family: 4 }]);
|
|
4368
|
+
});
|
|
4369
|
+
};
|
|
4370
|
+
var HTTP_PORT = 80;
|
|
4371
|
+
var HELD_PORT_ERRNOS = /* @__PURE__ */ new Set(["EADDRINUSE", "EADDRNOTAVAIL"]);
|
|
4372
|
+
async function dialOnce(connectOptions) {
|
|
4373
|
+
return await new Promise((resolve) => {
|
|
4374
|
+
const socket = net2.connect(connectOptions);
|
|
4375
|
+
socket.once("connect", () => {
|
|
4376
|
+
resolve({ error: null, socket });
|
|
4377
|
+
});
|
|
4378
|
+
socket.once("error", (error) => {
|
|
4379
|
+
resolve({ error, socket });
|
|
4380
|
+
});
|
|
4381
|
+
});
|
|
4093
4382
|
}
|
|
4094
|
-
function
|
|
4095
|
-
return {
|
|
4383
|
+
function dialTarget(options, defaultPort) {
|
|
4384
|
+
return { host: typeof options.host === "string" ? options.host : "", port: typeof options.port === "number" ? options.port : defaultPort };
|
|
4096
4385
|
}
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
if (credential === void 0) {
|
|
4100
|
-
return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
|
|
4101
|
-
}
|
|
4102
|
-
if (credential.authorization === void 0) {
|
|
4103
|
-
return noOAuthCredentialResult(sessionId);
|
|
4104
|
-
}
|
|
4105
|
-
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
4106
|
-
return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
|
|
4107
|
-
}
|
|
4108
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
4109
|
-
let answer;
|
|
4110
|
-
try {
|
|
4111
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4112
|
-
} catch (error) {
|
|
4113
|
-
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4114
|
-
}
|
|
4115
|
-
const result = rcEventWriteResultFromAnswer(answer);
|
|
4116
|
-
if (result.ok) {
|
|
4117
|
-
deps.completePending(sessionId, requestId);
|
|
4118
|
-
}
|
|
4119
|
-
return result;
|
|
4386
|
+
function literalDial(target) {
|
|
4387
|
+
return net2.connect({ host: target.host, port: target.port });
|
|
4120
4388
|
}
|
|
4121
|
-
async function
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
try {
|
|
4132
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4133
|
-
} catch (error) {
|
|
4134
|
-
return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4389
|
+
async function reservedDial(target) {
|
|
4390
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
4391
|
+
const outcome = await dialOnce({ host: target.host, port: target.port, localPort: upstreamLocalPort(), lookup: realAddressLookup });
|
|
4392
|
+
if (outcome.error === null) {
|
|
4393
|
+
return outcome;
|
|
4394
|
+
}
|
|
4395
|
+
const held = outcome.error.code !== void 0 && HELD_PORT_ERRNOS.has(outcome.error.code);
|
|
4396
|
+
if (!held || attempt === UPSTREAM_LOCAL_PORT_COUNT - 1) {
|
|
4397
|
+
return outcome;
|
|
4398
|
+
}
|
|
4135
4399
|
}
|
|
4136
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
4137
4400
|
}
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
return { ok: false, message: "a set-model write needs a non-empty model id: the SDK's own field treats an omitted or null value as a reset to the session default, which this operation never sends, and an empty string is neither" };
|
|
4141
|
-
}
|
|
4142
|
-
const credential = deps.credentialOf(sessionId);
|
|
4143
|
-
if (credential === void 0) {
|
|
4144
|
-
return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
|
|
4145
|
-
}
|
|
4146
|
-
if (credential.authorization === void 0) {
|
|
4147
|
-
return noOAuthCredentialResult(sessionId);
|
|
4148
|
-
}
|
|
4149
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
|
|
4150
|
-
let answer;
|
|
4151
|
-
try {
|
|
4152
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4153
|
-
} catch (error) {
|
|
4154
|
-
return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4155
|
-
}
|
|
4156
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
4401
|
+
function requestServername(options, fallback) {
|
|
4402
|
+
return "servername" in options && typeof options.servername === "string" && options.servername !== "" ? options.servername : fallback;
|
|
4157
4403
|
}
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4404
|
+
var ExemptHttpAgent = class extends http.Agent {
|
|
4405
|
+
// A reserved-port dial that has to retry cannot produce its socket synchronously, so it settles first and arrives through the callback (node's documented asynchronous createConnection form); a literal dial has nothing to retry and keeps node's own synchronous shape.
|
|
4406
|
+
createConnection(options, oncreate) {
|
|
4407
|
+
const target = dialTarget(options, HTTP_PORT);
|
|
4408
|
+
if (net2.isIP(target.host) !== 0) {
|
|
4409
|
+
return literalDial(target);
|
|
4410
|
+
}
|
|
4411
|
+
void reservedDial(target).then((outcome) => {
|
|
4412
|
+
oncreate?.(outcome.error, outcome.socket);
|
|
4413
|
+
});
|
|
4414
|
+
return void 0;
|
|
4162
4415
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4416
|
+
};
|
|
4417
|
+
var ExemptTlsAgent = class extends https2.Agent {
|
|
4418
|
+
// The same split as the plain sibling: a literal dial wraps TLS around the socket node's own agent would have returned, a reserved dial settles (retries included) first and arrives through the callback, which is exactly where node's own agent wraps its own dials.
|
|
4419
|
+
createConnection(options, oncreate) {
|
|
4420
|
+
const target = dialTarget(options, HTTPS_PORT);
|
|
4421
|
+
if (net2.isIP(target.host) !== 0) {
|
|
4422
|
+
return tls.connect({ socket: literalDial(target), servername: requestServername(options, target.host), ...tlsFacts(options) });
|
|
4423
|
+
}
|
|
4424
|
+
void reservedDial(target).then((outcome) => {
|
|
4425
|
+
if (outcome.error !== null) {
|
|
4426
|
+
oncreate?.(outcome.error, outcome.socket);
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
oncreate?.(null, tls.connect({ socket: outcome.socket, servername: requestServername(options, target.host), ...tlsFacts(options) }));
|
|
4430
|
+
});
|
|
4431
|
+
return void 0;
|
|
4165
4432
|
}
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4433
|
+
};
|
|
4434
|
+
function tlsFacts(options) {
|
|
4435
|
+
const facts = {};
|
|
4436
|
+
if ("ca" in options && Array.isArray(options.ca)) {
|
|
4437
|
+
facts.ca = options.ca;
|
|
4172
4438
|
}
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
var https2 = __toESM(require("node:https"), 1);
|
|
4178
|
-
var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
|
|
4179
|
-
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
4180
|
-
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
4181
|
-
function answerJson(response, status, body) {
|
|
4182
|
-
const text = JSON.stringify(body);
|
|
4183
|
-
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
4184
|
-
response.end(text);
|
|
4439
|
+
if ("rejectUnauthorized" in options && typeof options.rejectUnauthorized === "boolean") {
|
|
4440
|
+
facts.rejectUnauthorized = options.rejectUnauthorized;
|
|
4441
|
+
}
|
|
4442
|
+
return facts;
|
|
4185
4443
|
}
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4444
|
+
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
4445
|
+
const agent = new ExemptTlsAgent();
|
|
4446
|
+
return {
|
|
4447
|
+
writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
|
|
4448
|
+
const options = {
|
|
4449
|
+
host: target.host,
|
|
4450
|
+
port: target.port,
|
|
4451
|
+
method: "POST",
|
|
4452
|
+
path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
|
|
4453
|
+
// The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
|
|
4454
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
4455
|
+
agent
|
|
4456
|
+
};
|
|
4457
|
+
options.servername = target.servername ?? target.host;
|
|
4458
|
+
if (target.ca !== void 0) {
|
|
4459
|
+
options.ca = [...target.ca];
|
|
4196
4460
|
}
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4461
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4462
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4463
|
+
}
|
|
4464
|
+
const request4 = https2.request(options, (response) => {
|
|
4465
|
+
const chunks = [];
|
|
4466
|
+
response.on("data", (chunk) => {
|
|
4467
|
+
chunks.push(chunk);
|
|
4468
|
+
});
|
|
4469
|
+
response.on("end", () => {
|
|
4470
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
4471
|
+
});
|
|
4472
|
+
});
|
|
4473
|
+
request4.once("error", reject);
|
|
4474
|
+
request4.end(body);
|
|
4475
|
+
})
|
|
4476
|
+
};
|
|
4477
|
+
}
|
|
4478
|
+
var NO_CHUNKS = {
|
|
4479
|
+
[Symbol.asyncIterator]: () => ({
|
|
4480
|
+
next: async () => await Promise.resolve({ done: true, value: void 0 })
|
|
4481
|
+
})
|
|
4482
|
+
};
|
|
4483
|
+
function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
4484
|
+
const agent = new ExemptTlsAgent();
|
|
4485
|
+
const request4 = async (method, path29, headers, body, signal) => await new Promise((resolve, reject) => {
|
|
4486
|
+
const options = {
|
|
4487
|
+
host: target.host,
|
|
4488
|
+
port: target.port,
|
|
4489
|
+
method,
|
|
4490
|
+
path: path29,
|
|
4491
|
+
// The Host header names the host whose API this is, not the address dialled: a redirected test dials a loopback stand-in while still speaking to the API host by name, exactly as the forwarded paths present their SNI.
|
|
4492
|
+
headers: { ...headers, host: target.servername ?? target.host, ...body === void 0 ? {} : { "content-length": String(Buffer.byteLength(body, "utf8")) } },
|
|
4493
|
+
agent,
|
|
4494
|
+
signal
|
|
4495
|
+
};
|
|
4496
|
+
options.servername = target.servername ?? target.host;
|
|
4497
|
+
if (target.ca !== void 0) {
|
|
4498
|
+
options.ca = [...target.ca];
|
|
4499
|
+
}
|
|
4500
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4501
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4502
|
+
}
|
|
4503
|
+
const sent = https2.request(options, (response) => {
|
|
4504
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, response });
|
|
4204
4505
|
});
|
|
4506
|
+
sent.once("error", reject);
|
|
4507
|
+
if (body === void 0) {
|
|
4508
|
+
sent.end();
|
|
4509
|
+
} else {
|
|
4510
|
+
sent.end(body);
|
|
4511
|
+
}
|
|
4205
4512
|
});
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
4211
|
-
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
4212
|
-
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
4213
|
-
const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
|
|
4214
|
-
const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
|
|
4215
|
-
const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
|
|
4216
|
-
return (request4, response) => {
|
|
4217
|
-
const presented = request4.headers.authorization;
|
|
4218
|
-
const bearerPrefix = "bearer ".length;
|
|
4219
|
-
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
4220
|
-
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
4221
|
-
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
4222
|
-
return;
|
|
4513
|
+
const readAll = async (response) => {
|
|
4514
|
+
const chunks = [];
|
|
4515
|
+
for await (const chunk of upstreamChunks(response)) {
|
|
4516
|
+
chunks.push(Buffer.from(chunk));
|
|
4223
4517
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
}
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
}
|
|
4239
|
-
const session = url.searchParams.get("session") ?? void 0;
|
|
4240
|
-
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
4241
|
-
answerJson(response, HTTP_STATUS.notFound, { error: `the front door has not observed Remote Control session ${session}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` });
|
|
4242
|
-
return;
|
|
4243
|
-
}
|
|
4244
|
-
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
4245
|
-
return;
|
|
4246
|
-
}
|
|
4247
|
-
if (url.pathname === injectPath) {
|
|
4248
|
-
if (request4.method !== "POST") {
|
|
4249
|
-
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
4250
|
-
return;
|
|
4251
|
-
}
|
|
4252
|
-
let parsed;
|
|
4253
|
-
try {
|
|
4254
|
-
parsed = JSON.parse(await readBody(request4));
|
|
4255
|
-
} catch {
|
|
4256
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
4257
|
-
return;
|
|
4258
|
-
}
|
|
4259
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
4260
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
4261
|
-
return;
|
|
4262
|
-
}
|
|
4263
|
-
const result = await deps.inject(parsed.session, parsed.text);
|
|
4264
|
-
if (result.ok) {
|
|
4265
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
4266
|
-
return;
|
|
4267
|
-
}
|
|
4268
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4269
|
-
return;
|
|
4518
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
4519
|
+
};
|
|
4520
|
+
return {
|
|
4521
|
+
announcePresence: async (sessionId, headers, clientId) => {
|
|
4522
|
+
const body = JSON.stringify({ client_id: clientId, clear: false });
|
|
4523
|
+
const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
|
|
4524
|
+
return { status, body: await readAll(response) };
|
|
4525
|
+
},
|
|
4526
|
+
openStream: async (sessionId, headers, resume, signal) => {
|
|
4527
|
+
const path29 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
|
|
4528
|
+
const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
|
|
4529
|
+
const { status, response } = await request4("GET", path29, streamHeaders, void 0, signal);
|
|
4530
|
+
if (!isSuccessful2(status)) {
|
|
4531
|
+
await readAll(response);
|
|
4532
|
+
return { status, chunks: NO_CHUNKS };
|
|
4270
4533
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
if (result.ok) {
|
|
4294
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
4295
|
-
return;
|
|
4296
|
-
}
|
|
4297
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4534
|
+
return { status, chunks: response };
|
|
4535
|
+
}
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
function realConnectEffects() {
|
|
4539
|
+
const plainAgent = new ExemptHttpAgent();
|
|
4540
|
+
const tlsAgent = new ExemptTlsAgent();
|
|
4541
|
+
const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
|
|
4542
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
4543
|
+
const server = net2.createServer((socket) => {
|
|
4544
|
+
accepted.add(socket);
|
|
4545
|
+
socket.on("close", () => {
|
|
4546
|
+
accepted.delete(socket);
|
|
4547
|
+
});
|
|
4548
|
+
onSocket(socket);
|
|
4549
|
+
});
|
|
4550
|
+
server.once("error", reject);
|
|
4551
|
+
server.listen(port, "127.0.0.1", () => {
|
|
4552
|
+
const address = server.address();
|
|
4553
|
+
const bound = typeof address === "object" && address !== null ? address.port : 0;
|
|
4554
|
+
if (bound === 0) {
|
|
4555
|
+
reject(new Error("could not bind a loopback port"));
|
|
4298
4556
|
return;
|
|
4299
4557
|
}
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4558
|
+
resolve({
|
|
4559
|
+
port: bound,
|
|
4560
|
+
close: async () => {
|
|
4561
|
+
await new Promise((closeResolve) => {
|
|
4562
|
+
server.close(() => {
|
|
4563
|
+
closeResolve(void 0);
|
|
4564
|
+
});
|
|
4565
|
+
for (const socket of accepted) {
|
|
4566
|
+
socket.destroy();
|
|
4567
|
+
}
|
|
4568
|
+
});
|
|
4304
4569
|
}
|
|
4305
|
-
|
|
4570
|
+
});
|
|
4571
|
+
});
|
|
4572
|
+
});
|
|
4573
|
+
return {
|
|
4574
|
+
listenLoopback: async (preferredPort, onSocket) => {
|
|
4575
|
+
if (preferredPort !== void 0) {
|
|
4306
4576
|
try {
|
|
4307
|
-
|
|
4577
|
+
return await listenOnce(preferredPort, onSocket);
|
|
4308
4578
|
} catch {
|
|
4309
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
|
|
4310
|
-
return;
|
|
4311
|
-
}
|
|
4312
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
|
|
4313
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
|
|
4314
|
-
return;
|
|
4315
|
-
}
|
|
4316
|
-
const result = await deps.interrupt(parsed.session);
|
|
4317
|
-
if (result.ok) {
|
|
4318
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
4319
|
-
return;
|
|
4320
4579
|
}
|
|
4321
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4322
|
-
return;
|
|
4323
4580
|
}
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4581
|
+
return await listenOnce(0, onSocket);
|
|
4582
|
+
},
|
|
4583
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
4584
|
+
let pendingContext = "";
|
|
4585
|
+
const server = tls.createServer(
|
|
4586
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
4587
|
+
(secure) => {
|
|
4588
|
+
onSecure(secure, pendingContext);
|
|
4328
4589
|
}
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4590
|
+
);
|
|
4591
|
+
server.on("clientError", (_error, socket) => {
|
|
4592
|
+
socket.destroy();
|
|
4593
|
+
});
|
|
4594
|
+
return {
|
|
4595
|
+
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
4596
|
+
accept: (socket, context) => {
|
|
4597
|
+
pendingContext = context;
|
|
4598
|
+
server.emit("connection", socket);
|
|
4599
|
+
},
|
|
4600
|
+
close: () => {
|
|
4601
|
+
server.close();
|
|
4335
4602
|
}
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4603
|
+
};
|
|
4604
|
+
},
|
|
4605
|
+
createSniTlsAcceptor: (hosts, leafFor, onSecure) => {
|
|
4606
|
+
const contexts = new Map(
|
|
4607
|
+
hosts.map((host) => {
|
|
4608
|
+
const leaf = leafFor(host);
|
|
4609
|
+
return [host, tls.createSecureContext({ key: leaf.keyPem, cert: leaf.certPem })];
|
|
4610
|
+
})
|
|
4611
|
+
);
|
|
4612
|
+
const server = tls.createServer(
|
|
4613
|
+
{
|
|
4614
|
+
// No default key or cert exists, so the SNI selection is the only source of credentials: an unserved name fails its handshake here, and a client that sent no SNI at all never reaches the selection and fails for want of any certificate, which is exactly the fail-closed shape this surface wants.
|
|
4615
|
+
SNICallback: (servername, callback) => {
|
|
4616
|
+
const context = contexts.get(servername);
|
|
4617
|
+
callback(context === void 0 ? new Error(`the transparent surface serves no host named ${servername}`) : null, context);
|
|
4618
|
+
}
|
|
4619
|
+
},
|
|
4620
|
+
(secure) => {
|
|
4621
|
+
onSecure(secure, typeof secure.servername === "string" ? secure.servername : "");
|
|
4339
4622
|
}
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4623
|
+
);
|
|
4624
|
+
server.on("clientError", (_error, socket) => {
|
|
4625
|
+
socket.destroy();
|
|
4626
|
+
});
|
|
4627
|
+
return {
|
|
4628
|
+
// The same hand-off the single-leaf acceptor uses: emitting the connection event runs the TLS server's own connection listener on a socket this process already owns (the redirect half of the connection) rather than one the server accepted itself.
|
|
4629
|
+
accept: (socket) => {
|
|
4630
|
+
server.emit("connection", socket);
|
|
4631
|
+
},
|
|
4632
|
+
close: () => {
|
|
4633
|
+
server.close();
|
|
4344
4634
|
}
|
|
4345
|
-
|
|
4346
|
-
|
|
4635
|
+
};
|
|
4636
|
+
},
|
|
4637
|
+
createHttpSession: (handler, onUpgrade) => {
|
|
4638
|
+
const server = http.createServer(handler);
|
|
4639
|
+
server.on("clientError", (_error, socket) => {
|
|
4640
|
+
socket.destroy();
|
|
4641
|
+
});
|
|
4642
|
+
if (onUpgrade !== void 0) {
|
|
4643
|
+
server.on("upgrade", (request4, socket, head) => {
|
|
4644
|
+
onUpgrade(request4, socket, head);
|
|
4645
|
+
});
|
|
4347
4646
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4647
|
+
return {
|
|
4648
|
+
serve: (socket) => {
|
|
4649
|
+
server.emit("connection", socket);
|
|
4650
|
+
},
|
|
4651
|
+
close: () => {
|
|
4652
|
+
server.close();
|
|
4653
|
+
server.closeAllConnections();
|
|
4352
4654
|
}
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4655
|
+
};
|
|
4656
|
+
},
|
|
4657
|
+
connectTlsUpstream: async (host, port, clientAlpn) => {
|
|
4658
|
+
const dialled = await reservedDial({ host, port });
|
|
4659
|
+
if (dialled.error !== null) {
|
|
4660
|
+
throw dialled.error;
|
|
4661
|
+
}
|
|
4662
|
+
return await new Promise((resolve, reject) => {
|
|
4663
|
+
const secure = tls.connect({ socket: dialled.socket, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
4664
|
+
secure.once("secureConnect", () => {
|
|
4665
|
+
resolve(secure);
|
|
4666
|
+
});
|
|
4667
|
+
secure.once("error", reject);
|
|
4668
|
+
});
|
|
4669
|
+
},
|
|
4670
|
+
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
4671
|
+
const socket = net2.connect({ host, port });
|
|
4672
|
+
socket.once("connect", () => {
|
|
4673
|
+
resolve(socket);
|
|
4674
|
+
});
|
|
4675
|
+
socket.once("error", reject);
|
|
4676
|
+
}),
|
|
4677
|
+
forwardHttp: (target, request4, response, observer) => {
|
|
4678
|
+
const headers = forwardableHeaders(request4.headers);
|
|
4679
|
+
const options = {
|
|
4680
|
+
host: target.host,
|
|
4681
|
+
port: target.port,
|
|
4682
|
+
lookup: realAddressLookup,
|
|
4683
|
+
method: request4.method,
|
|
4684
|
+
path: request4.url,
|
|
4685
|
+
headers,
|
|
4686
|
+
agent: target.tls ? tlsAgent : plainAgent
|
|
4687
|
+
};
|
|
4688
|
+
if (target.tls) {
|
|
4689
|
+
options.servername = target.servername ?? target.host;
|
|
4690
|
+
if (target.ca !== void 0) {
|
|
4691
|
+
options.ca = [...target.ca];
|
|
4359
4692
|
}
|
|
4360
|
-
if (
|
|
4361
|
-
|
|
4362
|
-
return;
|
|
4693
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4694
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4363
4695
|
}
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4696
|
+
}
|
|
4697
|
+
const onUpstreamResponse = (upstreamResponse) => {
|
|
4698
|
+
observer?.onResponse(upstreamResponse.statusCode ?? HTTP_BAD_GATEWAY, upstreamResponse.headers);
|
|
4699
|
+
response.writeHead(upstreamResponse.statusCode ?? HTTP_BAD_GATEWAY, forwardableHeaders(upstreamResponse.headers));
|
|
4700
|
+
response.flushHeaders();
|
|
4701
|
+
if (observer !== void 0) {
|
|
4702
|
+
upstreamResponse.on("data", (chunk) => {
|
|
4703
|
+
observer.onResponseChunk(chunk);
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4706
|
+
upstreamResponse.pipe(response);
|
|
4707
|
+
};
|
|
4708
|
+
const upstream = target.tls ? https2.request(options, onUpstreamResponse) : http.request(options, onUpstreamResponse);
|
|
4709
|
+
upstream.on("error", (error) => {
|
|
4710
|
+
if (response.headersSent) {
|
|
4711
|
+
response.destroy(error);
|
|
4367
4712
|
return;
|
|
4368
4713
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4714
|
+
const body = `agent-shim headroom: upstream unreachable (${error.message})`;
|
|
4715
|
+
response.writeHead(HTTP_BAD_GATEWAY, { "content-type": "text/plain", "content-length": String(body.length) });
|
|
4716
|
+
response.end(body);
|
|
4717
|
+
});
|
|
4718
|
+
if (observer !== void 0) {
|
|
4719
|
+
request4.on("data", (chunk) => {
|
|
4720
|
+
observer.onRequestChunk(chunk);
|
|
4721
|
+
});
|
|
4722
|
+
if (observer.onRequestEnd !== void 0) {
|
|
4723
|
+
request4.on("end", () => {
|
|
4724
|
+
observer.onRequestEnd?.();
|
|
4725
|
+
});
|
|
4726
|
+
}
|
|
4727
|
+
response.on("close", () => {
|
|
4728
|
+
observer.onEnd();
|
|
4729
|
+
});
|
|
4371
4730
|
}
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4731
|
+
request4.pipe(upstream);
|
|
4732
|
+
request4.socket.setNoDelay(true);
|
|
4733
|
+
upstream.on("socket", (socket) => {
|
|
4734
|
+
socket.setNoDelay(true);
|
|
4735
|
+
});
|
|
4736
|
+
}
|
|
4377
4737
|
};
|
|
4378
4738
|
}
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4739
|
+
|
|
4740
|
+
// src/frontdoor/rcApi.ts
|
|
4741
|
+
var import_client = require("@orpc/client");
|
|
4742
|
+
var import_fetch = require("@orpc/client/fetch");
|
|
4743
|
+
var import_server2 = require("@orpc/server");
|
|
4744
|
+
var import_node = require("@orpc/server/node");
|
|
4745
|
+
var import_undici = require("undici");
|
|
4746
|
+
|
|
4747
|
+
// src/frontdoor/rcSchemas.ts
|
|
4748
|
+
var import_zod4 = require("zod");
|
|
4749
|
+
var RcPermissionModeSchema = import_zod4.z.enum(RC_PERMISSION_MODES);
|
|
4750
|
+
var RcSessionSummarySchema = import_zod4.z.strictObject({
|
|
4751
|
+
id: import_zod4.z.string(),
|
|
4752
|
+
createdAt: import_zod4.z.number(),
|
|
4753
|
+
lastSeenAt: import_zod4.z.number()
|
|
4754
|
+
});
|
|
4755
|
+
function workerFactSchema(value) {
|
|
4756
|
+
return import_zod4.z.strictObject({ value, observedAt: import_zod4.z.number() });
|
|
4757
|
+
}
|
|
4758
|
+
var RcPendingRequestSummarySchema = import_zod4.z.strictObject({
|
|
4759
|
+
sessionId: import_zod4.z.string(),
|
|
4760
|
+
requestId: import_zod4.z.string(),
|
|
4761
|
+
type: import_zod4.z.string(),
|
|
4762
|
+
summary: import_zod4.z.string(),
|
|
4763
|
+
observedAt: import_zod4.z.number()
|
|
4764
|
+
});
|
|
4765
|
+
var RcSessionStatusSchema = import_zod4.z.strictObject({
|
|
4766
|
+
...RcSessionSummarySchema.shape,
|
|
4767
|
+
workerState: workerFactSchema(import_zod4.z.string()).optional(),
|
|
4768
|
+
workerIdleSeconds: workerFactSchema(import_zod4.z.number()).optional(),
|
|
4769
|
+
pending: import_zod4.z.readonly(import_zod4.z.array(RcPendingRequestSummarySchema))
|
|
4770
|
+
});
|
|
4771
|
+
var RcStreamEnvelopeSchema = import_zod4.z.looseObject({
|
|
4772
|
+
/** The envelope's own event id, when the host sent one. */
|
|
4773
|
+
event_id: import_zod4.z.string().optional(),
|
|
4774
|
+
/** The envelope's event type, the discriminator a consumer switches on (`control_request`, `user`, and kin). */
|
|
4775
|
+
event_type: import_zod4.z.string(),
|
|
4776
|
+
/** The sequence number the host assigned, the value a resume continues after. */
|
|
4777
|
+
sequence_num: import_zod4.z.int(),
|
|
4778
|
+
/** Who emitted the event (`worker` for the CLI's half, a client id for a client's). */
|
|
4779
|
+
source: import_zod4.z.string(),
|
|
4780
|
+
/** The event's payload, carried verbatim: its shape is the payload family's, narrowed by whichever consumer needs it. Optional in the schema's own type because the door forwards the host's envelope as it arrived, asserting only the fields it relies on itself. */
|
|
4781
|
+
payload: import_zod4.z.unknown().optional(),
|
|
4782
|
+
/** When the host created the event; the protocol's own type for this field is not established here, so it is forwarded rather than asserted. */
|
|
4783
|
+
created_at: import_zod4.z.unknown().optional()
|
|
4784
|
+
});
|
|
4785
|
+
var RcStreamEventSchema = import_zod4.z.strictObject({
|
|
4786
|
+
session: import_zod4.z.string(),
|
|
4787
|
+
envelope: RcStreamEnvelopeSchema
|
|
4788
|
+
});
|
|
4789
|
+
var RcSessionQuerySchema = import_zod4.z.strictObject({
|
|
4790
|
+
session: import_zod4.z.string().min(1).optional()
|
|
4791
|
+
});
|
|
4792
|
+
var RcSendInputSchema = import_zod4.z.strictObject({
|
|
4793
|
+
session: import_zod4.z.string().min(1),
|
|
4794
|
+
text: import_zod4.z.string().min(1)
|
|
4795
|
+
});
|
|
4796
|
+
var RcAnswerInputSchema = import_zod4.z.strictObject({
|
|
4797
|
+
session: import_zod4.z.string().min(1),
|
|
4798
|
+
request: import_zod4.z.string().min(1),
|
|
4799
|
+
approve: import_zod4.z.boolean(),
|
|
4800
|
+
text: import_zod4.z.string().optional()
|
|
4801
|
+
}).refine((input) => !input.approve || input.text === void 0 || input.text === "", { message: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
|
|
4802
|
+
var RcInterruptInputSchema = import_zod4.z.strictObject({
|
|
4803
|
+
session: import_zod4.z.string().min(1)
|
|
4804
|
+
});
|
|
4805
|
+
var RcSetModelInputSchema = import_zod4.z.strictObject({
|
|
4806
|
+
session: import_zod4.z.string().min(1),
|
|
4807
|
+
model: import_zod4.z.string().min(1)
|
|
4808
|
+
});
|
|
4809
|
+
var RcSetPermissionModeInputSchema = import_zod4.z.strictObject({
|
|
4810
|
+
session: import_zod4.z.string().min(1),
|
|
4811
|
+
mode: RcPermissionModeSchema
|
|
4812
|
+
});
|
|
4813
|
+
var RcListOutputSchema = import_zod4.z.strictObject({ sessions: import_zod4.z.readonly(import_zod4.z.array(RcSessionSummarySchema)) });
|
|
4814
|
+
var RcStatusOutputSchema = import_zod4.z.strictObject({ statuses: import_zod4.z.readonly(import_zod4.z.array(RcSessionStatusSchema)) });
|
|
4815
|
+
var RcPendingOutputSchema = import_zod4.z.strictObject({ pending: import_zod4.z.readonly(import_zod4.z.array(RcPendingRequestSummarySchema)) });
|
|
4816
|
+
var RcWriteOutputSchema = import_zod4.z.strictObject({
|
|
4817
|
+
session: import_zod4.z.string(),
|
|
4818
|
+
sequenceNums: import_zod4.z.readonly(import_zod4.z.array(import_zod4.z.number()))
|
|
4819
|
+
});
|
|
4820
|
+
var RcAnswerOutputSchema = import_zod4.z.strictObject({
|
|
4821
|
+
session: import_zod4.z.string(),
|
|
4822
|
+
request: import_zod4.z.string(),
|
|
4823
|
+
sequenceNums: import_zod4.z.readonly(import_zod4.z.array(import_zod4.z.number()))
|
|
4824
|
+
});
|
|
4825
|
+
|
|
4826
|
+
// src/frontdoor/rcApi.ts
|
|
4827
|
+
var RC_ORPC_PATH_PREFIX = "/__agent-shim/orpc";
|
|
4828
|
+
var RC_SUBSCRIBER_BUFFER_EVENTS = 100;
|
|
4829
|
+
function createRcApiRouter(deps) {
|
|
4830
|
+
const authed = import_server2.os.$context().use(({ context, next }) => {
|
|
4831
|
+
const presented = context.headers.authorization;
|
|
4832
|
+
const bearerPrefix = "bearer ".length;
|
|
4833
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
4834
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
4835
|
+
throw new import_client.ORPCError("UNAUTHORIZED", { message: "the front door's typed API demands this generation's control token as a Bearer credential" });
|
|
4836
|
+
}
|
|
4837
|
+
return next();
|
|
4838
|
+
});
|
|
4839
|
+
const delivered = (sessionId, result) => {
|
|
4840
|
+
if (!result.ok) {
|
|
4841
|
+
throw new import_client.ORPCError("BAD_GATEWAY", { message: result.message });
|
|
4842
|
+
}
|
|
4843
|
+
return result.sequenceNums;
|
|
4844
|
+
};
|
|
4845
|
+
return {
|
|
4846
|
+
rc: {
|
|
4847
|
+
list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
|
|
4848
|
+
status: authed.input(RcSessionQuerySchema).output(RcStatusOutputSchema).handler(({ input }) => {
|
|
4849
|
+
const statuses = deps.statusOf(input.session);
|
|
4850
|
+
if (input.session !== void 0 && !statuses.some((entry) => entry.id === input.session)) {
|
|
4851
|
+
throw new import_client.ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
|
|
4400
4852
|
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4853
|
+
return { statuses };
|
|
4854
|
+
}),
|
|
4855
|
+
pending: authed.input(RcSessionQuerySchema).output(RcPendingOutputSchema).handler(({ input }) => {
|
|
4856
|
+
const pending = deps.pendingOf(input.session);
|
|
4857
|
+
if (input.session !== void 0 && deps.statusOf(input.session).length === 0) {
|
|
4858
|
+
throw new import_client.ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
|
|
4859
|
+
}
|
|
4860
|
+
return { pending };
|
|
4861
|
+
}),
|
|
4862
|
+
send: authed.input(RcSendInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.inject(input.session, input.text)) })),
|
|
4863
|
+
answer: authed.input(RcAnswerInputSchema).output(RcAnswerOutputSchema).handler(async ({ input }) => {
|
|
4864
|
+
const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
|
|
4865
|
+
return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
|
|
4866
|
+
}),
|
|
4867
|
+
interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.interrupt(input.session)) })),
|
|
4868
|
+
setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setModel(input.session, input.model)) })),
|
|
4869
|
+
setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
|
|
4870
|
+
subscribe: authed.input(RcSessionQuerySchema).output((0, import_server2.eventIterator)(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
|
|
4871
|
+
const queue = [];
|
|
4872
|
+
let wake;
|
|
4873
|
+
const detach = deps.fanout.subscribe(input.session, (event) => {
|
|
4874
|
+
if (queue.length >= RC_SUBSCRIBER_BUFFER_EVENTS) {
|
|
4875
|
+
queue.shift();
|
|
4876
|
+
}
|
|
4877
|
+
queue.push(event);
|
|
4878
|
+
wake?.();
|
|
4879
|
+
});
|
|
4880
|
+
const ended = new Promise((resolve) => {
|
|
4881
|
+
signal?.addEventListener("abort", () => {
|
|
4882
|
+
resolve();
|
|
4883
|
+
}, { once: true });
|
|
4884
|
+
});
|
|
4885
|
+
try {
|
|
4886
|
+
for (; ; ) {
|
|
4887
|
+
signal?.throwIfAborted();
|
|
4888
|
+
const event = queue.shift();
|
|
4889
|
+
if (event !== void 0) {
|
|
4890
|
+
yield (0, import_server2.withEventMeta)(event, { id: String(event.envelope.sequence_num) });
|
|
4891
|
+
continue;
|
|
4892
|
+
}
|
|
4893
|
+
await Promise.race([
|
|
4894
|
+
new Promise((resolve) => {
|
|
4895
|
+
wake = resolve;
|
|
4896
|
+
}),
|
|
4897
|
+
ended
|
|
4898
|
+
]);
|
|
4899
|
+
}
|
|
4900
|
+
} finally {
|
|
4901
|
+
detach();
|
|
4902
|
+
}
|
|
4903
|
+
})
|
|
4904
|
+
}
|
|
4409
4905
|
};
|
|
4410
4906
|
}
|
|
4411
|
-
function
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4907
|
+
function createRcApiNodeHandler(deps) {
|
|
4908
|
+
const handler = new import_node.RPCHandler(createRcApiRouter(deps), { plugins: [new import_node.BodyLimitPlugin({ maxBodySize: CONTROL_BODY_CAP_BYTES })] });
|
|
4909
|
+
return {
|
|
4910
|
+
pathPrefix: RC_ORPC_PATH_PREFIX,
|
|
4911
|
+
handle: async (request4, response) => await handler.handle(request4, response, { context: { headers: request4.headers }, prefix: RC_ORPC_PATH_PREFIX })
|
|
4912
|
+
};
|
|
4913
|
+
}
|
|
4914
|
+
function frontDoorRcApiClient(port, ca, token) {
|
|
4915
|
+
const dispatcher = new import_undici.Agent({ connect: { ca } });
|
|
4916
|
+
const link = new import_fetch.RPCLink({
|
|
4917
|
+
url: `https://127.0.0.1:${String(port)}${RC_ORPC_PATH_PREFIX}`,
|
|
4918
|
+
headers: { authorization: `Bearer ${token}` },
|
|
4919
|
+
// undici's own Request and Response types and the global ones are distinct declarations of the same standard shapes (the package's bundled types sit beside @types/node's own), and its fetch refuses the global Request instance outright, so the call is rebuilt from the request's parts and the answer re-wrapped through the global constructors, never cast between the two. The request body is one bounded JSON object (the same protocol cap the handler enforces), so reading it whole costs nothing; it is the answer that streams.
|
|
4920
|
+
fetch: async (request4, _init, options) => {
|
|
4921
|
+
const body = request4.body === null ? void 0 : Buffer.from(await request4.arrayBuffer());
|
|
4922
|
+
const answered = await (0, import_undici.fetch)(request4.url, {
|
|
4923
|
+
method: request4.method,
|
|
4924
|
+
headers: [...request4.headers],
|
|
4925
|
+
...body === void 0 ? {} : { body },
|
|
4926
|
+
dispatcher,
|
|
4927
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
4928
|
+
});
|
|
4929
|
+
return new Response(answered.body, { status: answered.status, statusText: answered.statusText, headers: [...answered.headers] });
|
|
4416
4930
|
}
|
|
4417
|
-
}
|
|
4418
|
-
|
|
4419
|
-
}
|
|
4420
|
-
return void 0;
|
|
4931
|
+
});
|
|
4932
|
+
return (0, import_client.createORPCClient)(link);
|
|
4421
4933
|
}
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
return
|
|
4934
|
+
|
|
4935
|
+
// src/frontdoor/rcStream.ts
|
|
4936
|
+
var RC_STREAM_EVENT_NAME = "client_event";
|
|
4937
|
+
var SSE_BYTE_ORDER_MARK = 65279;
|
|
4938
|
+
var RC_STREAM_BACKOFF_MS = 45e3;
|
|
4939
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
|
|
4940
|
+
function isSuccessful3(status) {
|
|
4941
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
|
|
4942
|
+
}
|
|
4943
|
+
function createSseParser() {
|
|
4944
|
+
let buffer = "";
|
|
4945
|
+
let eventName;
|
|
4946
|
+
let eventId;
|
|
4947
|
+
let dataLines = [];
|
|
4948
|
+
let seenAnyText = false;
|
|
4949
|
+
return {
|
|
4950
|
+
write: (text) => {
|
|
4951
|
+
let incoming = text;
|
|
4952
|
+
if (!seenAnyText) {
|
|
4953
|
+
seenAnyText = true;
|
|
4954
|
+
incoming = incoming.charCodeAt(0) === SSE_BYTE_ORDER_MARK ? incoming.slice(1) : incoming;
|
|
4955
|
+
}
|
|
4956
|
+
buffer += incoming;
|
|
4957
|
+
const completed = [];
|
|
4958
|
+
for (; ; ) {
|
|
4959
|
+
const carriage = buffer.indexOf("\r");
|
|
4960
|
+
const lineFeed = buffer.indexOf("\n");
|
|
4961
|
+
const end = carriage === -1 ? lineFeed : lineFeed === -1 ? carriage : carriage < lineFeed ? carriage : lineFeed;
|
|
4962
|
+
if (end === -1) {
|
|
4963
|
+
break;
|
|
4964
|
+
}
|
|
4965
|
+
if (buffer[end] === "\r" && end === buffer.length - 1) {
|
|
4966
|
+
break;
|
|
4967
|
+
}
|
|
4968
|
+
const after = buffer[end] === "\r" && buffer[end + 1] === "\n" ? end + 2 : end + 1;
|
|
4969
|
+
const line = buffer.slice(0, end);
|
|
4970
|
+
buffer = buffer.slice(after);
|
|
4971
|
+
if (line === "") {
|
|
4972
|
+
if (dataLines.length > 0) {
|
|
4973
|
+
completed.push({ event: eventName, id: eventId, data: dataLines.join("\n") });
|
|
4974
|
+
}
|
|
4975
|
+
eventName = void 0;
|
|
4976
|
+
dataLines = [];
|
|
4977
|
+
continue;
|
|
4978
|
+
}
|
|
4979
|
+
if (line.startsWith(":")) {
|
|
4980
|
+
continue;
|
|
4981
|
+
}
|
|
4982
|
+
const colon = line.indexOf(":");
|
|
4983
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
4984
|
+
const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
4985
|
+
if (field === "event") {
|
|
4986
|
+
eventName = value;
|
|
4987
|
+
} else if (field === "data") {
|
|
4988
|
+
dataLines.push(value);
|
|
4989
|
+
} else if (field === "id") {
|
|
4990
|
+
eventId = value;
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
return completed;
|
|
4994
|
+
}
|
|
4995
|
+
};
|
|
4430
4996
|
}
|
|
4431
|
-
function
|
|
4432
|
-
|
|
4433
|
-
return false;
|
|
4434
|
-
}
|
|
4435
|
-
if (!("value" in value) || !("observedAt" in value)) {
|
|
4436
|
-
return false;
|
|
4437
|
-
}
|
|
4438
|
-
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
4997
|
+
function isRecord3(value) {
|
|
4998
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4439
4999
|
}
|
|
4440
|
-
function
|
|
4441
|
-
if (
|
|
4442
|
-
return
|
|
4443
|
-
}
|
|
4444
|
-
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4445
|
-
return false;
|
|
4446
|
-
}
|
|
4447
|
-
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4448
|
-
return false;
|
|
5000
|
+
function sequenceNumOf(value) {
|
|
5001
|
+
if (typeof value === "number" && Number.isFinite(value) && Number.isInteger(value)) {
|
|
5002
|
+
return value;
|
|
4449
5003
|
}
|
|
4450
|
-
if (
|
|
4451
|
-
return
|
|
5004
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) {
|
|
5005
|
+
return Number(value);
|
|
4452
5006
|
}
|
|
4453
|
-
return
|
|
5007
|
+
return void 0;
|
|
4454
5008
|
}
|
|
4455
|
-
function
|
|
4456
|
-
if (
|
|
4457
|
-
return
|
|
4458
|
-
}
|
|
4459
|
-
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4460
|
-
return false;
|
|
5009
|
+
function parseRcStreamEnvelope(parsed) {
|
|
5010
|
+
if (parsed.event !== RC_STREAM_EVENT_NAME) {
|
|
5011
|
+
return void 0;
|
|
4461
5012
|
}
|
|
4462
|
-
|
|
4463
|
-
}
|
|
4464
|
-
function sequenceNumsOfBody(body) {
|
|
4465
|
-
let parsed;
|
|
5013
|
+
let data;
|
|
4466
5014
|
try {
|
|
4467
|
-
|
|
5015
|
+
data = JSON.parse(parsed.data);
|
|
4468
5016
|
} catch {
|
|
4469
5017
|
return void 0;
|
|
4470
5018
|
}
|
|
4471
|
-
if (
|
|
5019
|
+
if (!isRecord3(data)) {
|
|
4472
5020
|
return void 0;
|
|
4473
5021
|
}
|
|
4474
|
-
const
|
|
4475
|
-
const
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
}
|
|
4480
|
-
sequenceNums.push(value);
|
|
5022
|
+
const eventType = data.event_type;
|
|
5023
|
+
const sequenceNum = sequenceNumOf(data.sequence_num);
|
|
5024
|
+
const source = data.source;
|
|
5025
|
+
if (typeof eventType !== "string" || sequenceNum === void 0 || typeof source !== "string") {
|
|
5026
|
+
return void 0;
|
|
4481
5027
|
}
|
|
4482
|
-
return
|
|
5028
|
+
return { ...data, event_type: eventType, sequence_num: sequenceNum, source };
|
|
4483
5029
|
}
|
|
4484
|
-
function
|
|
4485
|
-
const
|
|
4486
|
-
const
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
5030
|
+
function createRcEventFanout() {
|
|
5031
|
+
const perSession = /* @__PURE__ */ new Map();
|
|
5032
|
+
const every = /* @__PURE__ */ new Set();
|
|
5033
|
+
return {
|
|
5034
|
+
publish: (event) => {
|
|
5035
|
+
for (const listener of every) {
|
|
5036
|
+
listener(event);
|
|
5037
|
+
}
|
|
5038
|
+
for (const listener of perSession.get(event.session) ?? []) {
|
|
5039
|
+
listener(event);
|
|
5040
|
+
}
|
|
5041
|
+
},
|
|
5042
|
+
subscribe: (session, listener) => {
|
|
5043
|
+
if (session === void 0) {
|
|
5044
|
+
every.add(listener);
|
|
5045
|
+
return () => {
|
|
5046
|
+
every.delete(listener);
|
|
5047
|
+
};
|
|
5048
|
+
}
|
|
5049
|
+
const listeners = perSession.get(session) ?? /* @__PURE__ */ new Set();
|
|
5050
|
+
perSession.set(session, listeners);
|
|
5051
|
+
listeners.add(listener);
|
|
5052
|
+
return () => {
|
|
5053
|
+
listeners.delete(listener);
|
|
5054
|
+
if (listeners.size === 0) {
|
|
5055
|
+
perSession.delete(session);
|
|
5056
|
+
}
|
|
5057
|
+
};
|
|
4491
5058
|
}
|
|
4492
5059
|
};
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4507
|
-
const sessions = [];
|
|
4508
|
-
for (const value of candidates) {
|
|
4509
|
-
if (!isRcSessionSummary(value)) {
|
|
4510
|
-
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
5060
|
+
}
|
|
5061
|
+
function createRcStreamHub(deps) {
|
|
5062
|
+
const attachments = /* @__PURE__ */ new Map();
|
|
5063
|
+
const birth = (sessionId) => {
|
|
5064
|
+
const controller = new AbortController();
|
|
5065
|
+
let stopped = false;
|
|
5066
|
+
return {
|
|
5067
|
+
sessionId,
|
|
5068
|
+
clientId: deps.newClientId(),
|
|
5069
|
+
controller,
|
|
5070
|
+
isStopped: () => stopped,
|
|
5071
|
+
stop: () => {
|
|
5072
|
+
stopped = true;
|
|
5073
|
+
controller.abort();
|
|
4511
5074
|
}
|
|
4512
|
-
|
|
4513
|
-
}
|
|
4514
|
-
return sessions;
|
|
5075
|
+
};
|
|
4515
5076
|
};
|
|
4516
|
-
const
|
|
4517
|
-
|
|
4518
|
-
|
|
5077
|
+
const streamHeaders = (credential) => {
|
|
5078
|
+
const headers = { accept: "text/event-stream" };
|
|
5079
|
+
if (credential.authorization !== void 0) {
|
|
5080
|
+
headers.authorization = credential.authorization;
|
|
4519
5081
|
}
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
parsed = JSON.parse(answer.body);
|
|
4523
|
-
} catch {
|
|
4524
|
-
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4525
|
-
}
|
|
4526
|
-
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4527
|
-
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
5082
|
+
if (credential.anthropicVersion !== void 0) {
|
|
5083
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
4528
5084
|
}
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
if (!isRcSessionStatus(value)) {
|
|
4532
|
-
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4533
|
-
}
|
|
4534
|
-
statuses.push(value);
|
|
5085
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
5086
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
4535
5087
|
}
|
|
4536
|
-
return
|
|
5088
|
+
return headers;
|
|
4537
5089
|
};
|
|
4538
|
-
const
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
5090
|
+
const attempt = async (attachment, credential) => {
|
|
5091
|
+
const headers = streamHeaders(credential);
|
|
5092
|
+
const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
|
|
5093
|
+
if (presence.status === HTTP_STATUS.unauthorized) {
|
|
5094
|
+
return "unauthorized";
|
|
5095
|
+
}
|
|
5096
|
+
if (!isSuccessful3(presence.status)) {
|
|
5097
|
+
throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
|
|
5098
|
+
}
|
|
5099
|
+
const cursor = deps.sequenceNumOf(attachment.sessionId);
|
|
5100
|
+
const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
|
|
5101
|
+
const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
|
|
5102
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
5103
|
+
return "unauthorized";
|
|
4547
5104
|
}
|
|
4548
|
-
if (
|
|
4549
|
-
throw new Error(
|
|
5105
|
+
if (!isSuccessful3(answer.status)) {
|
|
5106
|
+
throw new Error(`the client read stream answered HTTP ${String(answer.status)}`);
|
|
4550
5107
|
}
|
|
4551
|
-
const
|
|
4552
|
-
for (const
|
|
4553
|
-
|
|
4554
|
-
|
|
5108
|
+
const parser = createSseParser();
|
|
5109
|
+
for await (const chunk of answer.chunks) {
|
|
5110
|
+
attachment.controller.signal.throwIfAborted();
|
|
5111
|
+
for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
|
|
5112
|
+
const envelope = parseRcStreamEnvelope(parsed);
|
|
5113
|
+
if (envelope === void 0) {
|
|
5114
|
+
continue;
|
|
5115
|
+
}
|
|
5116
|
+
deps.fileStreamEvent(attachment.sessionId, envelope);
|
|
5117
|
+
deps.fanout.publish({ session: attachment.sessionId, envelope });
|
|
4555
5118
|
}
|
|
4556
|
-
pending.push(value);
|
|
4557
5119
|
}
|
|
4558
|
-
return
|
|
5120
|
+
return "dropped";
|
|
4559
5121
|
};
|
|
4560
|
-
const
|
|
4561
|
-
let
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4566
|
-
}
|
|
4567
|
-
if (answer.status === HTTP_STATUS.ok) {
|
|
4568
|
-
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4569
|
-
if (sequenceNums === void 0) {
|
|
4570
|
-
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
5122
|
+
const runLoop = async (attachment) => {
|
|
5123
|
+
let retriedUnauthorized = false;
|
|
5124
|
+
for (; ; ) {
|
|
5125
|
+
if (attachment.isStopped()) {
|
|
5126
|
+
return;
|
|
4571
5127
|
}
|
|
4572
|
-
|
|
5128
|
+
const live = deps.trackedSessions().includes(attachment.sessionId);
|
|
5129
|
+
const credential = deps.credentialOf(attachment.sessionId);
|
|
5130
|
+
if (!live || credential?.authorization === void 0) {
|
|
5131
|
+
if (attachments.get(attachment.sessionId) === attachment) {
|
|
5132
|
+
attachments.delete(attachment.sessionId);
|
|
5133
|
+
}
|
|
5134
|
+
return;
|
|
5135
|
+
}
|
|
5136
|
+
let outcome;
|
|
5137
|
+
try {
|
|
5138
|
+
outcome = await attempt(attachment, credential);
|
|
5139
|
+
} catch (error) {
|
|
5140
|
+
if (attachment.isStopped()) {
|
|
5141
|
+
return;
|
|
5142
|
+
}
|
|
5143
|
+
deps.log?.(`rc stream ${attachment.sessionId}: attachment failed (${error instanceof Error ? error.message : String(error)}); backing off`);
|
|
5144
|
+
await deps.sleep(deps.backoffMs);
|
|
5145
|
+
retriedUnauthorized = false;
|
|
5146
|
+
continue;
|
|
5147
|
+
}
|
|
5148
|
+
if (attachment.isStopped()) {
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5151
|
+
if (outcome === "dropped") {
|
|
5152
|
+
retriedUnauthorized = false;
|
|
5153
|
+
continue;
|
|
5154
|
+
}
|
|
5155
|
+
if (!retriedUnauthorized) {
|
|
5156
|
+
retriedUnauthorized = true;
|
|
5157
|
+
continue;
|
|
5158
|
+
}
|
|
5159
|
+
deps.log?.(`rc stream ${attachment.sessionId}: the client read stream refused the observed Authorization bearer past one retry; backing off`);
|
|
5160
|
+
await deps.sleep(deps.backoffMs);
|
|
5161
|
+
retriedUnauthorized = false;
|
|
4573
5162
|
}
|
|
4574
|
-
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4575
5163
|
};
|
|
4576
|
-
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
4577
5164
|
return {
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
5165
|
+
reconcile: () => {
|
|
5166
|
+
const live = new Set(deps.trackedSessions());
|
|
5167
|
+
for (const [sessionId, attachment] of attachments) {
|
|
5168
|
+
if (!live.has(sessionId)) {
|
|
5169
|
+
attachment.stop();
|
|
5170
|
+
attachments.delete(sessionId);
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
for (const sessionId of live) {
|
|
5174
|
+
if (attachments.has(sessionId)) {
|
|
5175
|
+
continue;
|
|
5176
|
+
}
|
|
5177
|
+
if (deps.credentialOf(sessionId)?.authorization === void 0) {
|
|
5178
|
+
continue;
|
|
5179
|
+
}
|
|
5180
|
+
const attachment = birth(sessionId);
|
|
5181
|
+
attachments.set(sessionId, attachment);
|
|
5182
|
+
void runLoop(attachment).catch((error) => {
|
|
5183
|
+
deps.log?.(`rc stream ${sessionId}: the attachment loop ended unexpectedly (${error instanceof Error ? error.message : String(error)})`);
|
|
5184
|
+
});
|
|
5185
|
+
}
|
|
5186
|
+
},
|
|
5187
|
+
close: () => {
|
|
5188
|
+
for (const attachment of attachments.values()) {
|
|
5189
|
+
attachment.stop();
|
|
5190
|
+
}
|
|
5191
|
+
attachments.clear();
|
|
5192
|
+
}
|
|
4590
5193
|
};
|
|
4591
5194
|
}
|
|
4592
5195
|
|
|
@@ -4596,31 +5199,31 @@ var import_node_crypto7 = require("node:crypto");
|
|
|
4596
5199
|
// src/headroom/state.ts
|
|
4597
5200
|
var import_node_crypto6 = require("node:crypto");
|
|
4598
5201
|
var import_node_path8 = __toESM(require("node:path"), 1);
|
|
4599
|
-
var
|
|
5202
|
+
var import_zod5 = require("zod");
|
|
4600
5203
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
4601
5204
|
var HEADROOM_STATE_SCHEMA_VERSION = 2;
|
|
4602
|
-
var HeadroomStateSchema =
|
|
5205
|
+
var HeadroomStateSchema = import_zod5.z.strictObject({
|
|
4603
5206
|
/** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
|
|
4604
|
-
supervisorPid:
|
|
5207
|
+
supervisorPid: import_zod5.z.number().int().positive().optional(),
|
|
4605
5208
|
/** The headroom proxy process itself. Absent while the supervisor is between restarts. */
|
|
4606
|
-
headroomPid:
|
|
5209
|
+
headroomPid: import_zod5.z.number().int().positive().optional(),
|
|
4607
5210
|
/**
|
|
4608
5211
|
* The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
|
|
4609
5212
|
*/
|
|
4610
|
-
socketPath:
|
|
5213
|
+
socketPath: import_zod5.z.string().min(1).optional(),
|
|
4611
5214
|
/** The `headroom --version` output of the running install. */
|
|
4612
|
-
version:
|
|
5215
|
+
version: import_zod5.z.string().optional(),
|
|
4613
5216
|
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
4614
|
-
allowlistHash:
|
|
5217
|
+
allowlistHash: import_zod5.z.string().optional(),
|
|
4615
5218
|
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
4616
|
-
settingsHash:
|
|
5219
|
+
settingsHash: import_zod5.z.string().optional(),
|
|
4617
5220
|
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
4618
|
-
installedSource:
|
|
5221
|
+
installedSource: import_zod5.z.string().optional(),
|
|
4619
5222
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
4620
|
-
lastError:
|
|
5223
|
+
lastError: import_zod5.z.string().optional()
|
|
4621
5224
|
});
|
|
4622
|
-
var HeadroomSessionSchema =
|
|
4623
|
-
var HeadroomLockSchema =
|
|
5225
|
+
var HeadroomSessionSchema = import_zod5.z.strictObject({ pid: import_zod5.z.number().int().positive(), startedAt: import_zod5.z.number(), supervisorPid: import_zod5.z.number().int().positive() });
|
|
5226
|
+
var HeadroomLockSchema = import_zod5.z.strictObject({ pid: import_zod5.z.number().int().positive(), at: import_zod5.z.number() });
|
|
4624
5227
|
function parseJson(raw, schema) {
|
|
4625
5228
|
if (raw === void 0 || raw.trim() === "") {
|
|
4626
5229
|
return void 0;
|
|
@@ -4800,118 +5403,118 @@ var import_node_path10 = __toESM(require("node:path"), 1);
|
|
|
4800
5403
|
var import_node_path9 = __toESM(require("node:path"), 1);
|
|
4801
5404
|
|
|
4802
5405
|
// src/usage/schema.ts
|
|
4803
|
-
var
|
|
5406
|
+
var import_zod6 = require("zod");
|
|
4804
5407
|
var USAGE_SCHEMA_VERSION = 1;
|
|
4805
|
-
var VersionSchema =
|
|
4806
|
-
var InstantSchema =
|
|
4807
|
-
var TokenCountSchema =
|
|
4808
|
-
var TokenUsageSchema =
|
|
5408
|
+
var VersionSchema = import_zod6.z.literal(USAGE_SCHEMA_VERSION);
|
|
5409
|
+
var InstantSchema = import_zod6.z.iso.datetime();
|
|
5410
|
+
var TokenCountSchema = import_zod6.z.number().int().nonnegative();
|
|
5411
|
+
var TokenUsageSchema = import_zod6.z.strictObject({
|
|
4809
5412
|
inputTokens: TokenCountSchema.optional(),
|
|
4810
5413
|
outputTokens: TokenCountSchema.optional(),
|
|
4811
5414
|
cacheCreationInputTokens: TokenCountSchema.optional(),
|
|
4812
5415
|
cacheReadInputTokens: TokenCountSchema.optional()
|
|
4813
5416
|
});
|
|
4814
5417
|
var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
|
|
4815
|
-
var LimitClassificationSchema =
|
|
4816
|
-
kind:
|
|
5418
|
+
var LimitClassificationSchema = import_zod6.z.strictObject({
|
|
5419
|
+
kind: import_zod6.z.enum(LIMIT_KINDS),
|
|
4817
5420
|
/** Seconds to wait before retrying, from the response's `retry-after`. */
|
|
4818
|
-
retryAfterSeconds:
|
|
5421
|
+
retryAfterSeconds: import_zod6.z.number().nonnegative().optional(),
|
|
4819
5422
|
/** When the limiting window resets, from the upstream's own reset header. */
|
|
4820
5423
|
resetAt: InstantSchema.optional(),
|
|
4821
5424
|
/** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
|
|
4822
|
-
window:
|
|
5425
|
+
window: import_zod6.z.string().optional(),
|
|
4823
5426
|
/** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
|
|
4824
|
-
evidence:
|
|
5427
|
+
evidence: import_zod6.z.array(import_zod6.z.string())
|
|
4825
5428
|
});
|
|
4826
|
-
var UsageRecordSchema =
|
|
5429
|
+
var UsageRecordSchema = import_zod6.z.strictObject({
|
|
4827
5430
|
schemaVersion: VersionSchema,
|
|
4828
5431
|
/** When the front door received the request. */
|
|
4829
5432
|
at: InstantSchema,
|
|
4830
5433
|
/** The launching identity, when the launch resolved one. */
|
|
4831
|
-
identity:
|
|
5434
|
+
identity: import_zod6.z.string().optional(),
|
|
4832
5435
|
/** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
|
|
4833
|
-
provider:
|
|
5436
|
+
provider: import_zod6.z.string(),
|
|
4834
5437
|
/** The launch's session id, generated per launch. */
|
|
4835
|
-
sessionId:
|
|
5438
|
+
sessionId: import_zod6.z.string().optional(),
|
|
4836
5439
|
/** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
|
|
4837
|
-
project:
|
|
5440
|
+
project: import_zod6.z.string().optional(),
|
|
4838
5441
|
/** The front-door route that served the request. */
|
|
4839
|
-
route:
|
|
4840
|
-
method:
|
|
5442
|
+
route: import_zod6.z.string(),
|
|
5443
|
+
method: import_zod6.z.string(),
|
|
4841
5444
|
/** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
|
|
4842
|
-
endpoint:
|
|
5445
|
+
endpoint: import_zod6.z.string(),
|
|
4843
5446
|
/** The model the response named. */
|
|
4844
|
-
model:
|
|
4845
|
-
status:
|
|
5447
|
+
model: import_zod6.z.string().optional(),
|
|
5448
|
+
status: import_zod6.z.number().int(),
|
|
4846
5449
|
/** Milliseconds from the request's arrival to its response head. */
|
|
4847
|
-
latencyMs:
|
|
5450
|
+
latencyMs: import_zod6.z.number().int().nonnegative(),
|
|
4848
5451
|
/** Milliseconds from the request's arrival to the end of its response body. */
|
|
4849
|
-
durationMs:
|
|
5452
|
+
durationMs: import_zod6.z.number().int().nonnegative(),
|
|
4850
5453
|
/** Whether the client received the whole body. */
|
|
4851
|
-
outcome:
|
|
5454
|
+
outcome: import_zod6.z.enum(["completed", "aborted"]),
|
|
4852
5455
|
usage: TokenUsageSchema.optional(),
|
|
4853
5456
|
/** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
|
|
4854
|
-
rateLimitHeaders:
|
|
5457
|
+
rateLimitHeaders: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()).optional(),
|
|
4855
5458
|
limit: LimitClassificationSchema.optional(),
|
|
4856
5459
|
/** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
|
|
4857
|
-
requestId:
|
|
5460
|
+
requestId: import_zod6.z.string().optional()
|
|
4858
5461
|
});
|
|
4859
|
-
var QuotaWindowSchema =
|
|
5462
|
+
var QuotaWindowSchema = import_zod6.z.strictObject({
|
|
4860
5463
|
/** The fraction of the window used, as the upstream reports it (0 to 1). */
|
|
4861
|
-
utilization:
|
|
5464
|
+
utilization: import_zod6.z.number().nonnegative().optional(),
|
|
4862
5465
|
resetsAt: InstantSchema.optional(),
|
|
4863
5466
|
/** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
|
|
4864
|
-
status:
|
|
5467
|
+
status: import_zod6.z.string().optional()
|
|
4865
5468
|
});
|
|
4866
|
-
var UnifiedRateLimitSchema =
|
|
5469
|
+
var UnifiedRateLimitSchema = import_zod6.z.strictObject({
|
|
4867
5470
|
/** The overall status: `allowed`, `allowed_warning` or `rejected`. */
|
|
4868
|
-
status:
|
|
5471
|
+
status: import_zod6.z.string().optional(),
|
|
4869
5472
|
fiveHour: QuotaWindowSchema.optional(),
|
|
4870
5473
|
sevenDay: QuotaWindowSchema.optional(),
|
|
4871
5474
|
/** The window that currently binds. */
|
|
4872
|
-
representativeClaim:
|
|
5475
|
+
representativeClaim: import_zod6.z.string().optional(),
|
|
4873
5476
|
/** When the binding window resets. */
|
|
4874
5477
|
resetAt: InstantSchema.optional(),
|
|
4875
5478
|
/** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
|
|
4876
|
-
overageStatus:
|
|
5479
|
+
overageStatus: import_zod6.z.string().optional()
|
|
4877
5480
|
});
|
|
4878
|
-
var RateLimitStateSchema =
|
|
5481
|
+
var RateLimitStateSchema = import_zod6.z.strictObject({
|
|
4879
5482
|
observedAt: InstantSchema,
|
|
4880
5483
|
/** Every quota and rate-limit header on that response, by name. */
|
|
4881
|
-
headers:
|
|
5484
|
+
headers: import_zod6.z.record(import_zod6.z.string(), import_zod6.z.string()),
|
|
4882
5485
|
unified: UnifiedRateLimitSchema.optional()
|
|
4883
5486
|
});
|
|
4884
5487
|
var LimitEventSchema = LimitClassificationSchema.extend({
|
|
4885
5488
|
observedAt: InstantSchema,
|
|
4886
|
-
status:
|
|
5489
|
+
status: import_zod6.z.number().int()
|
|
4887
5490
|
});
|
|
4888
|
-
var ProviderQuotaWindowSchema =
|
|
5491
|
+
var ProviderQuotaWindowSchema = import_zod6.z.strictObject({
|
|
4889
5492
|
/** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
|
|
4890
|
-
measures:
|
|
5493
|
+
measures: import_zod6.z.string(),
|
|
4891
5494
|
/** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
|
|
4892
|
-
periodMs:
|
|
5495
|
+
periodMs: import_zod6.z.number().int().positive().optional(),
|
|
4893
5496
|
/** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
|
|
4894
|
-
period:
|
|
5497
|
+
period: import_zod6.z.string().optional(),
|
|
4895
5498
|
/** The fraction of the window used (0 to 1). */
|
|
4896
|
-
utilization:
|
|
5499
|
+
utilization: import_zod6.z.number().nonnegative(),
|
|
4897
5500
|
resetsAt: InstantSchema.optional(),
|
|
4898
5501
|
/** Absolute counts, when the provider reports them for this window. */
|
|
4899
|
-
limit:
|
|
4900
|
-
used:
|
|
4901
|
-
remaining:
|
|
5502
|
+
limit: import_zod6.z.number().nonnegative().optional(),
|
|
5503
|
+
used: import_zod6.z.number().nonnegative().optional(),
|
|
5504
|
+
remaining: import_zod6.z.number().nonnegative().optional()
|
|
4902
5505
|
});
|
|
4903
|
-
var ProviderQuotaSchema =
|
|
5506
|
+
var ProviderQuotaSchema = import_zod6.z.strictObject({
|
|
4904
5507
|
observedAt: InstantSchema,
|
|
4905
5508
|
/** Which usage endpoint produced it (`z.ai`). */
|
|
4906
|
-
source:
|
|
5509
|
+
source: import_zod6.z.string(),
|
|
4907
5510
|
/** The subscription level the endpoint reports, when it does. */
|
|
4908
|
-
level:
|
|
4909
|
-
windows:
|
|
5511
|
+
level: import_zod6.z.string().optional(),
|
|
5512
|
+
windows: import_zod6.z.array(ProviderQuotaWindowSchema)
|
|
4910
5513
|
});
|
|
4911
|
-
var ProviderUsageStateSchema =
|
|
5514
|
+
var ProviderUsageStateSchema = import_zod6.z.strictObject({
|
|
4912
5515
|
lastRequestAt: InstantSchema,
|
|
4913
|
-
lastStatus:
|
|
4914
|
-
lastModel:
|
|
5516
|
+
lastStatus: import_zod6.z.number().int(),
|
|
5517
|
+
lastModel: import_zod6.z.string().optional(),
|
|
4915
5518
|
/** The latest rate-limit state, from the last response that carried any rate-limit header. */
|
|
4916
5519
|
rateLimit: RateLimitStateSchema.optional(),
|
|
4917
5520
|
/** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
|
|
@@ -4919,31 +5522,31 @@ var ProviderUsageStateSchema = import_zod5.z.strictObject({
|
|
|
4919
5522
|
/** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
|
|
4920
5523
|
quota: ProviderQuotaSchema.optional()
|
|
4921
5524
|
});
|
|
4922
|
-
var AccountMetadataSchema =
|
|
4923
|
-
accountUuid:
|
|
4924
|
-
emailAddress:
|
|
4925
|
-
displayName:
|
|
4926
|
-
organizationUuid:
|
|
4927
|
-
organizationName:
|
|
4928
|
-
organizationType:
|
|
4929
|
-
organizationRole:
|
|
4930
|
-
workspaceRole:
|
|
4931
|
-
billingType:
|
|
4932
|
-
seatTier:
|
|
4933
|
-
organizationRateLimitTier:
|
|
4934
|
-
userRateLimitTier:
|
|
4935
|
-
hasExtraUsageEnabled:
|
|
4936
|
-
subscriptionCreatedAt:
|
|
4937
|
-
accountCreatedAt:
|
|
5525
|
+
var AccountMetadataSchema = import_zod6.z.strictObject({
|
|
5526
|
+
accountUuid: import_zod6.z.string().optional(),
|
|
5527
|
+
emailAddress: import_zod6.z.string().optional(),
|
|
5528
|
+
displayName: import_zod6.z.string().optional(),
|
|
5529
|
+
organizationUuid: import_zod6.z.string().optional(),
|
|
5530
|
+
organizationName: import_zod6.z.string().optional(),
|
|
5531
|
+
organizationType: import_zod6.z.string().optional(),
|
|
5532
|
+
organizationRole: import_zod6.z.string().optional(),
|
|
5533
|
+
workspaceRole: import_zod6.z.string().optional(),
|
|
5534
|
+
billingType: import_zod6.z.string().optional(),
|
|
5535
|
+
seatTier: import_zod6.z.string().optional(),
|
|
5536
|
+
organizationRateLimitTier: import_zod6.z.string().optional(),
|
|
5537
|
+
userRateLimitTier: import_zod6.z.string().optional(),
|
|
5538
|
+
hasExtraUsageEnabled: import_zod6.z.boolean().optional(),
|
|
5539
|
+
subscriptionCreatedAt: import_zod6.z.string().optional(),
|
|
5540
|
+
accountCreatedAt: import_zod6.z.string().optional()
|
|
4938
5541
|
});
|
|
4939
|
-
var UsageSnapshotSchema =
|
|
5542
|
+
var UsageSnapshotSchema = import_zod6.z.strictObject({
|
|
4940
5543
|
schemaVersion: VersionSchema,
|
|
4941
|
-
identity:
|
|
5544
|
+
identity: import_zod6.z.string(),
|
|
4942
5545
|
updatedAt: InstantSchema,
|
|
4943
5546
|
/** The identity's account metadata as of `updatedAt`. */
|
|
4944
5547
|
account: AccountMetadataSchema.optional(),
|
|
4945
5548
|
/** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
|
|
4946
|
-
providers:
|
|
5549
|
+
providers: import_zod6.z.record(import_zod6.z.string(), ProviderUsageStateSchema)
|
|
4947
5550
|
});
|
|
4948
5551
|
|
|
4949
5552
|
// src/usage/account.ts
|
|
@@ -5147,20 +5750,20 @@ function quotaWarnings(snapshot, provider, nowMs) {
|
|
|
5147
5750
|
}
|
|
5148
5751
|
|
|
5149
5752
|
// src/usage/plan.ts
|
|
5150
|
-
var
|
|
5151
|
-
var PlanClassSchema =
|
|
5152
|
-
|
|
5153
|
-
kind:
|
|
5753
|
+
var import_zod7 = require("zod");
|
|
5754
|
+
var PlanClassSchema = import_zod7.z.union([
|
|
5755
|
+
import_zod7.z.strictObject({
|
|
5756
|
+
kind: import_zod7.z.literal("subscription"),
|
|
5154
5757
|
/** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
|
|
5155
|
-
capacity:
|
|
5758
|
+
capacity: import_zod7.z.number().positive(),
|
|
5156
5759
|
/** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
|
|
5157
|
-
recognised:
|
|
5760
|
+
recognised: import_zod7.z.boolean(),
|
|
5158
5761
|
/** The tier string the classification rests on, when the account had one. */
|
|
5159
|
-
tier:
|
|
5762
|
+
tier: import_zod7.z.string().optional()
|
|
5160
5763
|
}),
|
|
5161
|
-
|
|
5162
|
-
kind:
|
|
5163
|
-
tier:
|
|
5764
|
+
import_zod7.z.strictObject({
|
|
5765
|
+
kind: import_zod7.z.literal("pay-per-use"),
|
|
5766
|
+
tier: import_zod7.z.string().optional()
|
|
5164
5767
|
})
|
|
5165
5768
|
]);
|
|
5166
5769
|
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
@@ -5229,17 +5832,17 @@ function parseUnifiedRateLimit(headers) {
|
|
|
5229
5832
|
}
|
|
5230
5833
|
|
|
5231
5834
|
// src/usage/scan.ts
|
|
5232
|
-
var
|
|
5233
|
-
var AnthropicUsageSchema =
|
|
5234
|
-
input_tokens:
|
|
5235
|
-
output_tokens:
|
|
5236
|
-
cache_creation_input_tokens:
|
|
5237
|
-
cache_read_input_tokens:
|
|
5835
|
+
var import_zod8 = require("zod");
|
|
5836
|
+
var AnthropicUsageSchema = import_zod8.z.looseObject({
|
|
5837
|
+
input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5838
|
+
output_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5839
|
+
cache_creation_input_tokens: import_zod8.z.number().int().nonnegative().nullish(),
|
|
5840
|
+
cache_read_input_tokens: import_zod8.z.number().int().nonnegative().nullish()
|
|
5238
5841
|
});
|
|
5239
|
-
var ErrorObjectSchema =
|
|
5240
|
-
type:
|
|
5241
|
-
code:
|
|
5242
|
-
message:
|
|
5842
|
+
var ErrorObjectSchema = import_zod8.z.looseObject({
|
|
5843
|
+
type: import_zod8.z.string().optional(),
|
|
5844
|
+
code: import_zod8.z.union([import_zod8.z.string(), import_zod8.z.number()]).optional(),
|
|
5845
|
+
message: import_zod8.z.string().optional()
|
|
5243
5846
|
});
|
|
5244
5847
|
|
|
5245
5848
|
// src/usage/middleware.ts
|
|
@@ -5399,7 +6002,7 @@ function rankPool(input) {
|
|
|
5399
6002
|
|
|
5400
6003
|
// src/usage/poolPick.ts
|
|
5401
6004
|
var import_node_path11 = __toESM(require("node:path"), 1);
|
|
5402
|
-
var
|
|
6005
|
+
var import_zod9 = require("zod");
|
|
5403
6006
|
|
|
5404
6007
|
// src/usage/store.ts
|
|
5405
6008
|
var DAY_MS2 = 864e5;
|
|
@@ -5408,10 +6011,10 @@ var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
|
5408
6011
|
|
|
5409
6012
|
// src/usage/poolPick.ts
|
|
5410
6013
|
var SCHEMA_VERSION = 1;
|
|
5411
|
-
var PickEntrySchema =
|
|
5412
|
-
var PicksFileSchema =
|
|
5413
|
-
schemaVersion:
|
|
5414
|
-
picks:
|
|
6014
|
+
var PickEntrySchema = import_zod9.z.strictObject({ identity: import_zod9.z.string().min(1), at: import_zod9.z.iso.datetime() });
|
|
6015
|
+
var PicksFileSchema = import_zod9.z.strictObject({
|
|
6016
|
+
schemaVersion: import_zod9.z.literal(SCHEMA_VERSION),
|
|
6017
|
+
picks: import_zod9.z.record(import_zod9.z.string().min(1), PickEntrySchema)
|
|
5415
6018
|
});
|
|
5416
6019
|
function errorMessage(error) {
|
|
5417
6020
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5611,33 +6214,33 @@ function collectPoolPick(params) {
|
|
|
5611
6214
|
}
|
|
5612
6215
|
|
|
5613
6216
|
// src/usage/pickReportSchema.ts
|
|
5614
|
-
var
|
|
5615
|
-
var PoolPickCandidateViewSchema =
|
|
5616
|
-
identity:
|
|
6217
|
+
var import_zod10 = require("zod");
|
|
6218
|
+
var PoolPickCandidateViewSchema = import_zod10.z.strictObject({
|
|
6219
|
+
identity: import_zod10.z.string(),
|
|
5617
6220
|
/** In pick order: `scored` has usable quota data, `unknown` has none, `pay-per-use` bills by use, `ineligible` is refused right now. */
|
|
5618
|
-
class:
|
|
6221
|
+
class: import_zod10.z.enum(["scored", "unknown", "pay-per-use", "ineligible"]),
|
|
5619
6222
|
/** Plan-size-weighted remaining quota per hour until its reset; present for `scored` only. */
|
|
5620
|
-
score:
|
|
6223
|
+
score: import_zod10.z.number().optional(),
|
|
5621
6224
|
/** False when the five-hour window would run dry, at the observed pace, before it resets. */
|
|
5622
|
-
feasible:
|
|
6225
|
+
feasible: import_zod10.z.boolean(),
|
|
5623
6226
|
/** When an `ineligible` member can be used again, as an ISO instant. */
|
|
5624
|
-
blockedUntil:
|
|
6227
|
+
blockedUntil: import_zod10.z.iso.datetime().optional(),
|
|
5625
6228
|
plan: PlanClassSchema,
|
|
5626
6229
|
/** The facts behind the class and score, in the order they matter. */
|
|
5627
|
-
reasons:
|
|
6230
|
+
reasons: import_zod10.z.array(import_zod10.z.string())
|
|
5628
6231
|
});
|
|
5629
|
-
var PoolPickReportSchema =
|
|
5630
|
-
pool:
|
|
5631
|
-
directory:
|
|
6232
|
+
var PoolPickReportSchema = import_zod10.z.strictObject({
|
|
6233
|
+
pool: import_zod10.z.string(),
|
|
6234
|
+
directory: import_zod10.z.string(),
|
|
5632
6235
|
/** The member a launch would run as, absent when every member is refused. */
|
|
5633
|
-
pick:
|
|
5634
|
-
candidates:
|
|
6236
|
+
pick: import_zod10.z.string().optional(),
|
|
6237
|
+
candidates: import_zod10.z.array(PoolPickCandidateViewSchema),
|
|
5635
6238
|
/** Members that name an identity that does not exist; a launch skips them. */
|
|
5636
|
-
missing:
|
|
6239
|
+
missing: import_zod10.z.array(import_zod10.z.string()),
|
|
5637
6240
|
/** When nothing can be picked, the soonest any member returns. */
|
|
5638
|
-
earliestReturn:
|
|
6241
|
+
earliestReturn: import_zod10.z.strictObject({ identity: import_zod10.z.string(), at: import_zod10.z.iso.datetime() }).optional(),
|
|
5639
6242
|
/** Why the last-pick record could not be read, when that is the case. */
|
|
5640
|
-
stickyProblem:
|
|
6243
|
+
stickyProblem: import_zod10.z.string().optional()
|
|
5641
6244
|
});
|
|
5642
6245
|
|
|
5643
6246
|
// src/headroom/ensure.ts
|
|
@@ -5800,10 +6403,10 @@ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
|
|
|
5800
6403
|
}
|
|
5801
6404
|
|
|
5802
6405
|
// src/headroom/source.ts
|
|
5803
|
-
var
|
|
6406
|
+
var import_zod11 = require("zod");
|
|
5804
6407
|
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
5805
6408
|
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
5806
|
-
var DirectUrlSchema =
|
|
6409
|
+
var DirectUrlSchema = import_zod11.z.object({ vcs_info: import_zod11.z.object({ commit_id: import_zod11.z.string() }) });
|
|
5807
6410
|
function isMovingGitSource(spec) {
|
|
5808
6411
|
const match = GIT_SPEC_RE.exec(spec);
|
|
5809
6412
|
if (match === null) {
|
|
@@ -6517,11 +7120,11 @@ var import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
|
6517
7120
|
var import_node_path17 = __toESM(require("node:path"), 1);
|
|
6518
7121
|
|
|
6519
7122
|
// src/credentialCache.ts
|
|
6520
|
-
var
|
|
7123
|
+
var import_zod12 = require("zod");
|
|
6521
7124
|
var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
6522
|
-
var CachedCredentialSchema =
|
|
6523
|
-
token:
|
|
6524
|
-
fetchedAt:
|
|
7125
|
+
var CachedCredentialSchema = import_zod12.z.strictObject({
|
|
7126
|
+
token: import_zod12.z.string().min(1),
|
|
7127
|
+
fetchedAt: import_zod12.z.number().int().nonnegative(),
|
|
6525
7128
|
source: CredentialSourceSchema
|
|
6526
7129
|
});
|
|
6527
7130
|
function effectiveStore(cache, platform) {
|
|
@@ -6787,14 +7390,14 @@ function providerExists(paths, name) {
|
|
|
6787
7390
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
6788
7391
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
6789
7392
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
6790
|
-
function
|
|
7393
|
+
function isRecord4(value) {
|
|
6791
7394
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6792
7395
|
}
|
|
6793
7396
|
function legacyProviderConversion(raw) {
|
|
6794
|
-
if (!
|
|
7397
|
+
if (!isRecord4(raw) || "credential" in raw) {
|
|
6795
7398
|
return void 0;
|
|
6796
7399
|
}
|
|
6797
|
-
const env =
|
|
7400
|
+
const env = isRecord4(raw.env) ? raw.env : void 0;
|
|
6798
7401
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
6799
7402
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
6800
7403
|
if (fields.length === 0) {
|
|
@@ -7758,7 +8361,7 @@ function realResolveClaudeBinary(isOwnBinary) {
|
|
|
7758
8361
|
|
|
7759
8362
|
// src/checkReport.ts
|
|
7760
8363
|
var import_node_path23 = __toESM(require("node:path"), 1);
|
|
7761
|
-
var
|
|
8364
|
+
var import_zod13 = require("zod");
|
|
7762
8365
|
|
|
7763
8366
|
// src/cli/bool.ts
|
|
7764
8367
|
function parseBoolWord(input) {
|
|
@@ -7873,9 +8476,9 @@ function lookupKeychainService(run, farmRoot) {
|
|
|
7873
8476
|
note: serviceName === void 0 ? `A Keychain entry was found for account "${farmRoot}" but its service name could not be parsed from security's output.` : `Keychain service name for this identity: "${serviceName}".`
|
|
7874
8477
|
};
|
|
7875
8478
|
}
|
|
7876
|
-
var SettingsSecretsShapeSchema =
|
|
7877
|
-
env:
|
|
7878
|
-
hooks:
|
|
8479
|
+
var SettingsSecretsShapeSchema = import_zod13.z.looseObject({
|
|
8480
|
+
env: import_zod13.z.record(import_zod13.z.string(), import_zod13.z.unknown()).optional(),
|
|
8481
|
+
hooks: import_zod13.z.record(import_zod13.z.string(), import_zod13.z.array(import_zod13.z.unknown())).optional()
|
|
7879
8482
|
});
|
|
7880
8483
|
function hookCommandCountOf(group) {
|
|
7881
8484
|
if (typeof group !== "object" || group === null) {
|
|
@@ -8169,11 +8772,11 @@ function collectCheckReport(params) {
|
|
|
8169
8772
|
// src/claudeShim.ts
|
|
8170
8773
|
var import_node_fs8 = __toESM(require("node:fs"), 1);
|
|
8171
8774
|
var import_node_path24 = __toESM(require("node:path"), 1);
|
|
8172
|
-
var
|
|
8173
|
-
var ClaudeShimStateSchema =
|
|
8174
|
-
targetPath:
|
|
8175
|
-
method:
|
|
8176
|
-
installedAtMs:
|
|
8775
|
+
var import_zod14 = require("zod");
|
|
8776
|
+
var ClaudeShimStateSchema = import_zod14.z.strictObject({
|
|
8777
|
+
targetPath: import_zod14.z.string().min(1),
|
|
8778
|
+
method: import_zod14.z.enum(["hardlink", "copy"]),
|
|
8779
|
+
installedAtMs: import_zod14.z.number()
|
|
8177
8780
|
});
|
|
8178
8781
|
function commandFilename(ownExecutablePath, commandName) {
|
|
8179
8782
|
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
@@ -8732,9 +9335,11 @@ var BOOLEAN_FLAGS = [
|
|
|
8732
9335
|
];
|
|
8733
9336
|
var LAUNCHER_FLAG_NAMES = [
|
|
8734
9337
|
...VALUED_FLAGS,
|
|
9338
|
+
"--native",
|
|
8735
9339
|
"--no-provider",
|
|
8736
9340
|
...BOOLEAN_FLAGS.flatMap(({ flag }) => [flag, `--no-${flag.slice(2)}`])
|
|
8737
9341
|
];
|
|
9342
|
+
var NATIVE_FLAG = "--native";
|
|
8738
9343
|
var TERMINATOR2 = "--";
|
|
8739
9344
|
function matchValuedFlag(token) {
|
|
8740
9345
|
for (const flag of VALUED_FLAGS) {
|
|
@@ -8762,6 +9367,7 @@ function parseLauncherArgv(argv) {
|
|
|
8762
9367
|
const first = argv[0];
|
|
8763
9368
|
const positionalIdentity = first !== void 0 && first.startsWith("@") && first.length > 1 ? first.slice(1) : void 0;
|
|
8764
9369
|
const remaining = positionalIdentity === void 0 ? argv : argv.slice(1);
|
|
9370
|
+
let native = false;
|
|
8765
9371
|
let flagIdentity;
|
|
8766
9372
|
let configProfile;
|
|
8767
9373
|
let provider;
|
|
@@ -8783,6 +9389,10 @@ function parseLauncherArgv(argv) {
|
|
|
8783
9389
|
provider = false;
|
|
8784
9390
|
continue;
|
|
8785
9391
|
}
|
|
9392
|
+
if (token === NATIVE_FLAG) {
|
|
9393
|
+
native = true;
|
|
9394
|
+
continue;
|
|
9395
|
+
}
|
|
8786
9396
|
const booleanFlag = matchBooleanFlag(token);
|
|
8787
9397
|
if (booleanFlag !== void 0) {
|
|
8788
9398
|
booleans[booleanFlag.key] = booleanFlag.value;
|
|
@@ -8836,6 +9446,7 @@ function parseLauncherArgv(argv) {
|
|
|
8836
9446
|
...identity === void 0 ? {} : { identity },
|
|
8837
9447
|
...configProfile === void 0 ? {} : { configProfile },
|
|
8838
9448
|
...provider === void 0 ? {} : { provider },
|
|
9449
|
+
...native ? { native: true } : {},
|
|
8839
9450
|
...booleans,
|
|
8840
9451
|
categoryFlags,
|
|
8841
9452
|
shareFlags,
|
|
@@ -9545,6 +10156,9 @@ function buildFarmRuntime(paths, cwd) {
|
|
|
9545
10156
|
...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
|
|
9546
10157
|
};
|
|
9547
10158
|
}
|
|
10159
|
+
function realClaudeBinaryResolver(paths) {
|
|
10160
|
+
return realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()));
|
|
10161
|
+
}
|
|
9548
10162
|
function realPrepareLaunchParams(paths, options) {
|
|
9549
10163
|
const { farm } = options;
|
|
9550
10164
|
return {
|
|
@@ -9552,7 +10166,7 @@ function realPrepareLaunchParams(paths, options) {
|
|
|
9552
10166
|
fs: realFsPort,
|
|
9553
10167
|
proc: options.proc,
|
|
9554
10168
|
log: options.log,
|
|
9555
|
-
resolveClaudeBinary:
|
|
10169
|
+
resolveClaudeBinary: realClaudeBinaryResolver(paths),
|
|
9556
10170
|
farm: farm.runtime,
|
|
9557
10171
|
headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
|
|
9558
10172
|
frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
|
|
@@ -9643,11 +10257,16 @@ function prepareClaudeLaunch(options) {
|
|
|
9643
10257
|
ProviderNotFoundError,
|
|
9644
10258
|
ProviderSchema,
|
|
9645
10259
|
RC_IDLE_EXPIRY_MS,
|
|
10260
|
+
RC_ORPC_PATH_PREFIX,
|
|
9646
10261
|
RC_PENDING_DEADLINE_MS,
|
|
9647
10262
|
RC_PERMISSION_MODES,
|
|
9648
10263
|
RC_REQUEST_PARSE_CAP_BYTES,
|
|
9649
10264
|
RC_SESSIONS_PATH_PREFIX,
|
|
10265
|
+
RC_STREAM_BACKOFF_MS,
|
|
9650
10266
|
ROUTED_PATH_PREFIX,
|
|
10267
|
+
RcPermissionModeSchema,
|
|
10268
|
+
RcStreamEnvelopeSchema,
|
|
10269
|
+
RcStreamEventSchema,
|
|
9651
10270
|
UsageError,
|
|
9652
10271
|
UsageSnapshotError,
|
|
9653
10272
|
UsageSnapshotSchema,
|
|
@@ -9673,8 +10292,13 @@ function prepareClaudeLaunch(options) {
|
|
|
9673
10292
|
collectPoolPick,
|
|
9674
10293
|
createLeafCache,
|
|
9675
10294
|
createProfile,
|
|
10295
|
+
createRcApiNodeHandler,
|
|
10296
|
+
createRcApiRouter,
|
|
9676
10297
|
createRcControlHandler,
|
|
10298
|
+
createRcEventFanout,
|
|
9677
10299
|
createRcSessionTracker,
|
|
10300
|
+
createRcStreamHub,
|
|
10301
|
+
createSseParser,
|
|
9678
10302
|
describeProviderEndpoint,
|
|
9679
10303
|
detectAmbientCredential,
|
|
9680
10304
|
effectiveWindow,
|
|
@@ -9687,6 +10311,7 @@ function prepareClaudeLaunch(options) {
|
|
|
9687
10311
|
formatCheckReport,
|
|
9688
10312
|
formatDoctorReport,
|
|
9689
10313
|
forwardableHeaders,
|
|
10314
|
+
frontDoorRcApiClient,
|
|
9690
10315
|
frontDoorRcControl,
|
|
9691
10316
|
generateCa,
|
|
9692
10317
|
identityExists,
|
|
@@ -9704,6 +10329,7 @@ function prepareClaudeLaunch(options) {
|
|
|
9704
10329
|
mintLeaf,
|
|
9705
10330
|
observingRoutedRoute,
|
|
9706
10331
|
parseConnectTarget,
|
|
10332
|
+
parseRcStreamEnvelope,
|
|
9707
10333
|
planOf,
|
|
9708
10334
|
prepareClaudeLaunch,
|
|
9709
10335
|
prepareLaunch,
|
|
@@ -9725,6 +10351,7 @@ function prepareClaudeLaunch(options) {
|
|
|
9725
10351
|
realConnectEffects,
|
|
9726
10352
|
realRcControlTransport,
|
|
9727
10353
|
realRcEventDial,
|
|
10354
|
+
realRcStreamDial,
|
|
9728
10355
|
recoverFarm,
|
|
9729
10356
|
recoveryDiagnostics,
|
|
9730
10357
|
removeDirectoryRule,
|