agent-shim 8.2.0 → 8.4.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 +5 -0
- package/dist/cli.cjs +1623 -643
- package/dist/index.cjs +951 -42
- package/dist/index.mjs +924 -33
- package/dist/types/index.d.ts +314 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2449,6 +2449,7 @@ function pumpTapSession(secure, openUpstream, tap, prefix = Buffer.alloc(0)) {
|
|
|
2449
2449
|
}
|
|
2450
2450
|
|
|
2451
2451
|
// src/frontdoor/capability.ts
|
|
2452
|
+
import { createHash as createHash2, timingSafeEqual } from "node:crypto";
|
|
2452
2453
|
var CONNECT_PROXY_USER = "agent-shim";
|
|
2453
2454
|
var CONNECT_PROXY_REALM = "agent-shim front door";
|
|
2454
2455
|
var BASIC_SCHEME = "basic";
|
|
@@ -2469,6 +2470,18 @@ function capabilityFromProxyAuthorization(value) {
|
|
|
2469
2470
|
const password = userPass.slice(colon + 1);
|
|
2470
2471
|
return password === "" ? void 0 : password;
|
|
2471
2472
|
}
|
|
2473
|
+
function digest(token) {
|
|
2474
|
+
return createHash2("sha256").update(token, "utf8").digest();
|
|
2475
|
+
}
|
|
2476
|
+
function isLiveCapability(presented, live) {
|
|
2477
|
+
const presentedDigest = digest(presented);
|
|
2478
|
+
let matched = false;
|
|
2479
|
+
for (const token of live) {
|
|
2480
|
+
const equal = timingSafeEqual(presentedDigest, digest(token));
|
|
2481
|
+
matched = equal || matched;
|
|
2482
|
+
}
|
|
2483
|
+
return matched;
|
|
2484
|
+
}
|
|
2472
2485
|
|
|
2473
2486
|
// src/frontdoor/route.ts
|
|
2474
2487
|
var IDENTITY_HEADER = "x-agent-shim-identity";
|
|
@@ -2779,14 +2792,14 @@ function caParses(ca) {
|
|
|
2779
2792
|
var TAP_ALPN_PROTOCOLS = ["h2", "http/1.1"];
|
|
2780
2793
|
async function startConnectServer(config, effects, preferredPort) {
|
|
2781
2794
|
const tunnelTokens = /* @__PURE__ */ new WeakMap();
|
|
2782
|
-
const onUpgrade = (host) => (
|
|
2783
|
-
config.capture?.upgrade(
|
|
2795
|
+
const onUpgrade = (host) => (request4, socket, head) => {
|
|
2796
|
+
config.capture?.upgrade(request4);
|
|
2784
2797
|
if (head.length > 0) {
|
|
2785
2798
|
socket.pause();
|
|
2786
2799
|
socket.unshift(head);
|
|
2787
2800
|
}
|
|
2788
|
-
const headText = `${
|
|
2789
|
-
${
|
|
2801
|
+
const headText = `${request4.method ?? "GET"} ${request4.url ?? "/"} HTTP/1.1\r
|
|
2802
|
+
${request4.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `${accumulated}${value}: ` : `${accumulated}${value}\r
|
|
2790
2803
|
`, "")}\r
|
|
2791
2804
|
`;
|
|
2792
2805
|
pumpTapSession(socket, async (clientAlpn) => await effects.connectTlsUpstream(host, HTTPS_PORT, clientAlpn), config.capture?.tapStream?.(host), Buffer.from(headText, "utf8"));
|
|
@@ -2799,16 +2812,16 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
2799
2812
|
const tlsAcceptor2 = effects.createTlsAcceptor(config.leafFor(host), serveSecure2, TAP_ALPN_PROTOCOLS);
|
|
2800
2813
|
return { host, serveSecure: serveSecure2, tlsAcceptor: tlsAcceptor2 };
|
|
2801
2814
|
}
|
|
2802
|
-
const handler = (
|
|
2803
|
-
if (host === config.routedHost && servedByPipeline(
|
|
2804
|
-
const tunnelToken = tunnelTokens.get(
|
|
2805
|
-
if (tunnelToken !== void 0 &&
|
|
2806
|
-
|
|
2815
|
+
const handler = (request4, response) => {
|
|
2816
|
+
if (host === config.routedHost && servedByPipeline(request4.url)) {
|
|
2817
|
+
const tunnelToken = tunnelTokens.get(request4.socket);
|
|
2818
|
+
if (tunnelToken !== void 0 && request4.headers[AUTH_HEADER] === void 0) {
|
|
2819
|
+
request4.headers[AUTH_HEADER] = tunnelToken;
|
|
2807
2820
|
}
|
|
2808
|
-
config.serveRouted(
|
|
2821
|
+
config.serveRouted(request4, response);
|
|
2809
2822
|
return;
|
|
2810
2823
|
}
|
|
2811
|
-
effects.forwardHttp(config.upstreamFor(host),
|
|
2824
|
+
effects.forwardHttp(config.upstreamFor(host), request4, response, config.capture?.observePassthrough(request4));
|
|
2812
2825
|
};
|
|
2813
2826
|
const httpSession = effects.createHttpSession(handler, onUpgrade(host));
|
|
2814
2827
|
const serveSecure = (secure, capability) => {
|
|
@@ -2850,7 +2863,7 @@ ${request3.rawHeaders.reduce((accumulated, value, index) => index % 2 === 0 ? `$
|
|
|
2850
2863
|
}
|
|
2851
2864
|
};
|
|
2852
2865
|
}
|
|
2853
|
-
function createConnectionLedger(limits,
|
|
2866
|
+
function createConnectionLedger(limits, isLiveCapability2) {
|
|
2854
2867
|
const pending = /* @__PURE__ */ new Map();
|
|
2855
2868
|
const authenticated = /* @__PURE__ */ new Map();
|
|
2856
2869
|
let tunnels = 0;
|
|
@@ -2863,7 +2876,7 @@ function createConnectionLedger(limits, isLiveCapability) {
|
|
|
2863
2876
|
};
|
|
2864
2877
|
const revalidation = setInterval(() => {
|
|
2865
2878
|
for (const [capability, sockets] of authenticated) {
|
|
2866
|
-
if (!
|
|
2879
|
+
if (!isLiveCapability2(capability)) {
|
|
2867
2880
|
for (const socket of sockets) {
|
|
2868
2881
|
socket.destroy();
|
|
2869
2882
|
}
|
|
@@ -3105,6 +3118,40 @@ var ExemptTlsAgent = class extends https.Agent {
|
|
|
3105
3118
|
return tls.connect(tlsOptions);
|
|
3106
3119
|
}
|
|
3107
3120
|
};
|
|
3121
|
+
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
3122
|
+
const agent = new ExemptTlsAgent();
|
|
3123
|
+
return {
|
|
3124
|
+
writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
|
|
3125
|
+
const options = {
|
|
3126
|
+
host: target.host,
|
|
3127
|
+
port: target.port,
|
|
3128
|
+
method: "POST",
|
|
3129
|
+
path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
|
|
3130
|
+
// 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.
|
|
3131
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
3132
|
+
agent
|
|
3133
|
+
};
|
|
3134
|
+
options.servername = target.servername ?? target.host;
|
|
3135
|
+
if (target.ca !== void 0) {
|
|
3136
|
+
options.ca = [...target.ca];
|
|
3137
|
+
}
|
|
3138
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
3139
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
3140
|
+
}
|
|
3141
|
+
const request4 = https.request(options, (response) => {
|
|
3142
|
+
const chunks = [];
|
|
3143
|
+
response.on("data", (chunk) => {
|
|
3144
|
+
chunks.push(chunk);
|
|
3145
|
+
});
|
|
3146
|
+
response.on("end", () => {
|
|
3147
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
3148
|
+
});
|
|
3149
|
+
});
|
|
3150
|
+
request4.once("error", reject);
|
|
3151
|
+
request4.end(body);
|
|
3152
|
+
})
|
|
3153
|
+
};
|
|
3154
|
+
}
|
|
3108
3155
|
function realConnectEffects() {
|
|
3109
3156
|
const plainAgent = new ExemptHttpAgent();
|
|
3110
3157
|
const tlsAgent = new ExemptTlsAgent();
|
|
@@ -3210,8 +3257,8 @@ function realConnectEffects() {
|
|
|
3210
3257
|
socket.destroy();
|
|
3211
3258
|
});
|
|
3212
3259
|
if (onUpgrade !== void 0) {
|
|
3213
|
-
server.on("upgrade", (
|
|
3214
|
-
onUpgrade(
|
|
3260
|
+
server.on("upgrade", (request4, socket, head) => {
|
|
3261
|
+
onUpgrade(request4, socket, head);
|
|
3215
3262
|
});
|
|
3216
3263
|
}
|
|
3217
3264
|
return {
|
|
@@ -3250,14 +3297,14 @@ function realConnectEffects() {
|
|
|
3250
3297
|
});
|
|
3251
3298
|
socket.once("error", reject);
|
|
3252
3299
|
}),
|
|
3253
|
-
forwardHttp: (target,
|
|
3254
|
-
const headers = forwardableHeaders(
|
|
3300
|
+
forwardHttp: (target, request4, response, observer) => {
|
|
3301
|
+
const headers = forwardableHeaders(request4.headers);
|
|
3255
3302
|
const options = {
|
|
3256
3303
|
host: target.host,
|
|
3257
3304
|
port: target.port,
|
|
3258
3305
|
lookup: realAddressLookup,
|
|
3259
|
-
method:
|
|
3260
|
-
path:
|
|
3306
|
+
method: request4.method,
|
|
3307
|
+
path: request4.url,
|
|
3261
3308
|
headers,
|
|
3262
3309
|
agent: target.tls ? tlsAgent : plainAgent
|
|
3263
3310
|
};
|
|
@@ -3292,15 +3339,20 @@ function realConnectEffects() {
|
|
|
3292
3339
|
response.end(body);
|
|
3293
3340
|
});
|
|
3294
3341
|
if (observer !== void 0) {
|
|
3295
|
-
|
|
3342
|
+
request4.on("data", (chunk) => {
|
|
3296
3343
|
observer.onRequestChunk(chunk);
|
|
3297
3344
|
});
|
|
3345
|
+
if (observer.onRequestEnd !== void 0) {
|
|
3346
|
+
request4.on("end", () => {
|
|
3347
|
+
observer.onRequestEnd?.();
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3298
3350
|
response.on("close", () => {
|
|
3299
3351
|
observer.onEnd();
|
|
3300
3352
|
});
|
|
3301
3353
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3354
|
+
request4.pipe(upstream);
|
|
3355
|
+
request4.socket.setNoDelay(true);
|
|
3304
3356
|
upstream.on("socket", (socket) => {
|
|
3305
3357
|
socket.setNoDelay(true);
|
|
3306
3358
|
});
|
|
@@ -3308,11 +3360,831 @@ function realConnectEffects() {
|
|
|
3308
3360
|
};
|
|
3309
3361
|
}
|
|
3310
3362
|
|
|
3363
|
+
// src/codex/http.ts
|
|
3364
|
+
var HTTP_STATUS = {
|
|
3365
|
+
ok: 200,
|
|
3366
|
+
badRequest: 400,
|
|
3367
|
+
unauthorized: 401,
|
|
3368
|
+
forbidden: 403,
|
|
3369
|
+
notFound: 404,
|
|
3370
|
+
methodNotAllowed: 405,
|
|
3371
|
+
payloadTooLarge: 413,
|
|
3372
|
+
tooManyRequests: 429,
|
|
3373
|
+
/** 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. */
|
|
3374
|
+
clientClosedRequest: 499,
|
|
3375
|
+
internalServerError: 500,
|
|
3376
|
+
badGateway: 502
|
|
3377
|
+
};
|
|
3378
|
+
|
|
3379
|
+
// src/frontdoor/rcSessions.ts
|
|
3380
|
+
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3381
|
+
var RC_SESSION_ID_PREFIX = "cse_";
|
|
3382
|
+
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3383
|
+
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3384
|
+
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3385
|
+
var RC_PENDING_DEADLINE_MS = 14e3;
|
|
3386
|
+
var RC_REQUEST_PARSE_CAP_BYTES = 262144;
|
|
3387
|
+
var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
|
|
3388
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3389
|
+
function isSuccessful(status) {
|
|
3390
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3391
|
+
}
|
|
3392
|
+
function singleHeader(headers, name) {
|
|
3393
|
+
const value = headers[name];
|
|
3394
|
+
return typeof value === "string" ? value : void 0;
|
|
3395
|
+
}
|
|
3396
|
+
function sessionCreateId(body) {
|
|
3397
|
+
let parsed;
|
|
3398
|
+
try {
|
|
3399
|
+
parsed = JSON.parse(body);
|
|
3400
|
+
} catch {
|
|
3401
|
+
return void 0;
|
|
3402
|
+
}
|
|
3403
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3404
|
+
return void 0;
|
|
3405
|
+
}
|
|
3406
|
+
const session = parsed.session;
|
|
3407
|
+
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3408
|
+
return void 0;
|
|
3409
|
+
}
|
|
3410
|
+
const id = session.id;
|
|
3411
|
+
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3412
|
+
}
|
|
3413
|
+
function isRecord2(value) {
|
|
3414
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3415
|
+
}
|
|
3416
|
+
function parseJsonObject(body) {
|
|
3417
|
+
let parsed;
|
|
3418
|
+
try {
|
|
3419
|
+
parsed = JSON.parse(body);
|
|
3420
|
+
} catch {
|
|
3421
|
+
return void 0;
|
|
3422
|
+
}
|
|
3423
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
3424
|
+
}
|
|
3425
|
+
function nonEmptyString(record, field) {
|
|
3426
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3427
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3428
|
+
}
|
|
3429
|
+
function finiteNumber(record, field) {
|
|
3430
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3431
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3432
|
+
}
|
|
3433
|
+
function excerpt(text) {
|
|
3434
|
+
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
3435
|
+
}
|
|
3436
|
+
function payloadEventsOf(body) {
|
|
3437
|
+
const parsed = parseJsonObject(body);
|
|
3438
|
+
const events = parsed === void 0 ? void 0 : parsed.events;
|
|
3439
|
+
if (!Array.isArray(events)) {
|
|
3440
|
+
return [];
|
|
3441
|
+
}
|
|
3442
|
+
const payloads = [];
|
|
3443
|
+
for (const event of events) {
|
|
3444
|
+
if (!isRecord2(event)) {
|
|
3445
|
+
continue;
|
|
3446
|
+
}
|
|
3447
|
+
const payload = event.payload;
|
|
3448
|
+
if (!isRecord2(payload)) {
|
|
3449
|
+
continue;
|
|
3450
|
+
}
|
|
3451
|
+
payloads.push(payload);
|
|
3452
|
+
}
|
|
3453
|
+
return payloads;
|
|
3454
|
+
}
|
|
3455
|
+
function pendingOfControlRequestPayload(payload) {
|
|
3456
|
+
if (payload.type !== "control_request") {
|
|
3457
|
+
return void 0;
|
|
3458
|
+
}
|
|
3459
|
+
const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3460
|
+
if (requestId === void 0) {
|
|
3461
|
+
return void 0;
|
|
3462
|
+
}
|
|
3463
|
+
const request4 = isRecord2(payload.request) ? payload.request : void 0;
|
|
3464
|
+
const subtype = nonEmptyString(request4, "subtype");
|
|
3465
|
+
const type = subtype ?? "control_request";
|
|
3466
|
+
let summary = "";
|
|
3467
|
+
if (subtype === "can_use_tool") {
|
|
3468
|
+
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3469
|
+
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3470
|
+
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3471
|
+
} else if (request4 !== void 0) {
|
|
3472
|
+
summary = excerpt(JSON.stringify(request4));
|
|
3473
|
+
}
|
|
3474
|
+
return { requestId, type, summary };
|
|
3475
|
+
}
|
|
3476
|
+
function answeredRequestIdsOfControlResponsePayload(payload) {
|
|
3477
|
+
if (payload.type !== "control_response") {
|
|
3478
|
+
return [];
|
|
3479
|
+
}
|
|
3480
|
+
const response = isRecord2(payload.response) ? payload.response : void 0;
|
|
3481
|
+
const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3482
|
+
return id === void 0 ? [] : [id];
|
|
3483
|
+
}
|
|
3484
|
+
function parseRcRequestBody(kind, body) {
|
|
3485
|
+
if (kind === "heartbeat") {
|
|
3486
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
|
|
3487
|
+
}
|
|
3488
|
+
if (kind === "worker-registration") {
|
|
3489
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
|
|
3490
|
+
}
|
|
3491
|
+
const controlRequests = [];
|
|
3492
|
+
const answeredRequestIds = [];
|
|
3493
|
+
for (const payload of payloadEventsOf(body)) {
|
|
3494
|
+
const pending = pendingOfControlRequestPayload(payload);
|
|
3495
|
+
if (pending !== void 0) {
|
|
3496
|
+
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3497
|
+
continue;
|
|
3498
|
+
}
|
|
3499
|
+
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3500
|
+
}
|
|
3501
|
+
return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
|
|
3502
|
+
}
|
|
3503
|
+
function createRcSessionTracker(deps) {
|
|
3504
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3505
|
+
const expire = (id) => {
|
|
3506
|
+
entries.delete(id);
|
|
3507
|
+
};
|
|
3508
|
+
const expirePendings = (entry, now) => {
|
|
3509
|
+
for (const [requestId, pending] of entry.pending) {
|
|
3510
|
+
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3511
|
+
entry.pending.delete(requestId);
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
};
|
|
3515
|
+
const sweep = () => {
|
|
3516
|
+
for (const [id, entry] of entries) {
|
|
3517
|
+
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3518
|
+
expire(id);
|
|
3519
|
+
continue;
|
|
3520
|
+
}
|
|
3521
|
+
expirePendings(entry, deps.now());
|
|
3522
|
+
}
|
|
3523
|
+
};
|
|
3524
|
+
const timer = setInterval(sweep, deps.idleMs);
|
|
3525
|
+
timer.unref();
|
|
3526
|
+
const birth = (id, facts) => {
|
|
3527
|
+
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
|
|
3528
|
+
entries.set(id, record);
|
|
3529
|
+
return record;
|
|
3530
|
+
};
|
|
3531
|
+
const observeExchange = (request4) => {
|
|
3532
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
3533
|
+
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3534
|
+
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3535
|
+
return void 0;
|
|
3536
|
+
}
|
|
3537
|
+
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3538
|
+
const facts = {
|
|
3539
|
+
authorization: singleHeader(request4.headers, "authorization"),
|
|
3540
|
+
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3541
|
+
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3542
|
+
};
|
|
3543
|
+
if (tail === "") {
|
|
3544
|
+
if (method !== "POST") {
|
|
3545
|
+
return void 0;
|
|
3546
|
+
}
|
|
3547
|
+
let accepted2 = false;
|
|
3548
|
+
let buffered2 = 0;
|
|
3549
|
+
let body = "";
|
|
3550
|
+
return {
|
|
3551
|
+
onResponse: (status) => {
|
|
3552
|
+
accepted2 = isSuccessful(status);
|
|
3553
|
+
},
|
|
3554
|
+
onBodyChunk: (chunk) => {
|
|
3555
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
3559
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3560
|
+
buffered2 = body.length;
|
|
3561
|
+
},
|
|
3562
|
+
onEnd: () => {
|
|
3563
|
+
if (!accepted2) {
|
|
3564
|
+
return;
|
|
3565
|
+
}
|
|
3566
|
+
const id2 = sessionCreateId(body);
|
|
3567
|
+
if (id2 === void 0) {
|
|
3568
|
+
return;
|
|
3569
|
+
}
|
|
3570
|
+
birth(id2, facts);
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
const rest = tail.slice(1);
|
|
3575
|
+
const [id] = rest.split("/");
|
|
3576
|
+
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3577
|
+
return void 0;
|
|
3578
|
+
}
|
|
3579
|
+
const entry = entries.get(id) ?? birth(id, facts);
|
|
3580
|
+
entry.lastSeenAt = deps.now();
|
|
3581
|
+
entry.authorization = facts.authorization ?? entry.authorization;
|
|
3582
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3583
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3584
|
+
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;
|
|
3585
|
+
let chunks = [];
|
|
3586
|
+
let buffered = 0;
|
|
3587
|
+
let oversized = false;
|
|
3588
|
+
let requestEnded = false;
|
|
3589
|
+
let accepted;
|
|
3590
|
+
let filed = false;
|
|
3591
|
+
const file = () => {
|
|
3592
|
+
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3595
|
+
filed = true;
|
|
3596
|
+
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3597
|
+
chunks = [];
|
|
3598
|
+
const entryNow = entries.get(id);
|
|
3599
|
+
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3600
|
+
return;
|
|
3601
|
+
}
|
|
3602
|
+
const now = deps.now();
|
|
3603
|
+
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3604
|
+
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3605
|
+
}
|
|
3606
|
+
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3607
|
+
entryNow.pending.delete(requestId);
|
|
3608
|
+
}
|
|
3609
|
+
if (factsOfBody.idleSeconds !== void 0) {
|
|
3610
|
+
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3611
|
+
}
|
|
3612
|
+
if (factsOfBody.workerStatus !== void 0) {
|
|
3613
|
+
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3614
|
+
}
|
|
3615
|
+
};
|
|
3616
|
+
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3617
|
+
return {
|
|
3618
|
+
...bodyKind === void 0 ? {} : {
|
|
3619
|
+
onRequestChunk: (chunk) => {
|
|
3620
|
+
if (oversized) {
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
chunks.push(Buffer.from(chunk));
|
|
3624
|
+
buffered += chunk.length;
|
|
3625
|
+
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3626
|
+
oversized = true;
|
|
3627
|
+
chunks = [];
|
|
3628
|
+
}
|
|
3629
|
+
},
|
|
3630
|
+
onRequestEnd: () => {
|
|
3631
|
+
requestEnded = true;
|
|
3632
|
+
file();
|
|
3633
|
+
}
|
|
3634
|
+
},
|
|
3635
|
+
onResponse: (status, headers) => {
|
|
3636
|
+
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3637
|
+
expire(id);
|
|
3638
|
+
return;
|
|
3639
|
+
}
|
|
3640
|
+
accepted = isSuccessful(status);
|
|
3641
|
+
file();
|
|
3642
|
+
},
|
|
3643
|
+
onBodyChunk: () => {
|
|
3644
|
+
},
|
|
3645
|
+
onEnd: () => {
|
|
3646
|
+
}
|
|
3647
|
+
};
|
|
3648
|
+
};
|
|
3649
|
+
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3650
|
+
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3651
|
+
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3652
|
+
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3653
|
+
return {
|
|
3654
|
+
observeExchange,
|
|
3655
|
+
list: () => {
|
|
3656
|
+
sweep();
|
|
3657
|
+
return sortedEntries().map(summaryOf);
|
|
3658
|
+
},
|
|
3659
|
+
statusOf: (sessionId) => {
|
|
3660
|
+
sweep();
|
|
3661
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3662
|
+
},
|
|
3663
|
+
pendingOf: (sessionId) => {
|
|
3664
|
+
sweep();
|
|
3665
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3666
|
+
},
|
|
3667
|
+
credentialOf: (sessionId) => {
|
|
3668
|
+
const entry = entries.get(sessionId);
|
|
3669
|
+
return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3670
|
+
},
|
|
3671
|
+
completePending: (sessionId, requestId) => {
|
|
3672
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3673
|
+
},
|
|
3674
|
+
close: () => {
|
|
3675
|
+
clearInterval(timer);
|
|
3676
|
+
}
|
|
3677
|
+
};
|
|
3678
|
+
}
|
|
3679
|
+
function observingRoutedRoute(route, tracker) {
|
|
3680
|
+
return {
|
|
3681
|
+
...route,
|
|
3682
|
+
serve: async (request4, response) => {
|
|
3683
|
+
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3684
|
+
if (observed?.onRequestChunk !== void 0) {
|
|
3685
|
+
request4.body.on("data", (chunk) => {
|
|
3686
|
+
observed.onRequestChunk?.(chunk);
|
|
3687
|
+
});
|
|
3688
|
+
request4.body.on("end", () => {
|
|
3689
|
+
observed.onRequestEnd?.();
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
3692
|
+
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
|
|
3693
|
+
}
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
function teeRoutedResponse(response, observer) {
|
|
3697
|
+
return {
|
|
3698
|
+
get headersSent() {
|
|
3699
|
+
return response.headersSent;
|
|
3700
|
+
},
|
|
3701
|
+
start: (status, headers) => {
|
|
3702
|
+
observer.onResponse(status, headers);
|
|
3703
|
+
response.start(status, headers);
|
|
3704
|
+
},
|
|
3705
|
+
flush: () => {
|
|
3706
|
+
response.flush();
|
|
3707
|
+
},
|
|
3708
|
+
write: async (chunk) => {
|
|
3709
|
+
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3710
|
+
await response.write(chunk);
|
|
3711
|
+
},
|
|
3712
|
+
end: () => {
|
|
3713
|
+
response.end();
|
|
3714
|
+
observer.onEnd();
|
|
3715
|
+
},
|
|
3716
|
+
destroy: () => {
|
|
3717
|
+
response.destroy();
|
|
3718
|
+
observer.onEnd();
|
|
3719
|
+
}
|
|
3720
|
+
};
|
|
3721
|
+
}
|
|
3722
|
+
function rcObserverAsPassthrough(observer) {
|
|
3723
|
+
return {
|
|
3724
|
+
onRequestChunk: (chunk) => {
|
|
3725
|
+
observer.onRequestChunk?.(chunk);
|
|
3726
|
+
},
|
|
3727
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3728
|
+
onRequestEnd: () => {
|
|
3729
|
+
observer.onRequestEnd?.();
|
|
3730
|
+
}
|
|
3731
|
+
},
|
|
3732
|
+
onResponse: (status, headers) => {
|
|
3733
|
+
observer.onResponse(status, headers);
|
|
3734
|
+
},
|
|
3735
|
+
onResponseChunk: (chunk) => {
|
|
3736
|
+
observer.onBodyChunk(chunk);
|
|
3737
|
+
},
|
|
3738
|
+
onEnd: () => {
|
|
3739
|
+
observer.onEnd();
|
|
3740
|
+
}
|
|
3741
|
+
};
|
|
3742
|
+
}
|
|
3743
|
+
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3744
|
+
return {
|
|
3745
|
+
type: "user",
|
|
3746
|
+
uuid,
|
|
3747
|
+
session_id: sessionId,
|
|
3748
|
+
parent_tool_use_id: null,
|
|
3749
|
+
message: { role: "user", content: text }
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
function buildRcEventWriteBody(payload) {
|
|
3753
|
+
return { events: [{ payload }] };
|
|
3754
|
+
}
|
|
3755
|
+
function sequenceNumsOf(body) {
|
|
3756
|
+
let parsed;
|
|
3757
|
+
try {
|
|
3758
|
+
parsed = JSON.parse(body);
|
|
3759
|
+
} catch {
|
|
3760
|
+
return void 0;
|
|
3761
|
+
}
|
|
3762
|
+
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3763
|
+
return void 0;
|
|
3764
|
+
}
|
|
3765
|
+
const sequenceNums = [];
|
|
3766
|
+
const results = parsed.results;
|
|
3767
|
+
for (const result of results) {
|
|
3768
|
+
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3769
|
+
return void 0;
|
|
3770
|
+
}
|
|
3771
|
+
const sequenceNum = result.sequence_num;
|
|
3772
|
+
if (typeof sequenceNum !== "number") {
|
|
3773
|
+
return void 0;
|
|
3774
|
+
}
|
|
3775
|
+
sequenceNums.push(sequenceNum);
|
|
3776
|
+
}
|
|
3777
|
+
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3778
|
+
}
|
|
3779
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3780
|
+
if (!isSuccessful(answer.status)) {
|
|
3781
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3782
|
+
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}` };
|
|
3783
|
+
}
|
|
3784
|
+
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3785
|
+
}
|
|
3786
|
+
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3787
|
+
if (sequenceNums === void 0) {
|
|
3788
|
+
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}` };
|
|
3789
|
+
}
|
|
3790
|
+
return { ok: true, sequenceNums };
|
|
3791
|
+
}
|
|
3792
|
+
function rcWriteHeaders(credential) {
|
|
3793
|
+
const headers = { "content-type": "application/json" };
|
|
3794
|
+
if (credential.authorization !== void 0) {
|
|
3795
|
+
headers.authorization = credential.authorization;
|
|
3796
|
+
}
|
|
3797
|
+
if (credential.anthropicVersion !== void 0) {
|
|
3798
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3799
|
+
}
|
|
3800
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
3801
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3802
|
+
}
|
|
3803
|
+
return headers;
|
|
3804
|
+
}
|
|
3805
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3806
|
+
const credential = deps.credentialOf(sessionId);
|
|
3807
|
+
if (credential === void 0) {
|
|
3808
|
+
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)` };
|
|
3809
|
+
}
|
|
3810
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3811
|
+
let answer;
|
|
3812
|
+
try {
|
|
3813
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3814
|
+
} catch (error) {
|
|
3815
|
+
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3816
|
+
}
|
|
3817
|
+
return rcEventWriteResultFromAnswer(answer);
|
|
3818
|
+
}
|
|
3819
|
+
function buildRcControlResponsePayload(requestId, decision) {
|
|
3820
|
+
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
3821
|
+
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
3822
|
+
}
|
|
3823
|
+
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
3824
|
+
const credential = deps.credentialOf(sessionId);
|
|
3825
|
+
if (credential === void 0) {
|
|
3826
|
+
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)` };
|
|
3827
|
+
}
|
|
3828
|
+
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
3829
|
+
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` };
|
|
3830
|
+
}
|
|
3831
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
3832
|
+
let answer;
|
|
3833
|
+
try {
|
|
3834
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3835
|
+
} catch (error) {
|
|
3836
|
+
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3837
|
+
}
|
|
3838
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3839
|
+
if (result.ok) {
|
|
3840
|
+
deps.completePending(sessionId, requestId);
|
|
3841
|
+
}
|
|
3842
|
+
return result;
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
// src/frontdoor/rcControl.ts
|
|
3846
|
+
import * as https2 from "node:https";
|
|
3847
|
+
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3848
|
+
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
3849
|
+
function answerJson(response, status, body) {
|
|
3850
|
+
const text = JSON.stringify(body);
|
|
3851
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3852
|
+
response.end(text);
|
|
3853
|
+
}
|
|
3854
|
+
async function readBody(request4) {
|
|
3855
|
+
return await new Promise((resolve, reject) => {
|
|
3856
|
+
const chunks = [];
|
|
3857
|
+
let total = 0;
|
|
3858
|
+
request4.on("data", (chunk) => {
|
|
3859
|
+
total += chunk.length;
|
|
3860
|
+
if (total > CONTROL_BODY_CAP_BYTES) {
|
|
3861
|
+
request4.destroy();
|
|
3862
|
+
reject(new Error("too large"));
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
chunks.push(chunk);
|
|
3866
|
+
});
|
|
3867
|
+
request4.on("end", () => {
|
|
3868
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
3869
|
+
});
|
|
3870
|
+
request4.on("error", () => {
|
|
3871
|
+
reject(new Error("the request body ended early"));
|
|
3872
|
+
});
|
|
3873
|
+
});
|
|
3874
|
+
}
|
|
3875
|
+
function createRcControlHandler(deps) {
|
|
3876
|
+
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
3877
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
3878
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3879
|
+
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3880
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3881
|
+
return (request4, response) => {
|
|
3882
|
+
const presented = request4.headers.authorization;
|
|
3883
|
+
const bearerPrefix = "bearer ".length;
|
|
3884
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
3885
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
3886
|
+
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3887
|
+
return;
|
|
3888
|
+
}
|
|
3889
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
3890
|
+
const handle = async () => {
|
|
3891
|
+
if (url.pathname === sessionsPath) {
|
|
3892
|
+
if (request4.method !== "GET") {
|
|
3893
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
3896
|
+
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
if (url.pathname === statusPath || url.pathname === pendingPath) {
|
|
3900
|
+
if (request4.method !== "GET") {
|
|
3901
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
const session = url.searchParams.get("session") ?? void 0;
|
|
3905
|
+
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
3906
|
+
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)` });
|
|
3907
|
+
return;
|
|
3908
|
+
}
|
|
3909
|
+
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
if (url.pathname === injectPath) {
|
|
3913
|
+
if (request4.method !== "POST") {
|
|
3914
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3915
|
+
return;
|
|
3916
|
+
}
|
|
3917
|
+
let parsed;
|
|
3918
|
+
try {
|
|
3919
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3920
|
+
} catch {
|
|
3921
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
3922
|
+
return;
|
|
3923
|
+
}
|
|
3924
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
3925
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
const result = await deps.inject(parsed.session, parsed.text);
|
|
3929
|
+
if (result.ok) {
|
|
3930
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
if (url.pathname === answerPath) {
|
|
3937
|
+
if (request4.method !== "POST") {
|
|
3938
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
|
|
3939
|
+
return;
|
|
3940
|
+
}
|
|
3941
|
+
let parsed;
|
|
3942
|
+
try {
|
|
3943
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3944
|
+
} catch {
|
|
3945
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3948
|
+
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 === "") {
|
|
3949
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
|
|
3953
|
+
if (parsed.approve && text !== void 0 && text !== "") {
|
|
3954
|
+
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" });
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
|
|
3958
|
+
if (result.ok) {
|
|
3959
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3963
|
+
return;
|
|
3964
|
+
}
|
|
3965
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
|
|
3966
|
+
};
|
|
3967
|
+
handle().catch((error) => {
|
|
3968
|
+
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
3969
|
+
});
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
function realRcControlTransport(port, ca) {
|
|
3973
|
+
return {
|
|
3974
|
+
request: async (options) => await new Promise((resolve, reject) => {
|
|
3975
|
+
const request4 = https2.request(
|
|
3976
|
+
{
|
|
3977
|
+
host: "127.0.0.1",
|
|
3978
|
+
port,
|
|
3979
|
+
method: options.method,
|
|
3980
|
+
path: options.path,
|
|
3981
|
+
headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
|
|
3982
|
+
agent: false,
|
|
3983
|
+
ca: [ca]
|
|
3984
|
+
},
|
|
3985
|
+
(response) => {
|
|
3986
|
+
const chunks = [];
|
|
3987
|
+
response.on("data", (chunk) => {
|
|
3988
|
+
chunks.push(chunk);
|
|
3989
|
+
});
|
|
3990
|
+
response.on("end", () => {
|
|
3991
|
+
resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
|
|
3992
|
+
});
|
|
3993
|
+
}
|
|
3994
|
+
);
|
|
3995
|
+
request4.once("error", reject);
|
|
3996
|
+
if (options.body === void 0) {
|
|
3997
|
+
request4.end();
|
|
3998
|
+
} else {
|
|
3999
|
+
request4.end(options.body);
|
|
4000
|
+
}
|
|
4001
|
+
})
|
|
4002
|
+
};
|
|
4003
|
+
}
|
|
4004
|
+
function controlErrorOf(body) {
|
|
4005
|
+
try {
|
|
4006
|
+
const parsed = JSON.parse(body);
|
|
4007
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
|
|
4008
|
+
return parsed.error;
|
|
4009
|
+
}
|
|
4010
|
+
} catch {
|
|
4011
|
+
return body;
|
|
4012
|
+
}
|
|
4013
|
+
return void 0;
|
|
4014
|
+
}
|
|
4015
|
+
function isRcSessionSummary(value) {
|
|
4016
|
+
if (typeof value !== "object" || value === null) {
|
|
4017
|
+
return false;
|
|
4018
|
+
}
|
|
4019
|
+
if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
|
|
4020
|
+
return false;
|
|
4021
|
+
}
|
|
4022
|
+
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
4023
|
+
}
|
|
4024
|
+
function isRcWorkerFact(value, valueIs) {
|
|
4025
|
+
if (typeof value !== "object" || value === null) {
|
|
4026
|
+
return false;
|
|
4027
|
+
}
|
|
4028
|
+
if (!("value" in value) || !("observedAt" in value)) {
|
|
4029
|
+
return false;
|
|
4030
|
+
}
|
|
4031
|
+
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
4032
|
+
}
|
|
4033
|
+
function isRcSessionStatus(value) {
|
|
4034
|
+
if (!isRcSessionSummary(value)) {
|
|
4035
|
+
return false;
|
|
4036
|
+
}
|
|
4037
|
+
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4038
|
+
return false;
|
|
4039
|
+
}
|
|
4040
|
+
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4041
|
+
return false;
|
|
4042
|
+
}
|
|
4043
|
+
if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
|
|
4044
|
+
return false;
|
|
4045
|
+
}
|
|
4046
|
+
return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
|
|
4047
|
+
}
|
|
4048
|
+
function isRcPendingRequestSummary(value) {
|
|
4049
|
+
if (typeof value !== "object" || value === null) {
|
|
4050
|
+
return false;
|
|
4051
|
+
}
|
|
4052
|
+
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4053
|
+
return false;
|
|
4054
|
+
}
|
|
4055
|
+
return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
|
|
4056
|
+
}
|
|
4057
|
+
function sequenceNumsOfBody(body) {
|
|
4058
|
+
let parsed;
|
|
4059
|
+
try {
|
|
4060
|
+
parsed = JSON.parse(body);
|
|
4061
|
+
} catch {
|
|
4062
|
+
return void 0;
|
|
4063
|
+
}
|
|
4064
|
+
if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
|
|
4065
|
+
return void 0;
|
|
4066
|
+
}
|
|
4067
|
+
const sequenceNums = [];
|
|
4068
|
+
const values = parsed.sequenceNums;
|
|
4069
|
+
for (const value of values) {
|
|
4070
|
+
if (typeof value !== "number") {
|
|
4071
|
+
return void 0;
|
|
4072
|
+
}
|
|
4073
|
+
sequenceNums.push(value);
|
|
4074
|
+
}
|
|
4075
|
+
return sequenceNums;
|
|
4076
|
+
}
|
|
4077
|
+
function frontDoorRcControl(transport, token) {
|
|
4078
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
4079
|
+
const listAnswerOf = async (path29, what) => {
|
|
4080
|
+
try {
|
|
4081
|
+
return await transport.request({ method: "GET", path: path29, headers });
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4084
|
+
}
|
|
4085
|
+
};
|
|
4086
|
+
const sessionsOf = (answer, what) => {
|
|
4087
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4088
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4089
|
+
}
|
|
4090
|
+
let parsed;
|
|
4091
|
+
try {
|
|
4092
|
+
parsed = JSON.parse(answer.body);
|
|
4093
|
+
} catch {
|
|
4094
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4095
|
+
}
|
|
4096
|
+
if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
|
|
4097
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4098
|
+
}
|
|
4099
|
+
const candidates = parsed.sessions;
|
|
4100
|
+
const sessions = [];
|
|
4101
|
+
for (const value of candidates) {
|
|
4102
|
+
if (!isRcSessionSummary(value)) {
|
|
4103
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4104
|
+
}
|
|
4105
|
+
sessions.push(value);
|
|
4106
|
+
}
|
|
4107
|
+
return sessions;
|
|
4108
|
+
};
|
|
4109
|
+
const statusesOf = (answer, what) => {
|
|
4110
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4111
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4112
|
+
}
|
|
4113
|
+
let parsed;
|
|
4114
|
+
try {
|
|
4115
|
+
parsed = JSON.parse(answer.body);
|
|
4116
|
+
} catch {
|
|
4117
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4118
|
+
}
|
|
4119
|
+
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4120
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4121
|
+
}
|
|
4122
|
+
const statuses = [];
|
|
4123
|
+
for (const value of parsed.statuses) {
|
|
4124
|
+
if (!isRcSessionStatus(value)) {
|
|
4125
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4126
|
+
}
|
|
4127
|
+
statuses.push(value);
|
|
4128
|
+
}
|
|
4129
|
+
return statuses;
|
|
4130
|
+
};
|
|
4131
|
+
const pendingOfAnswer = (answer, what) => {
|
|
4132
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4133
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4134
|
+
}
|
|
4135
|
+
let parsed;
|
|
4136
|
+
try {
|
|
4137
|
+
parsed = JSON.parse(answer.body);
|
|
4138
|
+
} catch {
|
|
4139
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4140
|
+
}
|
|
4141
|
+
if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
|
|
4142
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4143
|
+
}
|
|
4144
|
+
const pending = [];
|
|
4145
|
+
for (const value of parsed.pending) {
|
|
4146
|
+
if (!isRcPendingRequestSummary(value)) {
|
|
4147
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4148
|
+
}
|
|
4149
|
+
pending.push(value);
|
|
4150
|
+
}
|
|
4151
|
+
return pending;
|
|
4152
|
+
};
|
|
4153
|
+
const writeOf = async (path29, body, describeFailure) => {
|
|
4154
|
+
let answer;
|
|
4155
|
+
try {
|
|
4156
|
+
answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
|
|
4157
|
+
} catch (error) {
|
|
4158
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4159
|
+
}
|
|
4160
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4161
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4162
|
+
if (sequenceNums === void 0) {
|
|
4163
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4164
|
+
}
|
|
4165
|
+
return { ok: true, sequenceNums };
|
|
4166
|
+
}
|
|
4167
|
+
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4168
|
+
};
|
|
4169
|
+
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
4170
|
+
return {
|
|
4171
|
+
listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
|
|
4172
|
+
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"),
|
|
4173
|
+
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"),
|
|
4174
|
+
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}`),
|
|
4175
|
+
answerRequest: async (sessionId, requestId, decision) => await writeOf(
|
|
4176
|
+
`${CONTROL_PATH_PREFIX}/answer`,
|
|
4177
|
+
JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
|
|
4178
|
+
(status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4179
|
+
)
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
|
|
3311
4183
|
// src/frontdoor/ensure.ts
|
|
3312
4184
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3313
4185
|
|
|
3314
4186
|
// src/headroom/state.ts
|
|
3315
|
-
import { createHash as
|
|
4187
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3316
4188
|
import path8 from "node:path";
|
|
3317
4189
|
import { z as z4 } from "zod";
|
|
3318
4190
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
@@ -3373,7 +4245,7 @@ function headroomUpstreams(providers, frontDoorOrigin2) {
|
|
|
3373
4245
|
return upstreams;
|
|
3374
4246
|
}
|
|
3375
4247
|
function hashAllowlist(allowlist) {
|
|
3376
|
-
return
|
|
4248
|
+
return createHash3("sha256").update(allowlist.join("\n")).digest("hex");
|
|
3377
4249
|
}
|
|
3378
4250
|
function readAllProviders(fs12, providersDir) {
|
|
3379
4251
|
const result = [];
|
|
@@ -3871,7 +4743,7 @@ function ensureHeadroom(params) {
|
|
|
3871
4743
|
}
|
|
3872
4744
|
|
|
3873
4745
|
// src/headroom/settings.ts
|
|
3874
|
-
import { createHash as
|
|
4746
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3875
4747
|
function settingsOf(config) {
|
|
3876
4748
|
return {
|
|
3877
4749
|
...config.mode === void 0 ? {} : { mode: config.mode },
|
|
@@ -3896,7 +4768,7 @@ function settingsEnv(settings) {
|
|
|
3896
4768
|
return settings.rolloutChannel === void 0 ? {} : { HEADROOM_ROLLOUT_CHANNEL: settings.rolloutChannel };
|
|
3897
4769
|
}
|
|
3898
4770
|
function hashSettings(settings) {
|
|
3899
|
-
return
|
|
4771
|
+
return createHash4("sha256").update(JSON.stringify([...settingsArgs(settings), JSON.stringify(settingsEnv(settings))])).digest("hex");
|
|
3900
4772
|
}
|
|
3901
4773
|
|
|
3902
4774
|
// src/headroom/socket.ts
|
|
@@ -4327,6 +5199,7 @@ function buildLayoutPaths(root) {
|
|
|
4327
5199
|
frontdoorCaBundlesDir: path12.join(root, "frontdoor", "ca", "bundles"),
|
|
4328
5200
|
frontdoorStateFile: path12.join(root, "frontdoor", "state.json"),
|
|
4329
5201
|
frontdoorLockFile: path12.join(root, "frontdoor", "start.lock"),
|
|
5202
|
+
frontdoorControlTokenFile: path12.join(root, "frontdoor", "control-token"),
|
|
4330
5203
|
frontdoorSessionsDir: path12.join(root, "frontdoor", "sessions"),
|
|
4331
5204
|
frontdoorLogPath: path12.join(root, "logs", "frontdoor.log"),
|
|
4332
5205
|
usageDir: path12.join(root, "usage"),
|
|
@@ -4965,14 +5838,14 @@ function providerExists(paths, name) {
|
|
|
4965
5838
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
4966
5839
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
4967
5840
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
4968
|
-
function
|
|
5841
|
+
function isRecord3(value) {
|
|
4969
5842
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4970
5843
|
}
|
|
4971
5844
|
function legacyProviderConversion(raw) {
|
|
4972
|
-
if (!
|
|
5845
|
+
if (!isRecord3(raw) || "credential" in raw) {
|
|
4973
5846
|
return void 0;
|
|
4974
5847
|
}
|
|
4975
|
-
const env =
|
|
5848
|
+
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
4976
5849
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
4977
5850
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
4978
5851
|
if (fields.length === 0) {
|
|
@@ -5821,7 +6694,7 @@ function collectPoolPick(params) {
|
|
|
5821
6694
|
|
|
5822
6695
|
// src/realPorts.ts
|
|
5823
6696
|
import { spawn, spawnSync } from "node:child_process";
|
|
5824
|
-
import { createHash as
|
|
6697
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
5825
6698
|
import fs7 from "node:fs";
|
|
5826
6699
|
import os3 from "node:os";
|
|
5827
6700
|
import path21 from "node:path";
|
|
@@ -6016,7 +6889,7 @@ var realFarmFs = {
|
|
|
6016
6889
|
},
|
|
6017
6890
|
hashFile(filePath) {
|
|
6018
6891
|
try {
|
|
6019
|
-
return
|
|
6892
|
+
return createHash5("sha256").update(fs7.readFileSync(filePath)).digest("hex");
|
|
6020
6893
|
} catch (error) {
|
|
6021
6894
|
if (isEnoent3(error) || isErrorWithCode(error, "EISDIR")) {
|
|
6022
6895
|
return void 0;
|
|
@@ -8012,7 +8885,7 @@ function probeFrontDoorSync(port, caPem) {
|
|
|
8012
8885
|
}
|
|
8013
8886
|
|
|
8014
8887
|
// src/frontdoor/trust.ts
|
|
8015
|
-
import { createHash as
|
|
8888
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
8016
8889
|
import path28 from "node:path";
|
|
8017
8890
|
var BUNDLE_NAME_HEX_DIGITS = 16;
|
|
8018
8891
|
function resolveTrustBundle(params) {
|
|
@@ -8035,7 +8908,7 @@ function resolveTrustBundle(params) {
|
|
|
8035
8908
|
}
|
|
8036
8909
|
const combined = `${inheritedPem.trimEnd()}
|
|
8037
8910
|
${caPem}`;
|
|
8038
|
-
const bundle = path28.join(bundlesDir, `${
|
|
8911
|
+
const bundle = path28.join(bundlesDir, `${createHash6("sha256").update(combined).digest("hex").slice(0, BUNDLE_NAME_HEX_DIGITS)}.pem`);
|
|
8039
8912
|
if (!fs12.exists(bundle)) {
|
|
8040
8913
|
fs12.mkdirp(bundlesDir);
|
|
8041
8914
|
fs12.writeFileAtomic(bundle, combined);
|
|
@@ -8178,6 +9051,7 @@ export {
|
|
|
8178
9051
|
CONNECT_INTERCEPT_HOST,
|
|
8179
9052
|
CONNECT_INTERCEPT_HOSTS,
|
|
8180
9053
|
CONNECT_LIMITS,
|
|
9054
|
+
CONTROL_PATH_PREFIX,
|
|
8181
9055
|
CategoryClassificationOverlaySchema,
|
|
8182
9056
|
CategoryClassificationSchema,
|
|
8183
9057
|
CategoryMapSchema,
|
|
@@ -8216,6 +9090,10 @@ export {
|
|
|
8216
9090
|
ProviderKindMismatchError,
|
|
8217
9091
|
ProviderNotFoundError,
|
|
8218
9092
|
ProviderSchema,
|
|
9093
|
+
RC_IDLE_EXPIRY_MS,
|
|
9094
|
+
RC_PENDING_DEADLINE_MS,
|
|
9095
|
+
RC_REQUEST_PARSE_CAP_BYTES,
|
|
9096
|
+
RC_SESSIONS_PATH_PREFIX,
|
|
8219
9097
|
ROUTED_PATH_PREFIX,
|
|
8220
9098
|
UsageError,
|
|
8221
9099
|
UsageSnapshotError,
|
|
@@ -8225,8 +9103,12 @@ export {
|
|
|
8225
9103
|
addIdentity,
|
|
8226
9104
|
addPool,
|
|
8227
9105
|
addProvider,
|
|
9106
|
+
answerRcControlRequest,
|
|
8228
9107
|
buildEntryFacts,
|
|
8229
9108
|
buildLayoutPaths,
|
|
9109
|
+
buildRcControlResponsePayload,
|
|
9110
|
+
buildRcEventWriteBody,
|
|
9111
|
+
buildRcUserMessagePayload,
|
|
8230
9112
|
carryOver,
|
|
8231
9113
|
checkReportHasWarnings,
|
|
8232
9114
|
checkReportToJson,
|
|
@@ -8234,6 +9116,8 @@ export {
|
|
|
8234
9116
|
collectDoctorReport,
|
|
8235
9117
|
createLeafCache,
|
|
8236
9118
|
createProfile,
|
|
9119
|
+
createRcControlHandler,
|
|
9120
|
+
createRcSessionTracker,
|
|
8237
9121
|
describeProviderEndpoint,
|
|
8238
9122
|
detectAmbientCredential,
|
|
8239
9123
|
ensureCa,
|
|
@@ -8244,8 +9128,10 @@ export {
|
|
|
8244
9128
|
formatCheckReport,
|
|
8245
9129
|
formatDoctorReport,
|
|
8246
9130
|
forwardableHeaders,
|
|
9131
|
+
frontDoorRcControl,
|
|
8247
9132
|
generateCa,
|
|
8248
9133
|
identityExists,
|
|
9134
|
+
injectRcUserMessage,
|
|
8249
9135
|
isIdentityDirectoryName,
|
|
8250
9136
|
isInterceptedHost,
|
|
8251
9137
|
listDirectoryRules,
|
|
@@ -8254,11 +9140,14 @@ export {
|
|
|
8254
9140
|
listProviders,
|
|
8255
9141
|
listUsageSnapshots,
|
|
8256
9142
|
mintLeaf,
|
|
9143
|
+
observingRoutedRoute,
|
|
8257
9144
|
parseConnectTarget,
|
|
8258
9145
|
prepareClaudeLaunch,
|
|
8259
9146
|
prepareLaunch,
|
|
8260
9147
|
profileExists,
|
|
8261
9148
|
providerExists,
|
|
9149
|
+
rcEventWriteResultFromAnswer,
|
|
9150
|
+
rcObserverAsPassthrough,
|
|
8262
9151
|
readActiveIdentity,
|
|
8263
9152
|
readDirectoryRules,
|
|
8264
9153
|
readFarmManifest,
|
|
@@ -8270,6 +9159,8 @@ export {
|
|
|
8270
9159
|
readUsageSnapshot,
|
|
8271
9160
|
realConnectCertStore,
|
|
8272
9161
|
realConnectEffects,
|
|
9162
|
+
realRcControlTransport,
|
|
9163
|
+
realRcEventDial,
|
|
8273
9164
|
recoverFarm,
|
|
8274
9165
|
recoveryDiagnostics,
|
|
8275
9166
|
removeDirectoryRule,
|