agent-shim 8.3.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 +3 -0
- package/dist/cli.cjs +493 -68
- package/dist/index.cjs +414 -58
- package/dist/index.mjs +409 -57
- package/dist/types/index.d.ts +109 -26
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3342,6 +3342,11 @@ function realConnectEffects() {
|
|
|
3342
3342
|
request4.on("data", (chunk) => {
|
|
3343
3343
|
observer.onRequestChunk(chunk);
|
|
3344
3344
|
});
|
|
3345
|
+
if (observer.onRequestEnd !== void 0) {
|
|
3346
|
+
request4.on("end", () => {
|
|
3347
|
+
observer.onRequestEnd?.();
|
|
3348
|
+
});
|
|
3349
|
+
}
|
|
3345
3350
|
response.on("close", () => {
|
|
3346
3351
|
observer.onEnd();
|
|
3347
3352
|
});
|
|
@@ -3377,6 +3382,9 @@ var RC_SESSION_ID_PREFIX = "cse_";
|
|
|
3377
3382
|
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3378
3383
|
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3379
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;
|
|
3380
3388
|
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3381
3389
|
function isSuccessful(status) {
|
|
3382
3390
|
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
@@ -3402,20 +3410,124 @@ function sessionCreateId(body) {
|
|
|
3402
3410
|
const id = session.id;
|
|
3403
3411
|
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3404
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
|
+
}
|
|
3405
3503
|
function createRcSessionTracker(deps) {
|
|
3406
3504
|
const entries = /* @__PURE__ */ new Map();
|
|
3407
3505
|
const expire = (id) => {
|
|
3408
3506
|
entries.delete(id);
|
|
3409
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
|
+
};
|
|
3410
3515
|
const sweep = () => {
|
|
3411
3516
|
for (const [id, entry] of entries) {
|
|
3412
3517
|
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3413
3518
|
expire(id);
|
|
3519
|
+
continue;
|
|
3414
3520
|
}
|
|
3521
|
+
expirePendings(entry, deps.now());
|
|
3415
3522
|
}
|
|
3416
3523
|
};
|
|
3417
3524
|
const timer = setInterval(sweep, deps.idleMs);
|
|
3418
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
|
+
};
|
|
3419
3531
|
const observeExchange = (request4) => {
|
|
3420
3532
|
const method = (request4.method ?? "").toUpperCase();
|
|
3421
3533
|
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
@@ -3432,30 +3544,30 @@ function createRcSessionTracker(deps) {
|
|
|
3432
3544
|
if (method !== "POST") {
|
|
3433
3545
|
return void 0;
|
|
3434
3546
|
}
|
|
3435
|
-
let
|
|
3436
|
-
let
|
|
3547
|
+
let accepted2 = false;
|
|
3548
|
+
let buffered2 = 0;
|
|
3437
3549
|
let body = "";
|
|
3438
3550
|
return {
|
|
3439
3551
|
onResponse: (status) => {
|
|
3440
|
-
|
|
3552
|
+
accepted2 = isSuccessful(status);
|
|
3441
3553
|
},
|
|
3442
3554
|
onBodyChunk: (chunk) => {
|
|
3443
|
-
if (!
|
|
3555
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3444
3556
|
return;
|
|
3445
3557
|
}
|
|
3446
3558
|
const text = Buffer.from(chunk).toString("utf8");
|
|
3447
|
-
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES -
|
|
3448
|
-
|
|
3559
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3560
|
+
buffered2 = body.length;
|
|
3449
3561
|
},
|
|
3450
3562
|
onEnd: () => {
|
|
3451
|
-
if (!
|
|
3563
|
+
if (!accepted2) {
|
|
3452
3564
|
return;
|
|
3453
3565
|
}
|
|
3454
3566
|
const id2 = sessionCreateId(body);
|
|
3455
3567
|
if (id2 === void 0) {
|
|
3456
3568
|
return;
|
|
3457
3569
|
}
|
|
3458
|
-
|
|
3570
|
+
birth(id2, facts);
|
|
3459
3571
|
}
|
|
3460
3572
|
};
|
|
3461
3573
|
}
|
|
@@ -3464,21 +3576,69 @@ function createRcSessionTracker(deps) {
|
|
|
3464
3576
|
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3465
3577
|
return void 0;
|
|
3466
3578
|
}
|
|
3467
|
-
const entry = entries.get(id);
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
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
|
+
};
|
|
3476
3616
|
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3477
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
|
+
},
|
|
3478
3635
|
onResponse: (status, headers) => {
|
|
3479
3636
|
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3480
3637
|
expire(id);
|
|
3638
|
+
return;
|
|
3481
3639
|
}
|
|
3640
|
+
accepted = isSuccessful(status);
|
|
3641
|
+
file();
|
|
3482
3642
|
},
|
|
3483
3643
|
onBodyChunk: () => {
|
|
3484
3644
|
},
|
|
@@ -3486,16 +3646,31 @@ function createRcSessionTracker(deps) {
|
|
|
3486
3646
|
}
|
|
3487
3647
|
};
|
|
3488
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);
|
|
3489
3653
|
return {
|
|
3490
3654
|
observeExchange,
|
|
3491
3655
|
list: () => {
|
|
3492
3656
|
sweep();
|
|
3493
|
-
return
|
|
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));
|
|
3494
3666
|
},
|
|
3495
3667
|
credentialOf: (sessionId) => {
|
|
3496
3668
|
const entry = entries.get(sessionId);
|
|
3497
3669
|
return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3498
3670
|
},
|
|
3671
|
+
completePending: (sessionId, requestId) => {
|
|
3672
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3673
|
+
},
|
|
3499
3674
|
close: () => {
|
|
3500
3675
|
clearInterval(timer);
|
|
3501
3676
|
}
|
|
@@ -3506,6 +3681,14 @@ function observingRoutedRoute(route, tracker) {
|
|
|
3506
3681
|
...route,
|
|
3507
3682
|
serve: async (request4, response) => {
|
|
3508
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
|
+
}
|
|
3509
3692
|
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
|
|
3510
3693
|
}
|
|
3511
3694
|
};
|
|
@@ -3538,9 +3721,13 @@ function teeRoutedResponse(response, observer) {
|
|
|
3538
3721
|
}
|
|
3539
3722
|
function rcObserverAsPassthrough(observer) {
|
|
3540
3723
|
return {
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3724
|
+
onRequestChunk: (chunk) => {
|
|
3725
|
+
observer.onRequestChunk?.(chunk);
|
|
3726
|
+
},
|
|
3727
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3728
|
+
onRequestEnd: () => {
|
|
3729
|
+
observer.onRequestEnd?.();
|
|
3730
|
+
}
|
|
3544
3731
|
},
|
|
3545
3732
|
onResponse: (status, headers) => {
|
|
3546
3733
|
observer.onResponse(status, headers);
|
|
@@ -3589,7 +3776,7 @@ function sequenceNumsOf(body) {
|
|
|
3589
3776
|
}
|
|
3590
3777
|
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3591
3778
|
}
|
|
3592
|
-
function
|
|
3779
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3593
3780
|
if (!isSuccessful(answer.status)) {
|
|
3594
3781
|
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3595
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}` };
|
|
@@ -3602,11 +3789,7 @@ function rcInjectResultFromAnswer(answer) {
|
|
|
3602
3789
|
}
|
|
3603
3790
|
return { ok: true, sequenceNums };
|
|
3604
3791
|
}
|
|
3605
|
-
|
|
3606
|
-
const credential = deps.credentialOf(sessionId);
|
|
3607
|
-
if (credential === void 0) {
|
|
3608
|
-
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)` };
|
|
3609
|
-
}
|
|
3792
|
+
function rcWriteHeaders(credential) {
|
|
3610
3793
|
const headers = { "content-type": "application/json" };
|
|
3611
3794
|
if (credential.authorization !== void 0) {
|
|
3612
3795
|
headers.authorization = credential.authorization;
|
|
@@ -3617,14 +3800,46 @@ async function injectRcUserMessage(deps, sessionId, text) {
|
|
|
3617
3800
|
if (credential.anthropicClientPlatform !== void 0) {
|
|
3618
3801
|
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3619
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
|
+
}
|
|
3620
3810
|
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3621
3811
|
let answer;
|
|
3622
3812
|
try {
|
|
3623
|
-
answer = await deps.dial.writeEvents(sessionId,
|
|
3813
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3624
3814
|
} catch (error) {
|
|
3625
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)}` };
|
|
3626
3816
|
}
|
|
3627
|
-
return
|
|
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;
|
|
3628
3843
|
}
|
|
3629
3844
|
|
|
3630
3845
|
// src/frontdoor/rcControl.ts
|
|
@@ -3659,7 +3874,10 @@ async function readBody(request4) {
|
|
|
3659
3874
|
}
|
|
3660
3875
|
function createRcControlHandler(deps) {
|
|
3661
3876
|
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
3877
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
3878
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3662
3879
|
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3880
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3663
3881
|
return (request4, response) => {
|
|
3664
3882
|
const presented = request4.headers.authorization;
|
|
3665
3883
|
const bearerPrefix = "bearer ".length;
|
|
@@ -3668,9 +3886,9 @@ function createRcControlHandler(deps) {
|
|
|
3668
3886
|
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3669
3887
|
return;
|
|
3670
3888
|
}
|
|
3671
|
-
const url = request4.url ?? "/";
|
|
3889
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
3672
3890
|
const handle = async () => {
|
|
3673
|
-
if (url === sessionsPath) {
|
|
3891
|
+
if (url.pathname === sessionsPath) {
|
|
3674
3892
|
if (request4.method !== "GET") {
|
|
3675
3893
|
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3676
3894
|
return;
|
|
@@ -3678,7 +3896,20 @@ function createRcControlHandler(deps) {
|
|
|
3678
3896
|
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3679
3897
|
return;
|
|
3680
3898
|
}
|
|
3681
|
-
if (url ===
|
|
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) {
|
|
3682
3913
|
if (request4.method !== "POST") {
|
|
3683
3914
|
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3684
3915
|
return;
|
|
@@ -3702,7 +3933,36 @@ function createRcControlHandler(deps) {
|
|
|
3702
3933
|
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3703
3934
|
return;
|
|
3704
3935
|
}
|
|
3705
|
-
|
|
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}` });
|
|
3706
3966
|
};
|
|
3707
3967
|
handle().catch((error) => {
|
|
3708
3968
|
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
@@ -3761,6 +4021,39 @@ function isRcSessionSummary(value) {
|
|
|
3761
4021
|
}
|
|
3762
4022
|
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
3763
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
|
+
}
|
|
3764
4057
|
function sequenceNumsOfBody(body) {
|
|
3765
4058
|
let parsed;
|
|
3766
4059
|
try {
|
|
@@ -3783,6 +4076,13 @@ function sequenceNumsOfBody(body) {
|
|
|
3783
4076
|
}
|
|
3784
4077
|
function frontDoorRcControl(transport, token) {
|
|
3785
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
|
+
};
|
|
3786
4086
|
const sessionsOf = (answer, what) => {
|
|
3787
4087
|
if (answer.status !== HTTP_STATUS.ok) {
|
|
3788
4088
|
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
@@ -3806,29 +4106,77 @@ function frontDoorRcControl(transport, token) {
|
|
|
3806
4106
|
}
|
|
3807
4107
|
return sessions;
|
|
3808
4108
|
};
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
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}`);
|
|
3822
4126
|
}
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
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}`);
|
|
3829
4148
|
}
|
|
3830
|
-
|
|
4149
|
+
pending.push(value);
|
|
3831
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
|
+
)
|
|
3832
4180
|
};
|
|
3833
4181
|
}
|
|
3834
4182
|
|
|
@@ -5490,14 +5838,14 @@ function providerExists(paths, name) {
|
|
|
5490
5838
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
5491
5839
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
5492
5840
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
5493
|
-
function
|
|
5841
|
+
function isRecord3(value) {
|
|
5494
5842
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5495
5843
|
}
|
|
5496
5844
|
function legacyProviderConversion(raw) {
|
|
5497
|
-
if (!
|
|
5845
|
+
if (!isRecord3(raw) || "credential" in raw) {
|
|
5498
5846
|
return void 0;
|
|
5499
5847
|
}
|
|
5500
|
-
const env =
|
|
5848
|
+
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
5501
5849
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
5502
5850
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
5503
5851
|
if (fields.length === 0) {
|
|
@@ -8743,6 +9091,8 @@ export {
|
|
|
8743
9091
|
ProviderNotFoundError,
|
|
8744
9092
|
ProviderSchema,
|
|
8745
9093
|
RC_IDLE_EXPIRY_MS,
|
|
9094
|
+
RC_PENDING_DEADLINE_MS,
|
|
9095
|
+
RC_REQUEST_PARSE_CAP_BYTES,
|
|
8746
9096
|
RC_SESSIONS_PATH_PREFIX,
|
|
8747
9097
|
ROUTED_PATH_PREFIX,
|
|
8748
9098
|
UsageError,
|
|
@@ -8753,8 +9103,10 @@ export {
|
|
|
8753
9103
|
addIdentity,
|
|
8754
9104
|
addPool,
|
|
8755
9105
|
addProvider,
|
|
9106
|
+
answerRcControlRequest,
|
|
8756
9107
|
buildEntryFacts,
|
|
8757
9108
|
buildLayoutPaths,
|
|
9109
|
+
buildRcControlResponsePayload,
|
|
8758
9110
|
buildRcEventWriteBody,
|
|
8759
9111
|
buildRcUserMessagePayload,
|
|
8760
9112
|
carryOver,
|
|
@@ -8794,7 +9146,7 @@ export {
|
|
|
8794
9146
|
prepareLaunch,
|
|
8795
9147
|
profileExists,
|
|
8796
9148
|
providerExists,
|
|
8797
|
-
|
|
9149
|
+
rcEventWriteResultFromAnswer,
|
|
8798
9150
|
rcObserverAsPassthrough,
|
|
8799
9151
|
readActiveIdentity,
|
|
8800
9152
|
readDirectoryRules,
|