agent-shim 8.3.0 → 8.4.1
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 +494 -69
- 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.cjs
CHANGED
|
@@ -74,6 +74,8 @@ __export(index_exports, {
|
|
|
74
74
|
ProviderNotFoundError: () => ProviderNotFoundError,
|
|
75
75
|
ProviderSchema: () => ProviderSchema,
|
|
76
76
|
RC_IDLE_EXPIRY_MS: () => RC_IDLE_EXPIRY_MS,
|
|
77
|
+
RC_PENDING_DEADLINE_MS: () => RC_PENDING_DEADLINE_MS,
|
|
78
|
+
RC_REQUEST_PARSE_CAP_BYTES: () => RC_REQUEST_PARSE_CAP_BYTES,
|
|
77
79
|
RC_SESSIONS_PATH_PREFIX: () => RC_SESSIONS_PATH_PREFIX,
|
|
78
80
|
ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
|
|
79
81
|
UsageError: () => UsageError,
|
|
@@ -84,8 +86,10 @@ __export(index_exports, {
|
|
|
84
86
|
addIdentity: () => addIdentity,
|
|
85
87
|
addPool: () => addPool,
|
|
86
88
|
addProvider: () => addProvider,
|
|
89
|
+
answerRcControlRequest: () => answerRcControlRequest,
|
|
87
90
|
buildEntryFacts: () => buildEntryFacts,
|
|
88
91
|
buildLayoutPaths: () => buildLayoutPaths,
|
|
92
|
+
buildRcControlResponsePayload: () => buildRcControlResponsePayload,
|
|
89
93
|
buildRcEventWriteBody: () => buildRcEventWriteBody,
|
|
90
94
|
buildRcUserMessagePayload: () => buildRcUserMessagePayload,
|
|
91
95
|
carryOver: () => carryOver,
|
|
@@ -125,7 +129,7 @@ __export(index_exports, {
|
|
|
125
129
|
prepareLaunch: () => prepareLaunch,
|
|
126
130
|
profileExists: () => profileExists,
|
|
127
131
|
providerExists: () => providerExists,
|
|
128
|
-
|
|
132
|
+
rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
|
|
129
133
|
rcObserverAsPassthrough: () => rcObserverAsPassthrough,
|
|
130
134
|
readActiveIdentity: () => readActiveIdentity,
|
|
131
135
|
readDirectoryRules: () => readDirectoryRules,
|
|
@@ -3522,6 +3526,11 @@ function realConnectEffects() {
|
|
|
3522
3526
|
request4.on("data", (chunk) => {
|
|
3523
3527
|
observer.onRequestChunk(chunk);
|
|
3524
3528
|
});
|
|
3529
|
+
if (observer.onRequestEnd !== void 0) {
|
|
3530
|
+
request4.on("end", () => {
|
|
3531
|
+
observer.onRequestEnd?.();
|
|
3532
|
+
});
|
|
3533
|
+
}
|
|
3525
3534
|
response.on("close", () => {
|
|
3526
3535
|
observer.onEnd();
|
|
3527
3536
|
});
|
|
@@ -3557,6 +3566,9 @@ var RC_SESSION_ID_PREFIX = "cse_";
|
|
|
3557
3566
|
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3558
3567
|
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3559
3568
|
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3569
|
+
var RC_PENDING_DEADLINE_MS = 14e3;
|
|
3570
|
+
var RC_REQUEST_PARSE_CAP_BYTES = 262144;
|
|
3571
|
+
var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
|
|
3560
3572
|
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3561
3573
|
function isSuccessful(status) {
|
|
3562
3574
|
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
@@ -3582,20 +3594,124 @@ function sessionCreateId(body) {
|
|
|
3582
3594
|
const id = session.id;
|
|
3583
3595
|
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3584
3596
|
}
|
|
3597
|
+
function isRecord2(value) {
|
|
3598
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3599
|
+
}
|
|
3600
|
+
function parseJsonObject(body) {
|
|
3601
|
+
let parsed;
|
|
3602
|
+
try {
|
|
3603
|
+
parsed = JSON.parse(body);
|
|
3604
|
+
} catch {
|
|
3605
|
+
return void 0;
|
|
3606
|
+
}
|
|
3607
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
3608
|
+
}
|
|
3609
|
+
function nonEmptyString(record, field) {
|
|
3610
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3611
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3612
|
+
}
|
|
3613
|
+
function finiteNumber(record, field) {
|
|
3614
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3615
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3616
|
+
}
|
|
3617
|
+
function excerpt(text) {
|
|
3618
|
+
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
3619
|
+
}
|
|
3620
|
+
function payloadEventsOf(body) {
|
|
3621
|
+
const parsed = parseJsonObject(body);
|
|
3622
|
+
const events = parsed === void 0 ? void 0 : parsed.events;
|
|
3623
|
+
if (!Array.isArray(events)) {
|
|
3624
|
+
return [];
|
|
3625
|
+
}
|
|
3626
|
+
const payloads = [];
|
|
3627
|
+
for (const event of events) {
|
|
3628
|
+
if (!isRecord2(event)) {
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
const payload = event.payload;
|
|
3632
|
+
if (!isRecord2(payload)) {
|
|
3633
|
+
continue;
|
|
3634
|
+
}
|
|
3635
|
+
payloads.push(payload);
|
|
3636
|
+
}
|
|
3637
|
+
return payloads;
|
|
3638
|
+
}
|
|
3639
|
+
function pendingOfControlRequestPayload(payload) {
|
|
3640
|
+
if (payload.type !== "control_request") {
|
|
3641
|
+
return void 0;
|
|
3642
|
+
}
|
|
3643
|
+
const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3644
|
+
if (requestId === void 0) {
|
|
3645
|
+
return void 0;
|
|
3646
|
+
}
|
|
3647
|
+
const request4 = isRecord2(payload.request) ? payload.request : void 0;
|
|
3648
|
+
const subtype = nonEmptyString(request4, "subtype");
|
|
3649
|
+
const type = subtype ?? "control_request";
|
|
3650
|
+
let summary = "";
|
|
3651
|
+
if (subtype === "can_use_tool") {
|
|
3652
|
+
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3653
|
+
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3654
|
+
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3655
|
+
} else if (request4 !== void 0) {
|
|
3656
|
+
summary = excerpt(JSON.stringify(request4));
|
|
3657
|
+
}
|
|
3658
|
+
return { requestId, type, summary };
|
|
3659
|
+
}
|
|
3660
|
+
function answeredRequestIdsOfControlResponsePayload(payload) {
|
|
3661
|
+
if (payload.type !== "control_response") {
|
|
3662
|
+
return [];
|
|
3663
|
+
}
|
|
3664
|
+
const response = isRecord2(payload.response) ? payload.response : void 0;
|
|
3665
|
+
const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3666
|
+
return id === void 0 ? [] : [id];
|
|
3667
|
+
}
|
|
3668
|
+
function parseRcRequestBody(kind, body) {
|
|
3669
|
+
if (kind === "heartbeat") {
|
|
3670
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
|
|
3671
|
+
}
|
|
3672
|
+
if (kind === "worker-registration") {
|
|
3673
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
|
|
3674
|
+
}
|
|
3675
|
+
const controlRequests = [];
|
|
3676
|
+
const answeredRequestIds = [];
|
|
3677
|
+
for (const payload of payloadEventsOf(body)) {
|
|
3678
|
+
const pending = pendingOfControlRequestPayload(payload);
|
|
3679
|
+
if (pending !== void 0) {
|
|
3680
|
+
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3681
|
+
continue;
|
|
3682
|
+
}
|
|
3683
|
+
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3684
|
+
}
|
|
3685
|
+
return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
|
|
3686
|
+
}
|
|
3585
3687
|
function createRcSessionTracker(deps) {
|
|
3586
3688
|
const entries = /* @__PURE__ */ new Map();
|
|
3587
3689
|
const expire = (id) => {
|
|
3588
3690
|
entries.delete(id);
|
|
3589
3691
|
};
|
|
3692
|
+
const expirePendings = (entry, now) => {
|
|
3693
|
+
for (const [requestId, pending] of entry.pending) {
|
|
3694
|
+
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3695
|
+
entry.pending.delete(requestId);
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
};
|
|
3590
3699
|
const sweep = () => {
|
|
3591
3700
|
for (const [id, entry] of entries) {
|
|
3592
3701
|
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3593
3702
|
expire(id);
|
|
3703
|
+
continue;
|
|
3594
3704
|
}
|
|
3705
|
+
expirePendings(entry, deps.now());
|
|
3595
3706
|
}
|
|
3596
3707
|
};
|
|
3597
3708
|
const timer = setInterval(sweep, deps.idleMs);
|
|
3598
3709
|
timer.unref();
|
|
3710
|
+
const birth = (id, facts) => {
|
|
3711
|
+
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
|
|
3712
|
+
entries.set(id, record);
|
|
3713
|
+
return record;
|
|
3714
|
+
};
|
|
3599
3715
|
const observeExchange = (request4) => {
|
|
3600
3716
|
const method = (request4.method ?? "").toUpperCase();
|
|
3601
3717
|
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
@@ -3612,30 +3728,30 @@ function createRcSessionTracker(deps) {
|
|
|
3612
3728
|
if (method !== "POST") {
|
|
3613
3729
|
return void 0;
|
|
3614
3730
|
}
|
|
3615
|
-
let
|
|
3616
|
-
let
|
|
3731
|
+
let accepted2 = false;
|
|
3732
|
+
let buffered2 = 0;
|
|
3617
3733
|
let body = "";
|
|
3618
3734
|
return {
|
|
3619
3735
|
onResponse: (status) => {
|
|
3620
|
-
|
|
3736
|
+
accepted2 = isSuccessful(status);
|
|
3621
3737
|
},
|
|
3622
3738
|
onBodyChunk: (chunk) => {
|
|
3623
|
-
if (!
|
|
3739
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3624
3740
|
return;
|
|
3625
3741
|
}
|
|
3626
3742
|
const text = Buffer.from(chunk).toString("utf8");
|
|
3627
|
-
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES -
|
|
3628
|
-
|
|
3743
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3744
|
+
buffered2 = body.length;
|
|
3629
3745
|
},
|
|
3630
3746
|
onEnd: () => {
|
|
3631
|
-
if (!
|
|
3747
|
+
if (!accepted2) {
|
|
3632
3748
|
return;
|
|
3633
3749
|
}
|
|
3634
3750
|
const id2 = sessionCreateId(body);
|
|
3635
3751
|
if (id2 === void 0) {
|
|
3636
3752
|
return;
|
|
3637
3753
|
}
|
|
3638
|
-
|
|
3754
|
+
birth(id2, facts);
|
|
3639
3755
|
}
|
|
3640
3756
|
};
|
|
3641
3757
|
}
|
|
@@ -3644,21 +3760,69 @@ function createRcSessionTracker(deps) {
|
|
|
3644
3760
|
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3645
3761
|
return void 0;
|
|
3646
3762
|
}
|
|
3647
|
-
const entry = entries.get(id);
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3763
|
+
const entry = entries.get(id) ?? birth(id, facts);
|
|
3764
|
+
entry.lastSeenAt = deps.now();
|
|
3765
|
+
entry.authorization = facts.authorization ?? entry.authorization;
|
|
3766
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3767
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3768
|
+
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;
|
|
3769
|
+
let chunks = [];
|
|
3770
|
+
let buffered = 0;
|
|
3771
|
+
let oversized = false;
|
|
3772
|
+
let requestEnded = false;
|
|
3773
|
+
let accepted;
|
|
3774
|
+
let filed = false;
|
|
3775
|
+
const file = () => {
|
|
3776
|
+
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3777
|
+
return;
|
|
3778
|
+
}
|
|
3779
|
+
filed = true;
|
|
3780
|
+
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3781
|
+
chunks = [];
|
|
3782
|
+
const entryNow = entries.get(id);
|
|
3783
|
+
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
const now = deps.now();
|
|
3787
|
+
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3788
|
+
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3789
|
+
}
|
|
3790
|
+
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3791
|
+
entryNow.pending.delete(requestId);
|
|
3792
|
+
}
|
|
3793
|
+
if (factsOfBody.idleSeconds !== void 0) {
|
|
3794
|
+
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3795
|
+
}
|
|
3796
|
+
if (factsOfBody.workerStatus !== void 0) {
|
|
3797
|
+
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3798
|
+
}
|
|
3799
|
+
};
|
|
3656
3800
|
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3657
3801
|
return {
|
|
3802
|
+
...bodyKind === void 0 ? {} : {
|
|
3803
|
+
onRequestChunk: (chunk) => {
|
|
3804
|
+
if (oversized) {
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
chunks.push(Buffer.from(chunk));
|
|
3808
|
+
buffered += chunk.length;
|
|
3809
|
+
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3810
|
+
oversized = true;
|
|
3811
|
+
chunks = [];
|
|
3812
|
+
}
|
|
3813
|
+
},
|
|
3814
|
+
onRequestEnd: () => {
|
|
3815
|
+
requestEnded = true;
|
|
3816
|
+
file();
|
|
3817
|
+
}
|
|
3818
|
+
},
|
|
3658
3819
|
onResponse: (status, headers) => {
|
|
3659
3820
|
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3660
3821
|
expire(id);
|
|
3822
|
+
return;
|
|
3661
3823
|
}
|
|
3824
|
+
accepted = isSuccessful(status);
|
|
3825
|
+
file();
|
|
3662
3826
|
},
|
|
3663
3827
|
onBodyChunk: () => {
|
|
3664
3828
|
},
|
|
@@ -3666,16 +3830,31 @@ function createRcSessionTracker(deps) {
|
|
|
3666
3830
|
}
|
|
3667
3831
|
};
|
|
3668
3832
|
};
|
|
3833
|
+
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3834
|
+
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3835
|
+
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3836
|
+
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3669
3837
|
return {
|
|
3670
3838
|
observeExchange,
|
|
3671
3839
|
list: () => {
|
|
3672
3840
|
sweep();
|
|
3673
|
-
return
|
|
3841
|
+
return sortedEntries().map(summaryOf);
|
|
3842
|
+
},
|
|
3843
|
+
statusOf: (sessionId) => {
|
|
3844
|
+
sweep();
|
|
3845
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3846
|
+
},
|
|
3847
|
+
pendingOf: (sessionId) => {
|
|
3848
|
+
sweep();
|
|
3849
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3674
3850
|
},
|
|
3675
3851
|
credentialOf: (sessionId) => {
|
|
3676
3852
|
const entry = entries.get(sessionId);
|
|
3677
3853
|
return entry === void 0 ? void 0 : { authorization: entry.authorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3678
3854
|
},
|
|
3855
|
+
completePending: (sessionId, requestId) => {
|
|
3856
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3857
|
+
},
|
|
3679
3858
|
close: () => {
|
|
3680
3859
|
clearInterval(timer);
|
|
3681
3860
|
}
|
|
@@ -3686,6 +3865,14 @@ function observingRoutedRoute(route, tracker) {
|
|
|
3686
3865
|
...route,
|
|
3687
3866
|
serve: async (request4, response) => {
|
|
3688
3867
|
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3868
|
+
if (observed?.onRequestChunk !== void 0) {
|
|
3869
|
+
request4.body.on("data", (chunk) => {
|
|
3870
|
+
observed.onRequestChunk?.(chunk);
|
|
3871
|
+
});
|
|
3872
|
+
request4.body.on("end", () => {
|
|
3873
|
+
observed.onRequestEnd?.();
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3689
3876
|
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed));
|
|
3690
3877
|
}
|
|
3691
3878
|
};
|
|
@@ -3718,9 +3905,13 @@ function teeRoutedResponse(response, observer) {
|
|
|
3718
3905
|
}
|
|
3719
3906
|
function rcObserverAsPassthrough(observer) {
|
|
3720
3907
|
return {
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3908
|
+
onRequestChunk: (chunk) => {
|
|
3909
|
+
observer.onRequestChunk?.(chunk);
|
|
3910
|
+
},
|
|
3911
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3912
|
+
onRequestEnd: () => {
|
|
3913
|
+
observer.onRequestEnd?.();
|
|
3914
|
+
}
|
|
3724
3915
|
},
|
|
3725
3916
|
onResponse: (status, headers) => {
|
|
3726
3917
|
observer.onResponse(status, headers);
|
|
@@ -3769,7 +3960,7 @@ function sequenceNumsOf(body) {
|
|
|
3769
3960
|
}
|
|
3770
3961
|
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3771
3962
|
}
|
|
3772
|
-
function
|
|
3963
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3773
3964
|
if (!isSuccessful(answer.status)) {
|
|
3774
3965
|
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3775
3966
|
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}` };
|
|
@@ -3782,11 +3973,7 @@ function rcInjectResultFromAnswer(answer) {
|
|
|
3782
3973
|
}
|
|
3783
3974
|
return { ok: true, sequenceNums };
|
|
3784
3975
|
}
|
|
3785
|
-
|
|
3786
|
-
const credential = deps.credentialOf(sessionId);
|
|
3787
|
-
if (credential === void 0) {
|
|
3788
|
-
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)` };
|
|
3789
|
-
}
|
|
3976
|
+
function rcWriteHeaders(credential) {
|
|
3790
3977
|
const headers = { "content-type": "application/json" };
|
|
3791
3978
|
if (credential.authorization !== void 0) {
|
|
3792
3979
|
headers.authorization = credential.authorization;
|
|
@@ -3797,14 +3984,46 @@ async function injectRcUserMessage(deps, sessionId, text) {
|
|
|
3797
3984
|
if (credential.anthropicClientPlatform !== void 0) {
|
|
3798
3985
|
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3799
3986
|
}
|
|
3987
|
+
return headers;
|
|
3988
|
+
}
|
|
3989
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3990
|
+
const credential = deps.credentialOf(sessionId);
|
|
3991
|
+
if (credential === void 0) {
|
|
3992
|
+
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)` };
|
|
3993
|
+
}
|
|
3800
3994
|
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3801
3995
|
let answer;
|
|
3802
3996
|
try {
|
|
3803
|
-
answer = await deps.dial.writeEvents(sessionId,
|
|
3997
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3804
3998
|
} catch (error) {
|
|
3805
3999
|
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3806
4000
|
}
|
|
3807
|
-
return
|
|
4001
|
+
return rcEventWriteResultFromAnswer(answer);
|
|
4002
|
+
}
|
|
4003
|
+
function buildRcControlResponsePayload(requestId, decision) {
|
|
4004
|
+
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
4005
|
+
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
4006
|
+
}
|
|
4007
|
+
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
4008
|
+
const credential = deps.credentialOf(sessionId);
|
|
4009
|
+
if (credential === void 0) {
|
|
4010
|
+
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)` };
|
|
4011
|
+
}
|
|
4012
|
+
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
4013
|
+
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` };
|
|
4014
|
+
}
|
|
4015
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
4016
|
+
let answer;
|
|
4017
|
+
try {
|
|
4018
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4021
|
+
}
|
|
4022
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
4023
|
+
if (result.ok) {
|
|
4024
|
+
deps.completePending(sessionId, requestId);
|
|
4025
|
+
}
|
|
4026
|
+
return result;
|
|
3808
4027
|
}
|
|
3809
4028
|
|
|
3810
4029
|
// src/frontdoor/rcControl.ts
|
|
@@ -3839,7 +4058,10 @@ async function readBody(request4) {
|
|
|
3839
4058
|
}
|
|
3840
4059
|
function createRcControlHandler(deps) {
|
|
3841
4060
|
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
4061
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
4062
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3842
4063
|
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
4064
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3843
4065
|
return (request4, response) => {
|
|
3844
4066
|
const presented = request4.headers.authorization;
|
|
3845
4067
|
const bearerPrefix = "bearer ".length;
|
|
@@ -3848,9 +4070,9 @@ function createRcControlHandler(deps) {
|
|
|
3848
4070
|
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3849
4071
|
return;
|
|
3850
4072
|
}
|
|
3851
|
-
const url = request4.url ?? "/";
|
|
4073
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
3852
4074
|
const handle = async () => {
|
|
3853
|
-
if (url === sessionsPath) {
|
|
4075
|
+
if (url.pathname === sessionsPath) {
|
|
3854
4076
|
if (request4.method !== "GET") {
|
|
3855
4077
|
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3856
4078
|
return;
|
|
@@ -3858,7 +4080,20 @@ function createRcControlHandler(deps) {
|
|
|
3858
4080
|
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3859
4081
|
return;
|
|
3860
4082
|
}
|
|
3861
|
-
if (url ===
|
|
4083
|
+
if (url.pathname === statusPath || url.pathname === pendingPath) {
|
|
4084
|
+
if (request4.method !== "GET") {
|
|
4085
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
4086
|
+
return;
|
|
4087
|
+
}
|
|
4088
|
+
const session = url.searchParams.get("session") ?? void 0;
|
|
4089
|
+
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
4090
|
+
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)` });
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
4094
|
+
return;
|
|
4095
|
+
}
|
|
4096
|
+
if (url.pathname === injectPath) {
|
|
3862
4097
|
if (request4.method !== "POST") {
|
|
3863
4098
|
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3864
4099
|
return;
|
|
@@ -3882,7 +4117,36 @@ function createRcControlHandler(deps) {
|
|
|
3882
4117
|
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3883
4118
|
return;
|
|
3884
4119
|
}
|
|
3885
|
-
|
|
4120
|
+
if (url.pathname === answerPath) {
|
|
4121
|
+
if (request4.method !== "POST") {
|
|
4122
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
|
|
4123
|
+
return;
|
|
4124
|
+
}
|
|
4125
|
+
let parsed;
|
|
4126
|
+
try {
|
|
4127
|
+
parsed = JSON.parse(await readBody(request4));
|
|
4128
|
+
} catch {
|
|
4129
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
4132
|
+
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 === "") {
|
|
4133
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
|
|
4137
|
+
if (parsed.approve && text !== void 0 && text !== "") {
|
|
4138
|
+
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" });
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4141
|
+
const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
|
|
4142
|
+
if (result.ok) {
|
|
4143
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
4144
|
+
return;
|
|
4145
|
+
}
|
|
4146
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4147
|
+
return;
|
|
4148
|
+
}
|
|
4149
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
|
|
3886
4150
|
};
|
|
3887
4151
|
handle().catch((error) => {
|
|
3888
4152
|
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
@@ -3941,6 +4205,39 @@ function isRcSessionSummary(value) {
|
|
|
3941
4205
|
}
|
|
3942
4206
|
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
3943
4207
|
}
|
|
4208
|
+
function isRcWorkerFact(value, valueIs) {
|
|
4209
|
+
if (typeof value !== "object" || value === null) {
|
|
4210
|
+
return false;
|
|
4211
|
+
}
|
|
4212
|
+
if (!("value" in value) || !("observedAt" in value)) {
|
|
4213
|
+
return false;
|
|
4214
|
+
}
|
|
4215
|
+
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
4216
|
+
}
|
|
4217
|
+
function isRcSessionStatus(value) {
|
|
4218
|
+
if (!isRcSessionSummary(value)) {
|
|
4219
|
+
return false;
|
|
4220
|
+
}
|
|
4221
|
+
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4222
|
+
return false;
|
|
4223
|
+
}
|
|
4224
|
+
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4225
|
+
return false;
|
|
4226
|
+
}
|
|
4227
|
+
if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
|
|
4228
|
+
return false;
|
|
4229
|
+
}
|
|
4230
|
+
return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
|
|
4231
|
+
}
|
|
4232
|
+
function isRcPendingRequestSummary(value) {
|
|
4233
|
+
if (typeof value !== "object" || value === null) {
|
|
4234
|
+
return false;
|
|
4235
|
+
}
|
|
4236
|
+
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4237
|
+
return false;
|
|
4238
|
+
}
|
|
4239
|
+
return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
|
|
4240
|
+
}
|
|
3944
4241
|
function sequenceNumsOfBody(body) {
|
|
3945
4242
|
let parsed;
|
|
3946
4243
|
try {
|
|
@@ -3963,6 +4260,13 @@ function sequenceNumsOfBody(body) {
|
|
|
3963
4260
|
}
|
|
3964
4261
|
function frontDoorRcControl(transport, token) {
|
|
3965
4262
|
const headers = { authorization: `Bearer ${token}` };
|
|
4263
|
+
const listAnswerOf = async (path29, what) => {
|
|
4264
|
+
try {
|
|
4265
|
+
return await transport.request({ method: "GET", path: path29, headers });
|
|
4266
|
+
} catch (error) {
|
|
4267
|
+
throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4268
|
+
}
|
|
4269
|
+
};
|
|
3966
4270
|
const sessionsOf = (answer, what) => {
|
|
3967
4271
|
if (answer.status !== HTTP_STATUS.ok) {
|
|
3968
4272
|
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
@@ -3986,29 +4290,77 @@ function frontDoorRcControl(transport, token) {
|
|
|
3986
4290
|
}
|
|
3987
4291
|
return sessions;
|
|
3988
4292
|
};
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4293
|
+
const statusesOf = (answer, what) => {
|
|
4294
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4295
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4296
|
+
}
|
|
4297
|
+
let parsed;
|
|
4298
|
+
try {
|
|
4299
|
+
parsed = JSON.parse(answer.body);
|
|
4300
|
+
} catch {
|
|
4301
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4302
|
+
}
|
|
4303
|
+
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4304
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4305
|
+
}
|
|
4306
|
+
const statuses = [];
|
|
4307
|
+
for (const value of parsed.statuses) {
|
|
4308
|
+
if (!isRcSessionStatus(value)) {
|
|
4309
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4002
4310
|
}
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4311
|
+
statuses.push(value);
|
|
4312
|
+
}
|
|
4313
|
+
return statuses;
|
|
4314
|
+
};
|
|
4315
|
+
const pendingOfAnswer = (answer, what) => {
|
|
4316
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4317
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4318
|
+
}
|
|
4319
|
+
let parsed;
|
|
4320
|
+
try {
|
|
4321
|
+
parsed = JSON.parse(answer.body);
|
|
4322
|
+
} catch {
|
|
4323
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4324
|
+
}
|
|
4325
|
+
if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
|
|
4326
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4327
|
+
}
|
|
4328
|
+
const pending = [];
|
|
4329
|
+
for (const value of parsed.pending) {
|
|
4330
|
+
if (!isRcPendingRequestSummary(value)) {
|
|
4331
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4009
4332
|
}
|
|
4010
|
-
|
|
4333
|
+
pending.push(value);
|
|
4011
4334
|
}
|
|
4335
|
+
return pending;
|
|
4336
|
+
};
|
|
4337
|
+
const writeOf = async (path29, body, describeFailure) => {
|
|
4338
|
+
let answer;
|
|
4339
|
+
try {
|
|
4340
|
+
answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
|
|
4341
|
+
} catch (error) {
|
|
4342
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4343
|
+
}
|
|
4344
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4345
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4346
|
+
if (sequenceNums === void 0) {
|
|
4347
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4348
|
+
}
|
|
4349
|
+
return { ok: true, sequenceNums };
|
|
4350
|
+
}
|
|
4351
|
+
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4352
|
+
};
|
|
4353
|
+
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
4354
|
+
return {
|
|
4355
|
+
listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
|
|
4356
|
+
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"),
|
|
4357
|
+
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"),
|
|
4358
|
+
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}`),
|
|
4359
|
+
answerRequest: async (sessionId, requestId, decision) => await writeOf(
|
|
4360
|
+
`${CONTROL_PATH_PREFIX}/answer`,
|
|
4361
|
+
JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
|
|
4362
|
+
(status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4363
|
+
)
|
|
4012
4364
|
};
|
|
4013
4365
|
}
|
|
4014
4366
|
|
|
@@ -5670,14 +6022,14 @@ function providerExists(paths, name) {
|
|
|
5670
6022
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
5671
6023
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
5672
6024
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
5673
|
-
function
|
|
6025
|
+
function isRecord3(value) {
|
|
5674
6026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5675
6027
|
}
|
|
5676
6028
|
function legacyProviderConversion(raw) {
|
|
5677
|
-
if (!
|
|
6029
|
+
if (!isRecord3(raw) || "credential" in raw) {
|
|
5678
6030
|
return void 0;
|
|
5679
6031
|
}
|
|
5680
|
-
const env =
|
|
6032
|
+
const env = isRecord3(raw.env) ? raw.env : void 0;
|
|
5681
6033
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
5682
6034
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
5683
6035
|
if (fields.length === 0) {
|
|
@@ -8924,6 +9276,8 @@ function prepareClaudeLaunch(options) {
|
|
|
8924
9276
|
ProviderNotFoundError,
|
|
8925
9277
|
ProviderSchema,
|
|
8926
9278
|
RC_IDLE_EXPIRY_MS,
|
|
9279
|
+
RC_PENDING_DEADLINE_MS,
|
|
9280
|
+
RC_REQUEST_PARSE_CAP_BYTES,
|
|
8927
9281
|
RC_SESSIONS_PATH_PREFIX,
|
|
8928
9282
|
ROUTED_PATH_PREFIX,
|
|
8929
9283
|
UsageError,
|
|
@@ -8934,8 +9288,10 @@ function prepareClaudeLaunch(options) {
|
|
|
8934
9288
|
addIdentity,
|
|
8935
9289
|
addPool,
|
|
8936
9290
|
addProvider,
|
|
9291
|
+
answerRcControlRequest,
|
|
8937
9292
|
buildEntryFacts,
|
|
8938
9293
|
buildLayoutPaths,
|
|
9294
|
+
buildRcControlResponsePayload,
|
|
8939
9295
|
buildRcEventWriteBody,
|
|
8940
9296
|
buildRcUserMessagePayload,
|
|
8941
9297
|
carryOver,
|
|
@@ -8975,7 +9331,7 @@ function prepareClaudeLaunch(options) {
|
|
|
8975
9331
|
prepareLaunch,
|
|
8976
9332
|
profileExists,
|
|
8977
9333
|
providerExists,
|
|
8978
|
-
|
|
9334
|
+
rcEventWriteResultFromAnswer,
|
|
8979
9335
|
rcObserverAsPassthrough,
|
|
8980
9336
|
readActiveIdentity,
|
|
8981
9337
|
readDirectoryRules,
|