agent-shim 8.23.0 → 8.25.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 +10 -0
- package/dist/cli.cjs +2494 -1867
- package/dist/index.cjs +792 -207
- package/dist/index.mjs +765 -207
- package/dist/types/index.d.ts +410 -15
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3207,9 +3207,6 @@ var HTTP_STATUS = {
|
|
|
3207
3207
|
badGateway: 502
|
|
3208
3208
|
};
|
|
3209
3209
|
|
|
3210
|
-
// src/frontdoor/rcControl.ts
|
|
3211
|
-
import * as https from "node:https";
|
|
3212
|
-
|
|
3213
3210
|
// src/frontdoor/rcSessions.ts
|
|
3214
3211
|
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3215
3212
|
var RC_SESSION_ID_PREFIX = "cse_";
|
|
@@ -3630,6 +3627,8 @@ function rcObserverAsPassthrough(observer) {
|
|
|
3630
3627
|
}
|
|
3631
3628
|
};
|
|
3632
3629
|
}
|
|
3630
|
+
|
|
3631
|
+
// src/frontdoor/rcWrites.ts
|
|
3633
3632
|
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3634
3633
|
return {
|
|
3635
3634
|
type: "user",
|
|
@@ -3730,14 +3729,55 @@ var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan"
|
|
|
3730
3729
|
function isRcPermissionMode(value) {
|
|
3731
3730
|
return RC_PERMISSION_MODES.some((mode) => mode === value);
|
|
3732
3731
|
}
|
|
3732
|
+
function buildRcControlRequestPayload(requestId, request4) {
|
|
3733
|
+
return { type: "control_request", request_id: requestId, request: request4 };
|
|
3734
|
+
}
|
|
3733
3735
|
function buildRcInterruptPayload(requestId) {
|
|
3734
|
-
return
|
|
3736
|
+
return buildRcControlRequestPayload(requestId, { subtype: "interrupt" });
|
|
3735
3737
|
}
|
|
3736
3738
|
function buildRcSetModelPayload(requestId, model) {
|
|
3737
|
-
return
|
|
3739
|
+
return buildRcControlRequestPayload(requestId, { subtype: "set_model", model });
|
|
3738
3740
|
}
|
|
3739
3741
|
function buildRcSetPermissionModePayload(requestId, mode) {
|
|
3740
|
-
return
|
|
3742
|
+
return buildRcControlRequestPayload(requestId, { subtype: "set_permission_mode", mode });
|
|
3743
|
+
}
|
|
3744
|
+
function buildRcEndSessionPayload(requestId, reason) {
|
|
3745
|
+
return buildRcControlRequestPayload(requestId, { subtype: "end_session", ...reason === void 0 ? {} : { reason } });
|
|
3746
|
+
}
|
|
3747
|
+
function buildRcGetUsagePayload(requestId, skipBehaviors) {
|
|
3748
|
+
return buildRcControlRequestPayload(requestId, { subtype: "get_usage", ...skipBehaviors === true ? { skip_behaviors: true } : {} });
|
|
3749
|
+
}
|
|
3750
|
+
var RC_CONTEXT_USAGE_DETAILS = ["summary", "full"];
|
|
3751
|
+
function isRcContextUsageDetail(value) {
|
|
3752
|
+
return RC_CONTEXT_USAGE_DETAILS.some((detail) => detail === value);
|
|
3753
|
+
}
|
|
3754
|
+
function buildRcGetContextUsagePayload(requestId, detail) {
|
|
3755
|
+
return buildRcControlRequestPayload(requestId, { subtype: "get_context_usage", ...detail === void 0 ? {} : { detail } });
|
|
3756
|
+
}
|
|
3757
|
+
var RC_READ_FILE_ENCODINGS = ["utf-8", "base64"];
|
|
3758
|
+
function isRcReadFileEncoding(value) {
|
|
3759
|
+
return RC_READ_FILE_ENCODINGS.some((encoding) => encoding === value);
|
|
3760
|
+
}
|
|
3761
|
+
function buildRcReadFilePayload(requestId, path32, options) {
|
|
3762
|
+
return buildRcControlRequestPayload(requestId, { subtype: "read_file", path: path32, ...options?.maxBytes === void 0 ? {} : { max_bytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } });
|
|
3763
|
+
}
|
|
3764
|
+
function buildRcFileSuggestionsPayload(requestId, query) {
|
|
3765
|
+
return buildRcControlRequestPayload(requestId, { subtype: "file_suggestions", query });
|
|
3766
|
+
}
|
|
3767
|
+
function buildRcMcpStatusPayload(requestId) {
|
|
3768
|
+
return buildRcControlRequestPayload(requestId, { subtype: "mcp_status" });
|
|
3769
|
+
}
|
|
3770
|
+
function buildRcMcpReconnectPayload(requestId, serverName) {
|
|
3771
|
+
return buildRcControlRequestPayload(requestId, { subtype: "mcp_reconnect", serverName });
|
|
3772
|
+
}
|
|
3773
|
+
function buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri) {
|
|
3774
|
+
return buildRcControlRequestPayload(requestId, { subtype: "mcp_authenticate", serverName, redirectUri });
|
|
3775
|
+
}
|
|
3776
|
+
function buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl) {
|
|
3777
|
+
return buildRcControlRequestPayload(requestId, { subtype: "mcp_oauth_callback_url", serverName, callbackUrl });
|
|
3778
|
+
}
|
|
3779
|
+
function buildRcKeepAlivePayload() {
|
|
3780
|
+
return { type: "keep_alive" };
|
|
3741
3781
|
}
|
|
3742
3782
|
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
3743
3783
|
const credential = deps.credentialOf(sessionId);
|
|
@@ -3764,7 +3804,7 @@ async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
|
3764
3804
|
}
|
|
3765
3805
|
return result;
|
|
3766
3806
|
}
|
|
3767
|
-
async function
|
|
3807
|
+
async function deliverRcClientPayload(deps, sessionId, payload, action, to = "events") {
|
|
3768
3808
|
const credential = deps.credentialOf(sessionId);
|
|
3769
3809
|
if (credential === void 0) {
|
|
3770
3810
|
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
@@ -3772,12 +3812,12 @@ async function interruptRcSession(deps, sessionId) {
|
|
|
3772
3812
|
if (credential.authorization === void 0) {
|
|
3773
3813
|
return noOAuthCredentialResult(sessionId);
|
|
3774
3814
|
}
|
|
3775
|
-
const body = JSON.stringify(buildRcEventWriteBody(
|
|
3815
|
+
const body = JSON.stringify(buildRcEventWriteBody(payload));
|
|
3776
3816
|
let answer;
|
|
3777
3817
|
try {
|
|
3778
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3818
|
+
answer = await (to === "teleport-events" ? deps.dial.writeTeleportEvents(sessionId, rcWriteHeaders(credential), body) : deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body));
|
|
3779
3819
|
} catch (error) {
|
|
3780
|
-
return { ok: false, message: `the door could not reach the API host to
|
|
3820
|
+
return { ok: false, message: `the door could not reach the API host to ${action}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3781
3821
|
}
|
|
3782
3822
|
const result = rcEventWriteResultFromAnswer(answer);
|
|
3783
3823
|
if (result.ok) {
|
|
@@ -3785,53 +3825,102 @@ async function interruptRcSession(deps, sessionId) {
|
|
|
3785
3825
|
}
|
|
3786
3826
|
return result;
|
|
3787
3827
|
}
|
|
3828
|
+
async function interruptRcSession(deps, sessionId) {
|
|
3829
|
+
const requestId = deps.newUuid();
|
|
3830
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcInterruptPayload(requestId), `interrupt session ${sessionId}`);
|
|
3831
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3832
|
+
}
|
|
3788
3833
|
async function setRcSessionModel(deps, sessionId, model) {
|
|
3789
3834
|
if (model === "") {
|
|
3790
3835
|
return { ok: false, message: "a set-model write needs a non-empty model id: the SDK's own field treats an omitted or null value as a reset to the session default, which this operation never sends, and an empty string is neither" };
|
|
3791
3836
|
}
|
|
3792
|
-
const
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3837
|
+
const requestId = deps.newUuid();
|
|
3838
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcSetModelPayload(requestId, model), `set the model on session ${sessionId}`);
|
|
3839
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3840
|
+
}
|
|
3841
|
+
async function setRcSessionPermissionMode(deps, sessionId, mode) {
|
|
3842
|
+
const requestId = deps.newUuid();
|
|
3843
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcSetPermissionModePayload(requestId, mode), `set the permission mode on session ${sessionId}`);
|
|
3844
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3845
|
+
}
|
|
3846
|
+
async function endRcSession(deps, sessionId, reason) {
|
|
3847
|
+
if (reason === "") {
|
|
3848
|
+
return { ok: false, message: "an end-session write needs either no reason or a non-empty one: the worker reads an absent reason as its own unspecified form, and an empty string is neither" };
|
|
3798
3849
|
}
|
|
3799
|
-
const
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3850
|
+
const requestId = deps.newUuid();
|
|
3851
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcEndSessionPayload(requestId, reason), `end session ${sessionId}`);
|
|
3852
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3853
|
+
}
|
|
3854
|
+
async function getRcSessionUsage(deps, sessionId, skipBehaviors) {
|
|
3855
|
+
const requestId = deps.newUuid();
|
|
3856
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcGetUsagePayload(requestId, skipBehaviors), `request the usage of session ${sessionId}`);
|
|
3857
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3858
|
+
}
|
|
3859
|
+
async function getRcSessionContextUsage(deps, sessionId, detail) {
|
|
3860
|
+
const requestId = deps.newUuid();
|
|
3861
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcGetContextUsagePayload(requestId, detail), `request the context usage of session ${sessionId}`);
|
|
3862
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3863
|
+
}
|
|
3864
|
+
async function readRcSessionFile(deps, sessionId, path32, options) {
|
|
3865
|
+
if (path32 === "") {
|
|
3866
|
+
return { ok: false, message: "a read-file write needs a non-empty path: the SDK's own field names the file the worker resolves against the session's cwd, and an empty string names none" };
|
|
3805
3867
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3868
|
+
if (options?.maxBytes !== void 0 && (!Number.isInteger(options.maxBytes) || options.maxBytes <= 0)) {
|
|
3869
|
+
return { ok: false, message: "a read-file write's max-bytes is a positive whole number of bytes: the SDK's own field caps the read, and a zero or negative cap reads nothing" };
|
|
3809
3870
|
}
|
|
3810
|
-
|
|
3871
|
+
const requestId = deps.newUuid();
|
|
3872
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcReadFilePayload(requestId, path32, options), `read ${path32} from session ${sessionId}`);
|
|
3873
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3811
3874
|
}
|
|
3812
|
-
async function
|
|
3813
|
-
const
|
|
3814
|
-
|
|
3815
|
-
|
|
3875
|
+
async function suggestRcSessionFiles(deps, sessionId, query) {
|
|
3876
|
+
const requestId = deps.newUuid();
|
|
3877
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcFileSuggestionsPayload(requestId, query), `request file suggestions from session ${sessionId}`);
|
|
3878
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3879
|
+
}
|
|
3880
|
+
async function getRcSessionMcpStatus(deps, sessionId) {
|
|
3881
|
+
const requestId = deps.newUuid();
|
|
3882
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpStatusPayload(requestId), `request the MCP status of session ${sessionId}`);
|
|
3883
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3884
|
+
}
|
|
3885
|
+
async function reconnectRcSessionMcpServer(deps, sessionId, serverName) {
|
|
3886
|
+
if (serverName === "") {
|
|
3887
|
+
return { ok: false, message: "an mcp-reconnect write needs a non-empty server name: the SDK's own field names the server as mcp_status reports it, and an empty string names none" };
|
|
3816
3888
|
}
|
|
3817
|
-
|
|
3818
|
-
|
|
3889
|
+
const requestId = deps.newUuid();
|
|
3890
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpReconnectPayload(requestId, serverName), `reconnect MCP server ${serverName} on session ${sessionId}`);
|
|
3891
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3892
|
+
}
|
|
3893
|
+
async function authenticateRcSessionMcpServer(deps, sessionId, serverName, redirectUri) {
|
|
3894
|
+
if (serverName === "" || redirectUri === "") {
|
|
3895
|
+
return { ok: false, message: "an mcp-authenticate write needs a non-empty server name and redirect URI: the CLI's own request names the server and where its OAuth flow redirects back to, and an empty string names neither" };
|
|
3819
3896
|
}
|
|
3820
|
-
const
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3897
|
+
const requestId = deps.newUuid();
|
|
3898
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpAuthenticatePayload(requestId, serverName, redirectUri), `authenticate MCP server ${serverName} on session ${sessionId}`);
|
|
3899
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3900
|
+
}
|
|
3901
|
+
async function submitRcSessionMcpOAuthCallbackUrl(deps, sessionId, serverName, callbackUrl) {
|
|
3902
|
+
if (serverName === "" || callbackUrl === "") {
|
|
3903
|
+
return { ok: false, message: "an mcp-oauth-callback-url write needs a non-empty server name and callback URL: the CLI's own request names the server and the URL its OAuth flow landed on, and an empty string names neither" };
|
|
3826
3904
|
}
|
|
3827
|
-
const
|
|
3828
|
-
|
|
3829
|
-
|
|
3905
|
+
const requestId = deps.newUuid();
|
|
3906
|
+
const result = await deliverRcClientPayload(deps, sessionId, buildRcMcpOAuthCallbackUrlPayload(requestId, serverName, callbackUrl), `hand MCP server ${serverName}'s OAuth callback to session ${sessionId}`);
|
|
3907
|
+
return result.ok ? { ...result, requestId } : result;
|
|
3908
|
+
}
|
|
3909
|
+
async function sendRcKeepAlive(deps, sessionId) {
|
|
3910
|
+
return await deliverRcClientPayload(deps, sessionId, buildRcKeepAlivePayload(), `keep session ${sessionId} alive`);
|
|
3911
|
+
}
|
|
3912
|
+
async function teleportRcSession(deps, sessionId, marker) {
|
|
3913
|
+
if (marker === "") {
|
|
3914
|
+
return { ok: false, message: "a teleport write needs a non-empty marker: the teleport relay anchors on a marker line's uuid and string content, and an empty string carries neither" };
|
|
3830
3915
|
}
|
|
3831
|
-
return
|
|
3916
|
+
return await deliverRcClientPayload(deps, sessionId, buildRcUserMessagePayload(deps.newUuid(), sessionId, marker), `teleport a marker into session ${sessionId}`, "teleport-events");
|
|
3917
|
+
}
|
|
3918
|
+
function buildRcMarkReadBody(eventId) {
|
|
3919
|
+
return eventId === void 0 ? {} : { event_id: eventId };
|
|
3832
3920
|
}
|
|
3833
3921
|
|
|
3834
3922
|
// src/frontdoor/rcControl.ts
|
|
3923
|
+
import * as https from "node:https";
|
|
3835
3924
|
var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
|
|
3836
3925
|
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3837
3926
|
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
@@ -3840,6 +3929,25 @@ function answerJson(response, status, body) {
|
|
|
3840
3929
|
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3841
3930
|
response.end(text);
|
|
3842
3931
|
}
|
|
3932
|
+
function isJsonObject(value) {
|
|
3933
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3934
|
+
}
|
|
3935
|
+
function bodyString(body, field) {
|
|
3936
|
+
const value = body[field];
|
|
3937
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3938
|
+
}
|
|
3939
|
+
function bodyStringAllowingEmpty(body, field) {
|
|
3940
|
+
const value = body[field];
|
|
3941
|
+
return typeof value === "string" ? value : void 0;
|
|
3942
|
+
}
|
|
3943
|
+
function bodyBoolean(body, field) {
|
|
3944
|
+
const value = body[field];
|
|
3945
|
+
return typeof value === "boolean" ? value : void 0;
|
|
3946
|
+
}
|
|
3947
|
+
function bodyPositiveInteger(body, field) {
|
|
3948
|
+
const value = body[field];
|
|
3949
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
3950
|
+
}
|
|
3843
3951
|
async function readBody(request4) {
|
|
3844
3952
|
return await new Promise((resolve, reject) => {
|
|
3845
3953
|
const chunks = [];
|
|
@@ -3867,9 +3975,136 @@ function createRcControlHandler(deps) {
|
|
|
3867
3975
|
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3868
3976
|
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3869
3977
|
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3870
|
-
const
|
|
3871
|
-
|
|
3872
|
-
|
|
3978
|
+
const controlWrites = [
|
|
3979
|
+
{
|
|
3980
|
+
name: "interrupt",
|
|
3981
|
+
path: "interrupt",
|
|
3982
|
+
read: (body) => {
|
|
3983
|
+
const session = bodyString(body, "session");
|
|
3984
|
+
return session === void 0 ? { error: "the interrupt body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.interrupt(session) };
|
|
3985
|
+
}
|
|
3986
|
+
},
|
|
3987
|
+
{
|
|
3988
|
+
name: "set-model",
|
|
3989
|
+
path: "set-model",
|
|
3990
|
+
read: (body) => {
|
|
3991
|
+
const session = bodyString(body, "session");
|
|
3992
|
+
const model = bodyString(body, "model");
|
|
3993
|
+
return session === void 0 || model === void 0 ? { error: "the set-model body must be JSON naming a non-empty session id and model id" } : { session, run: async () => await deps.setModel(session, model) };
|
|
3994
|
+
}
|
|
3995
|
+
},
|
|
3996
|
+
{
|
|
3997
|
+
name: "set-permission-mode",
|
|
3998
|
+
path: "set-permission-mode",
|
|
3999
|
+
read: (body) => {
|
|
4000
|
+
const session = bodyString(body, "session");
|
|
4001
|
+
const mode = body.mode;
|
|
4002
|
+
return session === void 0 || !isRcPermissionMode(mode) ? { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` } : { session, run: async () => await deps.setPermissionMode(session, mode) };
|
|
4003
|
+
}
|
|
4004
|
+
},
|
|
4005
|
+
{
|
|
4006
|
+
name: "end-session",
|
|
4007
|
+
path: "end-session",
|
|
4008
|
+
read: (body) => {
|
|
4009
|
+
const session = bodyString(body, "session");
|
|
4010
|
+
const reason = "reason" in body ? bodyString(body, "reason") : void 0;
|
|
4011
|
+
return session === void 0 || "reason" in body && reason === void 0 ? { error: "the end-session body must be JSON naming a non-empty session id, and a reason only as a non-empty string or absent" } : { session, run: async () => await deps.endSession(session, reason) };
|
|
4012
|
+
}
|
|
4013
|
+
},
|
|
4014
|
+
{
|
|
4015
|
+
name: "get-usage",
|
|
4016
|
+
path: "get-usage",
|
|
4017
|
+
read: (body) => {
|
|
4018
|
+
const session = bodyString(body, "session");
|
|
4019
|
+
const skipBehaviors = "skipBehaviors" in body ? bodyBoolean(body, "skipBehaviors") : void 0;
|
|
4020
|
+
return session === void 0 || "skipBehaviors" in body && skipBehaviors === void 0 ? { error: "the get-usage body must be JSON naming a non-empty session id, and skipBehaviors only as a boolean or absent" } : { session, run: async () => await deps.getUsage(session, skipBehaviors) };
|
|
4021
|
+
}
|
|
4022
|
+
},
|
|
4023
|
+
{
|
|
4024
|
+
name: "get-context-usage",
|
|
4025
|
+
path: "get-context-usage",
|
|
4026
|
+
read: (body) => {
|
|
4027
|
+
const session = bodyString(body, "session");
|
|
4028
|
+
const detail = "detail" in body && isRcContextUsageDetail(body.detail) ? body.detail : void 0;
|
|
4029
|
+
return session === void 0 || "detail" in body && detail === void 0 ? { error: `the get-context-usage body must be JSON naming a non-empty session id, and a detail level the SDK's own type permits (${RC_CONTEXT_USAGE_DETAILS.join(", ")}) or none` } : { session, run: async () => await deps.getContextUsage(session, detail) };
|
|
4030
|
+
}
|
|
4031
|
+
},
|
|
4032
|
+
{
|
|
4033
|
+
name: "read-file",
|
|
4034
|
+
path: "read-file",
|
|
4035
|
+
read: (body) => {
|
|
4036
|
+
const session = bodyString(body, "session");
|
|
4037
|
+
const path32 = bodyString(body, "path");
|
|
4038
|
+
const maxBytes = "maxBytes" in body ? bodyPositiveInteger(body, "maxBytes") : void 0;
|
|
4039
|
+
const encoding = "encoding" in body && isRcReadFileEncoding(body.encoding) ? body.encoding : void 0;
|
|
4040
|
+
const refused2 = session === void 0 || path32 === void 0 || "maxBytes" in body && maxBytes === void 0 || "encoding" in body && encoding === void 0;
|
|
4041
|
+
return refused2 ? { error: `the read-file body must be JSON naming a non-empty session id and path, maxBytes only as a positive whole number or absent, and an encoding the SDK's own type permits (${RC_READ_FILE_ENCODINGS.join(", ")}) or none` } : { session, run: async () => await deps.readFile(session, path32, { ...maxBytes === void 0 ? {} : { maxBytes }, ...encoding === void 0 ? {} : { encoding } }) };
|
|
4042
|
+
}
|
|
4043
|
+
},
|
|
4044
|
+
{
|
|
4045
|
+
name: "file-suggestions",
|
|
4046
|
+
path: "file-suggestions",
|
|
4047
|
+
read: (body) => {
|
|
4048
|
+
const session = bodyString(body, "session");
|
|
4049
|
+
const query = bodyStringAllowingEmpty(body, "query");
|
|
4050
|
+
return session === void 0 || query === void 0 ? { error: "the file-suggestions body must be JSON naming a non-empty session id and a query string, which may be empty" } : { session, run: async () => await deps.fileSuggestions(session, query) };
|
|
4051
|
+
}
|
|
4052
|
+
},
|
|
4053
|
+
{
|
|
4054
|
+
name: "keep-alive",
|
|
4055
|
+
path: "keep-alive",
|
|
4056
|
+
read: (body) => {
|
|
4057
|
+
const session = bodyString(body, "session");
|
|
4058
|
+
return session === void 0 ? { error: "the keep-alive body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.keepAlive(session) };
|
|
4059
|
+
}
|
|
4060
|
+
},
|
|
4061
|
+
{
|
|
4062
|
+
name: "mcp-status",
|
|
4063
|
+
path: "mcp-status",
|
|
4064
|
+
read: (body) => {
|
|
4065
|
+
const session = bodyString(body, "session");
|
|
4066
|
+
return session === void 0 ? { error: "the mcp-status body must be JSON naming a non-empty session id" } : { session, run: async () => await deps.mcpStatus(session) };
|
|
4067
|
+
}
|
|
4068
|
+
},
|
|
4069
|
+
{
|
|
4070
|
+
name: "mcp-reconnect",
|
|
4071
|
+
path: "mcp-reconnect",
|
|
4072
|
+
read: (body) => {
|
|
4073
|
+
const session = bodyString(body, "session");
|
|
4074
|
+
const serverName = bodyString(body, "serverName");
|
|
4075
|
+
return session === void 0 || serverName === void 0 ? { error: "the mcp-reconnect body must be JSON naming a non-empty session id and server name" } : { session, run: async () => await deps.mcpReconnect(session, serverName) };
|
|
4076
|
+
}
|
|
4077
|
+
},
|
|
4078
|
+
{
|
|
4079
|
+
name: "mcp-authenticate",
|
|
4080
|
+
path: "mcp-authenticate",
|
|
4081
|
+
read: (body) => {
|
|
4082
|
+
const session = bodyString(body, "session");
|
|
4083
|
+
const serverName = bodyString(body, "serverName");
|
|
4084
|
+
const redirectUri = bodyString(body, "redirectUri");
|
|
4085
|
+
return session === void 0 || serverName === void 0 || redirectUri === void 0 ? { error: "the mcp-authenticate body must be JSON naming a non-empty session id, server name, and redirect URI" } : { session, run: async () => await deps.mcpAuthenticate(session, serverName, redirectUri) };
|
|
4086
|
+
}
|
|
4087
|
+
},
|
|
4088
|
+
{
|
|
4089
|
+
name: "mcp-oauth-callback-url",
|
|
4090
|
+
path: "mcp-oauth-callback-url",
|
|
4091
|
+
read: (body) => {
|
|
4092
|
+
const session = bodyString(body, "session");
|
|
4093
|
+
const serverName = bodyString(body, "serverName");
|
|
4094
|
+
const callbackUrl = bodyString(body, "callbackUrl");
|
|
4095
|
+
return session === void 0 || serverName === void 0 || callbackUrl === void 0 ? { error: "the mcp-oauth-callback-url body must be JSON naming a non-empty session id, server name, and callback URL" } : { session, run: async () => await deps.mcpOAuthCallbackUrl(session, serverName, callbackUrl) };
|
|
4096
|
+
}
|
|
4097
|
+
},
|
|
4098
|
+
{
|
|
4099
|
+
name: "teleport",
|
|
4100
|
+
path: "teleport",
|
|
4101
|
+
read: (body) => {
|
|
4102
|
+
const session = bodyString(body, "session");
|
|
4103
|
+
const marker = bodyString(body, "marker");
|
|
4104
|
+
return session === void 0 || marker === void 0 ? { error: "the teleport body must be JSON naming a non-empty session id and marker" } : { session, run: async () => await deps.teleport(session, marker) };
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
];
|
|
3873
4108
|
return (request4, response) => {
|
|
3874
4109
|
const presented = request4.headers.authorization;
|
|
3875
4110
|
const bearerPrefix = "bearer ".length;
|
|
@@ -3954,73 +4189,33 @@ function createRcControlHandler(deps) {
|
|
|
3954
4189
|
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3955
4190
|
return;
|
|
3956
4191
|
}
|
|
3957
|
-
|
|
3958
|
-
if (
|
|
3959
|
-
|
|
3960
|
-
return;
|
|
3961
|
-
}
|
|
3962
|
-
let parsed;
|
|
3963
|
-
try {
|
|
3964
|
-
parsed = JSON.parse(await readBody(request4));
|
|
3965
|
-
} catch {
|
|
3966
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
|
|
3967
|
-
return;
|
|
3968
|
-
}
|
|
3969
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
|
|
3970
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
|
|
3971
|
-
return;
|
|
3972
|
-
}
|
|
3973
|
-
const result = await deps.interrupt(parsed.session);
|
|
3974
|
-
if (result.ok) {
|
|
3975
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3976
|
-
return;
|
|
4192
|
+
for (const route of controlWrites) {
|
|
4193
|
+
if (url.pathname !== `${CONTROL_PATH_PREFIX}/${route.path}`) {
|
|
4194
|
+
continue;
|
|
3977
4195
|
}
|
|
3978
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3979
|
-
return;
|
|
3980
|
-
}
|
|
3981
|
-
if (url.pathname === setModelPath) {
|
|
3982
4196
|
if (request4.method !== "POST") {
|
|
3983
|
-
answerJson(response, HTTP_STATUS.methodNotAllowed, { error:
|
|
4197
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: `the ${route.name} operation is a POST` });
|
|
3984
4198
|
return;
|
|
3985
4199
|
}
|
|
3986
4200
|
let parsed;
|
|
3987
4201
|
try {
|
|
3988
4202
|
parsed = JSON.parse(await readBody(request4));
|
|
3989
4203
|
} catch {
|
|
3990
|
-
answerJson(response, HTTP_STATUS.badRequest, { error:
|
|
3991
|
-
return;
|
|
3992
|
-
}
|
|
3993
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
|
|
3994
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
|
|
3995
|
-
return;
|
|
3996
|
-
}
|
|
3997
|
-
const result = await deps.setModel(parsed.session, parsed.model);
|
|
3998
|
-
if (result.ok) {
|
|
3999
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
|
|
4000
|
-
return;
|
|
4001
|
-
}
|
|
4002
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
if (url.pathname === setPermissionModePath) {
|
|
4006
|
-
if (request4.method !== "POST") {
|
|
4007
|
-
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
|
|
4204
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body is not readable JSON within the control body cap` });
|
|
4008
4205
|
return;
|
|
4009
4206
|
}
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
parsed = JSON.parse(await readBody(request4));
|
|
4013
|
-
} catch {
|
|
4014
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
|
|
4207
|
+
if (!isJsonObject(parsed)) {
|
|
4208
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: `the ${route.name} body must be a JSON object` });
|
|
4015
4209
|
return;
|
|
4016
4210
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4211
|
+
const read = route.read(parsed);
|
|
4212
|
+
if ("error" in read) {
|
|
4213
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: read.error });
|
|
4019
4214
|
return;
|
|
4020
4215
|
}
|
|
4021
|
-
const result = await
|
|
4216
|
+
const result = await read.run();
|
|
4022
4217
|
if (result.ok) {
|
|
4023
|
-
answerJson(response, HTTP_STATUS.ok, { session:
|
|
4218
|
+
answerJson(response, HTTP_STATUS.ok, { session: read.session, ...result.requestId === void 0 ? {} : { request: result.requestId }, sequenceNums: result.sequenceNums });
|
|
4024
4219
|
return;
|
|
4025
4220
|
}
|
|
4026
4221
|
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
@@ -4138,6 +4333,19 @@ function sequenceNumsOfBody(body) {
|
|
|
4138
4333
|
}
|
|
4139
4334
|
return sequenceNums;
|
|
4140
4335
|
}
|
|
4336
|
+
function requestIdOfBody(body) {
|
|
4337
|
+
let parsed;
|
|
4338
|
+
try {
|
|
4339
|
+
parsed = JSON.parse(body);
|
|
4340
|
+
} catch {
|
|
4341
|
+
return void 0;
|
|
4342
|
+
}
|
|
4343
|
+
if (typeof parsed !== "object" || parsed === null || !("request" in parsed)) {
|
|
4344
|
+
return void 0;
|
|
4345
|
+
}
|
|
4346
|
+
const value = parsed.request;
|
|
4347
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
4348
|
+
}
|
|
4141
4349
|
function frontDoorRcControl(transport, token) {
|
|
4142
4350
|
const headers = { authorization: `Bearer ${token}` };
|
|
4143
4351
|
const listAnswerOf = async (path32, what) => {
|
|
@@ -4226,7 +4434,8 @@ function frontDoorRcControl(transport, token) {
|
|
|
4226
4434
|
if (sequenceNums === void 0) {
|
|
4227
4435
|
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4228
4436
|
}
|
|
4229
|
-
|
|
4437
|
+
const requestId = requestIdOfBody(answer.body);
|
|
4438
|
+
return requestId === void 0 ? { ok: true, sequenceNums } : { ok: true, sequenceNums, requestId };
|
|
4230
4439
|
}
|
|
4231
4440
|
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4232
4441
|
};
|
|
@@ -4243,7 +4452,22 @@ function frontDoorRcControl(transport, token) {
|
|
|
4243
4452
|
),
|
|
4244
4453
|
interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4245
4454
|
setModel: async (sessionId, model) => await writeOf(`${CONTROL_PATH_PREFIX}/set-model`, JSON.stringify({ session: sessionId, model }), (status, message) => `setting the model on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4246
|
-
setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`)
|
|
4455
|
+
setPermissionMode: async (sessionId, mode) => await writeOf(`${CONTROL_PATH_PREFIX}/set-permission-mode`, JSON.stringify({ session: sessionId, mode }), (status, message) => `setting the permission mode on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4456
|
+
endSession: async (sessionId, reason) => await writeOf(`${CONTROL_PATH_PREFIX}/end-session`, JSON.stringify({ session: sessionId, ...reason === void 0 ? {} : { reason } }), (status, message) => `ending ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4457
|
+
getUsage: async (sessionId, skipBehaviors) => await writeOf(`${CONTROL_PATH_PREFIX}/get-usage`, JSON.stringify({ session: sessionId, ...skipBehaviors === void 0 ? {} : { skipBehaviors } }), (status, message) => `requesting the usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4458
|
+
getContextUsage: async (sessionId, detail) => await writeOf(`${CONTROL_PATH_PREFIX}/get-context-usage`, JSON.stringify({ session: sessionId, ...detail === void 0 ? {} : { detail } }), (status, message) => `requesting the context usage of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4459
|
+
readFile: async (sessionId, path32, options) => await writeOf(
|
|
4460
|
+
`${CONTROL_PATH_PREFIX}/read-file`,
|
|
4461
|
+
JSON.stringify({ session: sessionId, path: path32, ...options?.maxBytes === void 0 ? {} : { maxBytes: options.maxBytes }, ...options?.encoding === void 0 ? {} : { encoding: options.encoding } }),
|
|
4462
|
+
(status, message) => `reading ${path32} from ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4463
|
+
),
|
|
4464
|
+
fileSuggestions: async (sessionId, query) => await writeOf(`${CONTROL_PATH_PREFIX}/file-suggestions`, JSON.stringify({ session: sessionId, query }), (status, message) => `requesting file suggestions from ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4465
|
+
keepAlive: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/keep-alive`, JSON.stringify({ session: sessionId }), (status, message) => `keeping ${sessionId} alive failed (HTTP ${String(status)}): ${message}`),
|
|
4466
|
+
mcpStatus: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-status`, JSON.stringify({ session: sessionId }), (status, message) => `requesting the MCP status of ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4467
|
+
mcpReconnect: async (sessionId, serverName) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-reconnect`, JSON.stringify({ session: sessionId, serverName }), (status, message) => `reconnecting MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4468
|
+
mcpAuthenticate: async (sessionId, serverName, redirectUri) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-authenticate`, JSON.stringify({ session: sessionId, serverName, redirectUri }), (status, message) => `authenticating MCP server ${serverName} on ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4469
|
+
mcpOAuthCallbackUrl: async (sessionId, serverName, callbackUrl) => await writeOf(`${CONTROL_PATH_PREFIX}/mcp-oauth-callback-url`, JSON.stringify({ session: sessionId, serverName, callbackUrl }), (status, message) => `handing MCP server ${serverName}'s OAuth callback to ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4470
|
+
teleport: async (sessionId, marker) => await writeOf(`${CONTROL_PATH_PREFIX}/teleport`, JSON.stringify({ session: sessionId, marker }), (status, message) => `teleporting a marker into ${sessionId} failed (HTTP ${String(status)}): ${message}`)
|
|
4247
4471
|
};
|
|
4248
4472
|
}
|
|
4249
4473
|
|
|
@@ -4390,36 +4614,38 @@ function tlsFacts(options) {
|
|
|
4390
4614
|
}
|
|
4391
4615
|
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
4392
4616
|
const agent = new ExemptTlsAgent();
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
const
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
chunks.push(chunk);
|
|
4415
|
-
});
|
|
4416
|
-
response.on("end", () => {
|
|
4417
|
-
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
4418
|
-
});
|
|
4617
|
+
const post = async (path32, headers, body) => await new Promise((resolve, reject) => {
|
|
4618
|
+
const options = {
|
|
4619
|
+
host: target.host,
|
|
4620
|
+
port: target.port,
|
|
4621
|
+
method: "POST",
|
|
4622
|
+
path: path32,
|
|
4623
|
+
// 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.
|
|
4624
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
4625
|
+
agent
|
|
4626
|
+
};
|
|
4627
|
+
options.servername = target.servername ?? target.host;
|
|
4628
|
+
if (target.ca !== void 0) {
|
|
4629
|
+
options.ca = [...target.ca];
|
|
4630
|
+
}
|
|
4631
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4632
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4633
|
+
}
|
|
4634
|
+
const request4 = https2.request(options, (response) => {
|
|
4635
|
+
const chunks = [];
|
|
4636
|
+
response.on("data", (chunk) => {
|
|
4637
|
+
chunks.push(chunk);
|
|
4419
4638
|
});
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4639
|
+
response.on("end", () => {
|
|
4640
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
4641
|
+
});
|
|
4642
|
+
});
|
|
4643
|
+
request4.once("error", reject);
|
|
4644
|
+
request4.end(body);
|
|
4645
|
+
});
|
|
4646
|
+
return {
|
|
4647
|
+
writeEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/events`, headers, body),
|
|
4648
|
+
writeTeleportEvents: async (sessionId, headers, body) => await post(`/v1/code/sessions/${encodeURIComponent(sessionId)}/teleport-events`, headers, body)
|
|
4423
4649
|
};
|
|
4424
4650
|
}
|
|
4425
4651
|
var NO_CHUNKS = {
|
|
@@ -4437,7 +4663,8 @@ function lateRcEventDial(resolve) {
|
|
|
4437
4663
|
return built.dial;
|
|
4438
4664
|
};
|
|
4439
4665
|
return {
|
|
4440
|
-
writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body)
|
|
4666
|
+
writeEvents: async (sessionId, headers, body) => await dialFor().writeEvents(sessionId, headers, body),
|
|
4667
|
+
writeTeleportEvents: async (sessionId, headers, body) => await dialFor().writeTeleportEvents(sessionId, headers, body)
|
|
4441
4668
|
};
|
|
4442
4669
|
}
|
|
4443
4670
|
function lateRcStreamDial(resolve) {
|
|
@@ -4450,7 +4677,8 @@ function lateRcStreamDial(resolve) {
|
|
|
4450
4677
|
return built.dial;
|
|
4451
4678
|
};
|
|
4452
4679
|
return {
|
|
4453
|
-
announcePresence: async (sessionId, headers, clientId) => await dialFor().announcePresence(sessionId, headers, clientId),
|
|
4680
|
+
announcePresence: async (sessionId, headers, clientId, clear) => await dialFor().announcePresence(sessionId, headers, clientId, clear),
|
|
4681
|
+
markRead: async (sessionId, headers, eventId) => await dialFor().markRead(sessionId, headers, eventId),
|
|
4454
4682
|
openStream: async (sessionId, headers, resume, signal) => await dialFor().openStream(sessionId, headers, resume, signal)
|
|
4455
4683
|
};
|
|
4456
4684
|
}
|
|
@@ -4492,11 +4720,16 @@ function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_P
|
|
|
4492
4720
|
return Buffer.concat(chunks).toString("utf8");
|
|
4493
4721
|
};
|
|
4494
4722
|
return {
|
|
4495
|
-
announcePresence: async (sessionId, headers, clientId) => {
|
|
4496
|
-
const body = JSON.stringify({ client_id: clientId, clear
|
|
4723
|
+
announcePresence: async (sessionId, headers, clientId, clear) => {
|
|
4724
|
+
const body = JSON.stringify({ client_id: clientId, clear });
|
|
4497
4725
|
const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
|
|
4498
4726
|
return { status, body: await readAll(response) };
|
|
4499
4727
|
},
|
|
4728
|
+
markRead: async (sessionId, headers, eventId) => {
|
|
4729
|
+
const body = JSON.stringify(buildRcMarkReadBody(eventId));
|
|
4730
|
+
const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/mark_read`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
|
|
4731
|
+
return { status, body: await readAll(response) };
|
|
4732
|
+
},
|
|
4500
4733
|
openStream: async (sessionId, headers, resume, signal) => {
|
|
4501
4734
|
const path32 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
|
|
4502
4735
|
const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
|
|
@@ -4865,6 +5098,52 @@ var RcSetPermissionModeInputSchema = z4.strictObject({
|
|
|
4865
5098
|
session: z4.string().min(1),
|
|
4866
5099
|
mode: RcPermissionModeSchema
|
|
4867
5100
|
});
|
|
5101
|
+
var RcEndSessionInputSchema = z4.strictObject({
|
|
5102
|
+
session: z4.string().min(1),
|
|
5103
|
+
reason: z4.string().min(1).optional()
|
|
5104
|
+
});
|
|
5105
|
+
var RcGetUsageInputSchema = z4.strictObject({
|
|
5106
|
+
session: z4.string().min(1),
|
|
5107
|
+
skipBehaviors: z4.boolean().optional()
|
|
5108
|
+
});
|
|
5109
|
+
var RcGetContextUsageInputSchema = z4.strictObject({
|
|
5110
|
+
session: z4.string().min(1),
|
|
5111
|
+
detail: z4.enum(RC_CONTEXT_USAGE_DETAILS).optional()
|
|
5112
|
+
});
|
|
5113
|
+
var RcReadFileInputSchema = z4.strictObject({
|
|
5114
|
+
session: z4.string().min(1),
|
|
5115
|
+
path: z4.string().min(1),
|
|
5116
|
+
maxBytes: z4.number().int().positive().optional(),
|
|
5117
|
+
encoding: z4.enum(RC_READ_FILE_ENCODINGS).optional()
|
|
5118
|
+
});
|
|
5119
|
+
var RcFileSuggestionsInputSchema = z4.strictObject({
|
|
5120
|
+
session: z4.string().min(1),
|
|
5121
|
+
query: z4.string()
|
|
5122
|
+
});
|
|
5123
|
+
var RcKeepAliveInputSchema = z4.strictObject({
|
|
5124
|
+
session: z4.string().min(1)
|
|
5125
|
+
});
|
|
5126
|
+
var RcMcpStatusInputSchema = z4.strictObject({
|
|
5127
|
+
session: z4.string().min(1)
|
|
5128
|
+
});
|
|
5129
|
+
var RcMcpReconnectInputSchema = z4.strictObject({
|
|
5130
|
+
session: z4.string().min(1),
|
|
5131
|
+
serverName: z4.string().min(1)
|
|
5132
|
+
});
|
|
5133
|
+
var RcMcpAuthenticateInputSchema = z4.strictObject({
|
|
5134
|
+
session: z4.string().min(1),
|
|
5135
|
+
serverName: z4.string().min(1),
|
|
5136
|
+
redirectUri: z4.string().min(1)
|
|
5137
|
+
});
|
|
5138
|
+
var RcMcpOAuthCallbackUrlInputSchema = z4.strictObject({
|
|
5139
|
+
session: z4.string().min(1),
|
|
5140
|
+
serverName: z4.string().min(1),
|
|
5141
|
+
callbackUrl: z4.string().min(1)
|
|
5142
|
+
});
|
|
5143
|
+
var RcTeleportInputSchema = z4.strictObject({
|
|
5144
|
+
session: z4.string().min(1),
|
|
5145
|
+
marker: z4.string().min(1)
|
|
5146
|
+
});
|
|
4868
5147
|
var RcListOutputSchema = z4.strictObject({ sessions: z4.readonly(z4.array(RcSessionSummarySchema)) });
|
|
4869
5148
|
var RcStatusOutputSchema = z4.strictObject({ statuses: z4.readonly(z4.array(RcSessionStatusSchema)) });
|
|
4870
5149
|
var RcPendingOutputSchema = z4.strictObject({ pending: z4.readonly(z4.array(RcPendingRequestSummarySchema)) });
|
|
@@ -4872,6 +5151,11 @@ var RcWriteOutputSchema = z4.strictObject({
|
|
|
4872
5151
|
session: z4.string(),
|
|
4873
5152
|
sequenceNums: z4.readonly(z4.array(z4.number()))
|
|
4874
5153
|
});
|
|
5154
|
+
var RcControlWriteOutputSchema = z4.strictObject({
|
|
5155
|
+
session: z4.string(),
|
|
5156
|
+
request: z4.string(),
|
|
5157
|
+
sequenceNums: z4.readonly(z4.array(z4.number()))
|
|
5158
|
+
});
|
|
4875
5159
|
var RcAnswerOutputSchema = z4.strictObject({
|
|
4876
5160
|
session: z4.string(),
|
|
4877
5161
|
request: z4.string(),
|
|
@@ -4900,6 +5184,15 @@ function createRcApiRouter(deps) {
|
|
|
4900
5184
|
}
|
|
4901
5185
|
return result.sequenceNums;
|
|
4902
5186
|
};
|
|
5187
|
+
const deliveredControl = (sessionId, result) => {
|
|
5188
|
+
if (!result.ok) {
|
|
5189
|
+
throw new ORPCError("BAD_GATEWAY", { message: result.message });
|
|
5190
|
+
}
|
|
5191
|
+
if (result.requestId === void 0) {
|
|
5192
|
+
throw new ORPCError("INTERNAL_SERVER_ERROR", { message: "the control request was delivered but the door cannot name the request id it minted, so the worker's answer cannot be matched" });
|
|
5193
|
+
}
|
|
5194
|
+
return { request: result.requestId, sequenceNums: result.sequenceNums };
|
|
5195
|
+
};
|
|
4903
5196
|
return {
|
|
4904
5197
|
rc: {
|
|
4905
5198
|
list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
|
|
@@ -4922,9 +5215,20 @@ function createRcApiRouter(deps) {
|
|
|
4922
5215
|
const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
|
|
4923
5216
|
return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
|
|
4924
5217
|
}),
|
|
4925
|
-
interrupt: authed.input(RcInterruptInputSchema).output(
|
|
4926
|
-
setModel: authed.input(RcSetModelInputSchema).output(
|
|
4927
|
-
setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(
|
|
5218
|
+
interrupt: authed.input(RcInterruptInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.interrupt(input.session)) })),
|
|
5219
|
+
setModel: authed.input(RcSetModelInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setModel(input.session, input.model)) })),
|
|
5220
|
+
setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
|
|
5221
|
+
endSession: authed.input(RcEndSessionInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.endSession(input.session, input.reason)) })),
|
|
5222
|
+
getUsage: authed.input(RcGetUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getUsage(input.session, input.skipBehaviors)) })),
|
|
5223
|
+
getContextUsage: authed.input(RcGetContextUsageInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.getContextUsage(input.session, input.detail)) })),
|
|
5224
|
+
readFile: authed.input(RcReadFileInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.readFile(input.session, input.path, { ...input.maxBytes === void 0 ? {} : { maxBytes: input.maxBytes }, ...input.encoding === void 0 ? {} : { encoding: input.encoding } })) })),
|
|
5225
|
+
fileSuggestions: authed.input(RcFileSuggestionsInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.fileSuggestions(input.session, input.query)) })),
|
|
5226
|
+
keepAlive: authed.input(RcKeepAliveInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.keepAlive(input.session)) })),
|
|
5227
|
+
mcpStatus: authed.input(RcMcpStatusInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpStatus(input.session)) })),
|
|
5228
|
+
mcpReconnect: authed.input(RcMcpReconnectInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpReconnect(input.session, input.serverName)) })),
|
|
5229
|
+
mcpAuthenticate: authed.input(RcMcpAuthenticateInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpAuthenticate(input.session, input.serverName, input.redirectUri)) })),
|
|
5230
|
+
mcpOAuthCallbackUrl: authed.input(RcMcpOAuthCallbackUrlInputSchema).output(RcControlWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, ...deliveredControl(input.session, await deps.mcpOAuthCallbackUrl(input.session, input.serverName, input.callbackUrl)) })),
|
|
5231
|
+
teleport: authed.input(RcTeleportInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.teleport(input.session, input.marker)) })),
|
|
4928
5232
|
subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
|
|
4929
5233
|
const queue = [];
|
|
4930
5234
|
let wake;
|
|
@@ -9881,9 +10185,26 @@ function createRcStreamHub(deps) {
|
|
|
9881
10185
|
}
|
|
9882
10186
|
return headers;
|
|
9883
10187
|
};
|
|
10188
|
+
let receiptFailureLogged = false;
|
|
10189
|
+
const sendReceipt = (attachment, headers, eventId) => {
|
|
10190
|
+
if (eventId === void 0) {
|
|
10191
|
+
return;
|
|
10192
|
+
}
|
|
10193
|
+
deps.dial.markRead(attachment.sessionId, headers, eventId).then(
|
|
10194
|
+
() => {
|
|
10195
|
+
receiptFailureLogged = false;
|
|
10196
|
+
},
|
|
10197
|
+
(error) => {
|
|
10198
|
+
if (!receiptFailureLogged) {
|
|
10199
|
+
receiptFailureLogged = true;
|
|
10200
|
+
deps.log?.(`rc stream ${attachment.sessionId}: the read receipt for event ${eventId} could not be sent (${error instanceof Error ? error.message : String(error)}); further receipt failures stay quiet until one lands`);
|
|
10201
|
+
}
|
|
10202
|
+
}
|
|
10203
|
+
);
|
|
10204
|
+
};
|
|
9884
10205
|
const attempt = async (attachment, credential) => {
|
|
9885
10206
|
const headers = streamHeaders(credential);
|
|
9886
|
-
const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
|
|
10207
|
+
const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, false);
|
|
9887
10208
|
if (presence.status === HTTP_STATUS.unauthorized) {
|
|
9888
10209
|
return "unauthorized";
|
|
9889
10210
|
}
|
|
@@ -9903,6 +10224,7 @@ function createRcStreamHub(deps) {
|
|
|
9903
10224
|
const parser = createSseParser();
|
|
9904
10225
|
for await (const chunk of answer.chunks) {
|
|
9905
10226
|
attachment.controller.signal.throwIfAborted();
|
|
10227
|
+
let lastEventId;
|
|
9906
10228
|
for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
|
|
9907
10229
|
const envelope = parseRcStreamEnvelope(parsed);
|
|
9908
10230
|
if (envelope === void 0) {
|
|
@@ -9910,10 +10232,21 @@ function createRcStreamHub(deps) {
|
|
|
9910
10232
|
}
|
|
9911
10233
|
deps.fileStreamEvent(attachment.sessionId, envelope);
|
|
9912
10234
|
deps.fanout.publish({ session: attachment.sessionId, envelope });
|
|
10235
|
+
if (typeof envelope.event_id === "string" && envelope.event_type !== "mark_read") {
|
|
10236
|
+
lastEventId = envelope.event_id;
|
|
10237
|
+
}
|
|
9913
10238
|
}
|
|
10239
|
+
sendReceipt(attachment, headers, lastEventId);
|
|
9914
10240
|
}
|
|
9915
10241
|
return "dropped";
|
|
9916
10242
|
};
|
|
10243
|
+
const clearPresence = (attachment, headers) => {
|
|
10244
|
+
if (headers === void 0) {
|
|
10245
|
+
return;
|
|
10246
|
+
}
|
|
10247
|
+
void deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId, true).catch(() => {
|
|
10248
|
+
});
|
|
10249
|
+
};
|
|
9917
10250
|
const runLoop = async (attachment) => {
|
|
9918
10251
|
let retriedUnauthorized = false;
|
|
9919
10252
|
for (; ; ) {
|
|
@@ -9925,6 +10258,7 @@ function createRcStreamHub(deps) {
|
|
|
9925
10258
|
if (!live || credential?.authorization === void 0) {
|
|
9926
10259
|
deps.log?.(`rc stream ${attachment.sessionId}: the attachment ended (${live ? "the session's client credential is no longer known" : "the tracker gave up on the session"})`);
|
|
9927
10260
|
persistCursor(attachment.sessionId);
|
|
10261
|
+
clearPresence(attachment, credential === void 0 ? void 0 : streamHeaders(credential));
|
|
9928
10262
|
if (attachments.get(attachment.sessionId) === attachment) {
|
|
9929
10263
|
attachments.delete(attachment.sessionId);
|
|
9930
10264
|
}
|
|
@@ -9987,6 +10321,8 @@ function createRcStreamHub(deps) {
|
|
|
9987
10321
|
close: () => {
|
|
9988
10322
|
for (const attachment of attachments.values()) {
|
|
9989
10323
|
persistCursor(attachment.sessionId);
|
|
10324
|
+
const credential = deps.credentialOf(attachment.sessionId);
|
|
10325
|
+
clearPresence(attachment, credential?.authorization === void 0 ? void 0 : streamHeaders(credential));
|
|
9990
10326
|
attachment.stop();
|
|
9991
10327
|
}
|
|
9992
10328
|
attachments.clear();
|
|
@@ -9994,6 +10330,193 @@ function createRcStreamHub(deps) {
|
|
|
9994
10330
|
};
|
|
9995
10331
|
}
|
|
9996
10332
|
|
|
10333
|
+
// src/frontdoor/rcSelfHostEndpoints.ts
|
|
10334
|
+
var RC_SOURCE_WORKER = "worker";
|
|
10335
|
+
var RC_SOURCE_CLIENT = "client";
|
|
10336
|
+
function isRecord6(value) {
|
|
10337
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10338
|
+
}
|
|
10339
|
+
function parseJsonObject2(body) {
|
|
10340
|
+
let parsed;
|
|
10341
|
+
try {
|
|
10342
|
+
parsed = JSON.parse(body);
|
|
10343
|
+
} catch {
|
|
10344
|
+
return void 0;
|
|
10345
|
+
}
|
|
10346
|
+
return isRecord6(parsed) ? parsed : void 0;
|
|
10347
|
+
}
|
|
10348
|
+
function nonEmptyString2(record, field) {
|
|
10349
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
10350
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10351
|
+
}
|
|
10352
|
+
function numberOf(record, field) {
|
|
10353
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
10354
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
10355
|
+
return value;
|
|
10356
|
+
}
|
|
10357
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) {
|
|
10358
|
+
return Number(value);
|
|
10359
|
+
}
|
|
10360
|
+
return void 0;
|
|
10361
|
+
}
|
|
10362
|
+
function pageOfRcChannel(url, rows, idOf, rowOf) {
|
|
10363
|
+
const query = new URL(url, "http://127.0.0.1").searchParams;
|
|
10364
|
+
const anchor = query.get("after_event_id") ?? query.get("cursor");
|
|
10365
|
+
const rawLimit = query.get("limit");
|
|
10366
|
+
const limit = rawLimit === null ? void 0 : numberOf({ value: rawLimit }, "value");
|
|
10367
|
+
if (limit !== void 0 && limit < 1) {
|
|
10368
|
+
return { error: "the limit names a whole number of rows of at least one" };
|
|
10369
|
+
}
|
|
10370
|
+
const from = anchor === null ? 0 : rows.findIndex((row) => idOf(row) === anchor) + 1;
|
|
10371
|
+
if (anchor !== null && from === 0) {
|
|
10372
|
+
return { error: `the anchor names an event id this surface holds no row for: ${anchor}` };
|
|
10373
|
+
}
|
|
10374
|
+
const page = limit === void 0 ? rows.slice(from) : rows.slice(from, from + limit);
|
|
10375
|
+
const last = page[page.length - 1];
|
|
10376
|
+
return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
|
|
10377
|
+
}
|
|
10378
|
+
async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
10379
|
+
const { request: request4, response, session } = ctx;
|
|
10380
|
+
if (tail === "client/presence" && method === "POST") {
|
|
10381
|
+
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10382
|
+
const clientId = nonEmptyString2(body, "client_id");
|
|
10383
|
+
const clear = body?.clear;
|
|
10384
|
+
if (clientId === void 0 || clear !== void 0 && typeof clear !== "boolean") {
|
|
10385
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a presence announcement is JSON naming a non-empty client_id, with clear only as a boolean or absent" } });
|
|
10386
|
+
return true;
|
|
10387
|
+
}
|
|
10388
|
+
if (clear === true) {
|
|
10389
|
+
session.clients.delete(clientId);
|
|
10390
|
+
ctx.log?.(`rc selfhost ${session.id}: client ${clientId} cleared its presence`);
|
|
10391
|
+
} else {
|
|
10392
|
+
session.clients.set(clientId, ctx.now());
|
|
10393
|
+
ctx.log?.(`rc selfhost ${session.id}: client ${clientId} announced its presence${session.clients.size === 1 ? "" : ` (${String(session.clients.size)} clients now announced)`}`);
|
|
10394
|
+
}
|
|
10395
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
|
|
10396
|
+
return true;
|
|
10397
|
+
}
|
|
10398
|
+
if (tail === "mark_read" && method === "POST") {
|
|
10399
|
+
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10400
|
+
const eventId = nonEmptyString2(body, "event_id");
|
|
10401
|
+
const clientId = nonEmptyString2(body, "client_id");
|
|
10402
|
+
if (body !== void 0 && "event_id" in body && eventId === void 0 || "client_id" in (body ?? {}) && clientId === void 0) {
|
|
10403
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a read receipt is JSON naming the event_id it read or none for the whole session, and client_id only as a non-empty string or absent" } });
|
|
10404
|
+
return true;
|
|
10405
|
+
}
|
|
10406
|
+
const receipt = ctx.publish(session, clientId ?? RC_SOURCE_CLIENT, { type: "mark_read", ...eventId === void 0 ? {} : { event_id: eventId }, ...clientId === void 0 ? {} : { client_id: clientId } }, true);
|
|
10407
|
+
ctx.log?.(`rc selfhost ${session.id}: ${clientId === void 0 ? "an unnamed client" : `client ${clientId}`} marked ${eventId === void 0 ? "the session read" : `event ${eventId} read`} as sequence ${String(receipt.sequenceNum)}`);
|
|
10408
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10409
|
+
return true;
|
|
10410
|
+
}
|
|
10411
|
+
if (tail === "teleport-events" && method === "POST") {
|
|
10412
|
+
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10413
|
+
const events = body?.events;
|
|
10414
|
+
if (!Array.isArray(events) || events.length === 0 || events.length > maxBatchEvents) {
|
|
10415
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a teleport write is a non-empty array of at most ${String(maxBatchEvents)} events` } });
|
|
10416
|
+
return true;
|
|
10417
|
+
}
|
|
10418
|
+
const results = [];
|
|
10419
|
+
for (const event of events) {
|
|
10420
|
+
if (!isRecord6(event) || !isRecord6(event.payload)) {
|
|
10421
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a teleport write is an object carrying one payload object" } });
|
|
10422
|
+
return true;
|
|
10423
|
+
}
|
|
10424
|
+
const published = ctx.publish(session, RC_SOURCE_CLIENT, event.payload);
|
|
10425
|
+
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10426
|
+
}
|
|
10427
|
+
ctx.sweep();
|
|
10428
|
+
ctx.log?.(`rc selfhost ${session.id}: teleport write published ${String(results.length)} marker event(s)`);
|
|
10429
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10430
|
+
return true;
|
|
10431
|
+
}
|
|
10432
|
+
if (tail === "teleport-events" && method === "GET") {
|
|
10433
|
+
const page = pageOfRcChannel(request4.url, session.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
|
|
10434
|
+
if ("error" in page) {
|
|
10435
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
|
|
10436
|
+
return true;
|
|
10437
|
+
}
|
|
10438
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, page);
|
|
10439
|
+
return true;
|
|
10440
|
+
}
|
|
10441
|
+
return false;
|
|
10442
|
+
}
|
|
10443
|
+
var compatSessionRow = (session, now) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(now()).toISOString(), session_status: "active" });
|
|
10444
|
+
async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
10445
|
+
const { request: request4, response } = ctx;
|
|
10446
|
+
const method = request4.method.toUpperCase();
|
|
10447
|
+
if (pathname === compatPath) {
|
|
10448
|
+
if (method !== "GET") {
|
|
10449
|
+
await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
|
|
10450
|
+
return;
|
|
10451
|
+
}
|
|
10452
|
+
const listRefusal = ctx.credentialRefusal(request4.headers);
|
|
10453
|
+
if (listRefusal !== void 0) {
|
|
10454
|
+
await ctx.unauthorized(response, listRefusal);
|
|
10455
|
+
return;
|
|
10456
|
+
}
|
|
10457
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...ctx.sessions.values()].map((session2) => compatSessionRow(session2, ctx.now)) });
|
|
10458
|
+
return;
|
|
10459
|
+
}
|
|
10460
|
+
const rest = pathname.slice(`${compatPath}/`.length);
|
|
10461
|
+
const slash = rest.indexOf("/");
|
|
10462
|
+
const address = slash === -1 ? rest : rest.slice(0, slash);
|
|
10463
|
+
const compatTail = slash === -1 ? "" : rest.slice(slash + 1);
|
|
10464
|
+
const refusal = ctx.credentialRefusal(request4.headers);
|
|
10465
|
+
if (refusal !== void 0) {
|
|
10466
|
+
await ctx.unauthorized(response, refusal);
|
|
10467
|
+
return;
|
|
10468
|
+
}
|
|
10469
|
+
const session = ctx.sessions.get(address) ?? ctx.sessions.get(`cse_${address}`);
|
|
10470
|
+
if (compatTail === "") {
|
|
10471
|
+
if (method === "GET") {
|
|
10472
|
+
if (session === void 0) {
|
|
10473
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
|
|
10474
|
+
return;
|
|
10475
|
+
}
|
|
10476
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
if (method === "PATCH" || method === "PUT") {
|
|
10480
|
+
if (session === void 0) {
|
|
10481
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
|
|
10482
|
+
return;
|
|
10483
|
+
}
|
|
10484
|
+
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10485
|
+
const title = nonEmptyString2(body, "title");
|
|
10486
|
+
if (title === void 0) {
|
|
10487
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a compatibility session update is JSON naming a non-empty title" } });
|
|
10488
|
+
return;
|
|
10489
|
+
}
|
|
10490
|
+
session.title = title;
|
|
10491
|
+
session.lastTrafficAt = ctx.now();
|
|
10492
|
+
ctx.log?.(`rc selfhost ${session.id}: retitled through the compatibility family`);
|
|
10493
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10494
|
+
return;
|
|
10495
|
+
}
|
|
10496
|
+
await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session read is a GET and its update a PATCH or PUT" } });
|
|
10497
|
+
return;
|
|
10498
|
+
}
|
|
10499
|
+
if ((compatTail === "archive" || compatTail === "unarchive") && method === "POST") {
|
|
10500
|
+
await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES);
|
|
10501
|
+
if (session === void 0) {
|
|
10502
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `Session not found: ${address}` } });
|
|
10503
|
+
return;
|
|
10504
|
+
}
|
|
10505
|
+
if (compatTail === "unarchive") {
|
|
10506
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10507
|
+
return;
|
|
10508
|
+
}
|
|
10509
|
+
for (const sink of [...session.workerStreams, ...session.clientStreams]) {
|
|
10510
|
+
sink.retire();
|
|
10511
|
+
}
|
|
10512
|
+
ctx.sessions.delete(session.id);
|
|
10513
|
+
ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
|
|
10514
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10515
|
+
return;
|
|
10516
|
+
}
|
|
10517
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
|
|
10518
|
+
}
|
|
10519
|
+
|
|
9997
10520
|
// src/frontdoor/rcWebFetch.ts
|
|
9998
10521
|
import * as dns2 from "node:dns";
|
|
9999
10522
|
import * as http2 from "node:http";
|
|
@@ -10262,21 +10785,19 @@ var RC_PRESENCE_REFRESH_SECONDS = 60;
|
|
|
10262
10785
|
var RC_SELF_HOST_MAX_BATCH_EVENTS = 100;
|
|
10263
10786
|
var RC_SELF_HOST_WORKER_EPOCH = 1;
|
|
10264
10787
|
var RC_STREAM_EVENT_NAME2 = "client_event";
|
|
10265
|
-
var RC_SOURCE_WORKER = "worker";
|
|
10266
|
-
var RC_SOURCE_CLIENT = "client";
|
|
10267
10788
|
var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
|
|
10268
10789
|
var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
|
|
10269
|
-
function
|
|
10790
|
+
function isRecord7(value) {
|
|
10270
10791
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10271
10792
|
}
|
|
10272
10793
|
function isWebSearchRefusal(value) {
|
|
10273
10794
|
return !Array.isArray(value);
|
|
10274
10795
|
}
|
|
10275
|
-
function
|
|
10796
|
+
function nonEmptyString3(record, field) {
|
|
10276
10797
|
const value = record === void 0 ? void 0 : record[field];
|
|
10277
10798
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10278
10799
|
}
|
|
10279
|
-
function
|
|
10800
|
+
function numberOf2(record, field) {
|
|
10280
10801
|
const value = record === void 0 ? void 0 : record[field];
|
|
10281
10802
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
10282
10803
|
return value;
|
|
@@ -10343,14 +10864,14 @@ async function readBody2(request4, capBytes) {
|
|
|
10343
10864
|
});
|
|
10344
10865
|
});
|
|
10345
10866
|
}
|
|
10346
|
-
function
|
|
10867
|
+
function parseJsonObject3(body) {
|
|
10347
10868
|
let parsed;
|
|
10348
10869
|
try {
|
|
10349
10870
|
parsed = JSON.parse(body);
|
|
10350
10871
|
} catch {
|
|
10351
10872
|
return void 0;
|
|
10352
10873
|
}
|
|
10353
|
-
return
|
|
10874
|
+
return isRecord7(parsed) ? parsed : void 0;
|
|
10354
10875
|
}
|
|
10355
10876
|
function mintWorkerJwt(randomToken, issuedAt) {
|
|
10356
10877
|
const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
@@ -10373,11 +10894,16 @@ function createRcSelfHostSurface(deps) {
|
|
|
10373
10894
|
for (const session of sessions.values()) {
|
|
10374
10895
|
while (session.events[0] !== void 0 && now - session.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
|
|
10375
10896
|
const dropped = session.events.shift();
|
|
10376
|
-
const uuid = dropped === void 0 ? void 0 :
|
|
10897
|
+
const uuid = dropped === void 0 ? void 0 : nonEmptyString3(dropped.payload, "uuid");
|
|
10377
10898
|
if (uuid !== void 0) {
|
|
10378
10899
|
session.seenPayloadUuids.delete(uuid);
|
|
10379
10900
|
}
|
|
10380
10901
|
}
|
|
10902
|
+
for (const [clientId, lastSeenAt] of session.clients) {
|
|
10903
|
+
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
10904
|
+
session.clients.delete(clientId);
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10381
10907
|
if (session.workerStreams.size === 0 && session.clientStreams.size === 0 && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
|
|
10382
10908
|
sessions.delete(session.id);
|
|
10383
10909
|
}
|
|
@@ -10416,7 +10942,7 @@ function createRcSelfHostSurface(deps) {
|
|
|
10416
10942
|
const frameOf = (event) => {
|
|
10417
10943
|
const envelope = {
|
|
10418
10944
|
event_id: event.eventId,
|
|
10419
|
-
event_type:
|
|
10945
|
+
event_type: nonEmptyString3(event.payload, "type") ?? "event",
|
|
10420
10946
|
sequence_num: event.sequenceNum,
|
|
10421
10947
|
source: event.source,
|
|
10422
10948
|
payload: event.payload,
|
|
@@ -10430,7 +10956,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10430
10956
|
};
|
|
10431
10957
|
const holdStream = async (session, which, wantsReplay, request4, response) => {
|
|
10432
10958
|
const url = new URL(request4.url, "http://127.0.0.1");
|
|
10433
|
-
const cursor =
|
|
10959
|
+
const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
|
|
10434
10960
|
const replay = cursor === void 0 ? [] : session.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
|
|
10435
10961
|
response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
|
|
10436
10962
|
response.flush();
|
|
@@ -10459,8 +10985,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10459
10985
|
sink.retire();
|
|
10460
10986
|
response.end();
|
|
10461
10987
|
};
|
|
10462
|
-
const publish = (session, source, payload) => {
|
|
10463
|
-
const uuid =
|
|
10988
|
+
const publish = (session, source, payload, toWorker = source === RC_SOURCE_CLIENT) => {
|
|
10989
|
+
const uuid = nonEmptyString3(payload, "uuid");
|
|
10464
10990
|
const duplicate = uuid !== void 0 && session.seenPayloadUuids.has(uuid);
|
|
10465
10991
|
session.sequenceNum += 1;
|
|
10466
10992
|
const event = {
|
|
@@ -10468,9 +10994,9 @@ data: ${JSON.stringify(envelope)}
|
|
|
10468
10994
|
eventId: uuid ?? deps.newUuid(),
|
|
10469
10995
|
source,
|
|
10470
10996
|
// The one enrichment the real service was observed making on the live rig's stream: the wall-clock instant it took the write. The worker's handler ignores it; the door's own parser forwards it verbatim.
|
|
10471
|
-
payload: source ===
|
|
10997
|
+
payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
|
|
10472
10998
|
createdAt: new Date(deps.now()).toISOString(),
|
|
10473
|
-
toWorker
|
|
10999
|
+
toWorker,
|
|
10474
11000
|
storedAt: deps.now()
|
|
10475
11001
|
};
|
|
10476
11002
|
session.events.push(event);
|
|
@@ -10518,8 +11044,26 @@ data: ${JSON.stringify(envelope)}
|
|
|
10518
11044
|
}
|
|
10519
11045
|
session.lastTrafficAt = deps.now();
|
|
10520
11046
|
if (tail === "") {
|
|
11047
|
+
if (method === "PATCH" || method === "PUT") {
|
|
11048
|
+
const refusal = credentialRefusal(request4.headers);
|
|
11049
|
+
if (refusal !== void 0) {
|
|
11050
|
+
await unauthorized(response, refusal);
|
|
11051
|
+
return;
|
|
11052
|
+
}
|
|
11053
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11054
|
+
const title = nonEmptyString3(body, "title");
|
|
11055
|
+
if (title === void 0) {
|
|
11056
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
|
|
11057
|
+
return;
|
|
11058
|
+
}
|
|
11059
|
+
session.title = title;
|
|
11060
|
+
session.lastTrafficAt = deps.now();
|
|
11061
|
+
deps.log?.(`rc selfhost ${session.id}: retitled at its client's request`);
|
|
11062
|
+
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
11063
|
+
return;
|
|
11064
|
+
}
|
|
10521
11065
|
if (method !== "GET") {
|
|
10522
|
-
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET" } });
|
|
11066
|
+
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET, and its update a PATCH or PUT" } });
|
|
10523
11067
|
return;
|
|
10524
11068
|
}
|
|
10525
11069
|
await answerJson2(response, HTTP_STATUS.ok, { session: { id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active" } });
|
|
@@ -10564,17 +11108,17 @@ data: ${JSON.stringify(envelope)}
|
|
|
10564
11108
|
return;
|
|
10565
11109
|
}
|
|
10566
11110
|
if (workerTail === "" && method === "PUT") {
|
|
10567
|
-
const body =
|
|
10568
|
-
const claimedEpoch =
|
|
11111
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11112
|
+
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
10569
11113
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
10570
11114
|
await epochStale(response);
|
|
10571
11115
|
return;
|
|
10572
11116
|
}
|
|
10573
|
-
const status =
|
|
11117
|
+
const status = nonEmptyString3(body, "worker_status");
|
|
10574
11118
|
if (status !== void 0) {
|
|
10575
11119
|
session.workerStatus = status;
|
|
10576
11120
|
}
|
|
10577
|
-
if (
|
|
11121
|
+
if (isRecord7(body?.external_metadata)) {
|
|
10578
11122
|
session.externalMetadata = body.external_metadata;
|
|
10579
11123
|
}
|
|
10580
11124
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
@@ -10591,8 +11135,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10591
11135
|
return;
|
|
10592
11136
|
}
|
|
10593
11137
|
if (workerTail === "events" && method === "POST") {
|
|
10594
|
-
const body =
|
|
10595
|
-
const claimedEpoch =
|
|
11138
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11139
|
+
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
10596
11140
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
10597
11141
|
await epochStale(response);
|
|
10598
11142
|
return;
|
|
@@ -10604,7 +11148,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10604
11148
|
}
|
|
10605
11149
|
const results = [];
|
|
10606
11150
|
for (const event of events) {
|
|
10607
|
-
if (!
|
|
11151
|
+
if (!isRecord7(event) || !isRecord7(event.payload)) {
|
|
10608
11152
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a batch is an object carrying one payload object" } });
|
|
10609
11153
|
return;
|
|
10610
11154
|
}
|
|
@@ -10625,8 +11169,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10625
11169
|
return;
|
|
10626
11170
|
}
|
|
10627
11171
|
if (workerTail === "internal-events" && method === "POST") {
|
|
10628
|
-
const body =
|
|
10629
|
-
const claimedEpoch =
|
|
11172
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11173
|
+
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
10630
11174
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
10631
11175
|
await epochStale(response);
|
|
10632
11176
|
return;
|
|
@@ -10637,19 +11181,26 @@ data: ${JSON.stringify(envelope)}
|
|
|
10637
11181
|
return;
|
|
10638
11182
|
}
|
|
10639
11183
|
for (const event of events) {
|
|
10640
|
-
if (
|
|
10641
|
-
session.internalEvents.push({
|
|
11184
|
+
if (isRecord7(event)) {
|
|
11185
|
+
session.internalEvents.push({ eventId: deps.newUuid(), body: event });
|
|
10642
11186
|
}
|
|
10643
11187
|
}
|
|
10644
11188
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
10645
11189
|
return;
|
|
10646
11190
|
}
|
|
10647
11191
|
if (workerTail === "internal-events" && method === "GET") {
|
|
10648
|
-
const
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
|
|
11192
|
+
const query = new URL(request4.url, "http://127.0.0.1").searchParams;
|
|
11193
|
+
if (query.get("session_agent_id") !== null || query.get("subagents") !== null) {
|
|
11194
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "this surface keeps one internal-events channel per session, so an agent-scoped read has no subset to serve" } });
|
|
11195
|
+
return;
|
|
11196
|
+
}
|
|
11197
|
+
const page = pageOfRcChannel(request4.url, session.internalEvents, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.body }));
|
|
11198
|
+
if ("error" in page) {
|
|
11199
|
+
const anchorUnknown = query.get("after_event_id") !== null && page.error.startsWith("the anchor names an event id");
|
|
11200
|
+
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: anchorUnknown ? "after_event_id_not_found" : "invalid_request_error", message: page.error } });
|
|
11201
|
+
return;
|
|
11202
|
+
}
|
|
11203
|
+
await answerJson2(response, HTTP_STATUS.ok, page);
|
|
10653
11204
|
return;
|
|
10654
11205
|
}
|
|
10655
11206
|
if (workerTail === "diagnostics" && method === "POST") {
|
|
@@ -10658,8 +11209,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10658
11209
|
return;
|
|
10659
11210
|
}
|
|
10660
11211
|
if (workerTail === "web-fetch" && method === "POST") {
|
|
10661
|
-
const body =
|
|
10662
|
-
const target =
|
|
11212
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11213
|
+
const target = nonEmptyString3(body, "url");
|
|
10663
11214
|
if (target === void 0) {
|
|
10664
11215
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
|
|
10665
11216
|
return;
|
|
@@ -10681,8 +11232,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10681
11232
|
return;
|
|
10682
11233
|
}
|
|
10683
11234
|
if (workerTail === "web-search" && method === "POST") {
|
|
10684
|
-
const body =
|
|
10685
|
-
const query =
|
|
11235
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11236
|
+
const query = nonEmptyString3(body, "query");
|
|
10686
11237
|
if (query === void 0) {
|
|
10687
11238
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
|
|
10688
11239
|
return;
|
|
@@ -10693,7 +11244,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10693
11244
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "the domain filters of a worker web-search are string arrays" } });
|
|
10694
11245
|
return;
|
|
10695
11246
|
}
|
|
10696
|
-
const searchProfile =
|
|
11247
|
+
const searchProfile = nonEmptyString3(body, "search_profile");
|
|
10697
11248
|
const backend = deps.webSearch;
|
|
10698
11249
|
if (backend === void 0) {
|
|
10699
11250
|
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
|
|
@@ -10718,7 +11269,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10718
11269
|
return;
|
|
10719
11270
|
}
|
|
10720
11271
|
if (tail === "events" && method === "POST") {
|
|
10721
|
-
const body =
|
|
11272
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10722
11273
|
const events = body?.events;
|
|
10723
11274
|
if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
|
|
10724
11275
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a client event write is a non-empty array of at most ${String(RC_SELF_HOST_MAX_BATCH_EVENTS)} events` } });
|
|
@@ -10726,7 +11277,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10726
11277
|
}
|
|
10727
11278
|
const results = [];
|
|
10728
11279
|
for (const event of events) {
|
|
10729
|
-
if (!
|
|
11280
|
+
if (!isRecord7(event) || !isRecord7(event.payload)) {
|
|
10730
11281
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
|
|
10731
11282
|
return;
|
|
10732
11283
|
}
|
|
@@ -10738,26 +11289,14 @@ data: ${JSON.stringify(envelope)}
|
|
|
10738
11289
|
return;
|
|
10739
11290
|
}
|
|
10740
11291
|
if (tail === "events" && method === "GET") {
|
|
10741
|
-
await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type:
|
|
11292
|
+
await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString3(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
|
|
10742
11293
|
return;
|
|
10743
11294
|
}
|
|
10744
11295
|
if (tail === "events/stream" && method === "GET") {
|
|
10745
11296
|
await holdStream(session, "clientStreams", () => true, request4, response);
|
|
10746
11297
|
return;
|
|
10747
11298
|
}
|
|
10748
|
-
if (
|
|
10749
|
-
await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
|
|
10750
|
-
await answerJson2(response, HTTP_STATUS.ok, { refresh_after_seconds: RC_PRESENCE_REFRESH_SECONDS });
|
|
10751
|
-
return;
|
|
10752
|
-
}
|
|
10753
|
-
if (tail === "mark_read" && method === "POST") {
|
|
10754
|
-
await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
|
|
10755
|
-
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
10756
|
-
return;
|
|
10757
|
-
}
|
|
10758
|
-
if (tail === "teleport-events" && method === "POST") {
|
|
10759
|
-
await readBody2(request4.body, CONTROL_BODY_CAP_BYTES);
|
|
10760
|
-
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
11299
|
+
if (await serveRcClientChannels({ request: request4, response, session, answerJson: answerJson2, readBody: readBody2, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
|
|
10761
11300
|
return;
|
|
10762
11301
|
}
|
|
10763
11302
|
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
|
|
@@ -10774,17 +11313,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
10774
11313
|
await noCredential(response);
|
|
10775
11314
|
return;
|
|
10776
11315
|
}
|
|
10777
|
-
if (pathname === RC_COMPAT_SESSIONS_PATH) {
|
|
10778
|
-
|
|
10779
|
-
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the compatibility session list is a GET" } });
|
|
10780
|
-
return;
|
|
10781
|
-
}
|
|
10782
|
-
const listRefusal = credentialRefusal(request4.headers);
|
|
10783
|
-
if (listRefusal !== void 0) {
|
|
10784
|
-
await unauthorized(response, listRefusal);
|
|
10785
|
-
return;
|
|
10786
|
-
}
|
|
10787
|
-
await answerJson2(response, HTTP_STATUS.ok, { data: [] });
|
|
11316
|
+
if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
|
|
11317
|
+
await serveRcCompatSessions({ request: request4, response, sessions, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
|
|
10788
11318
|
return;
|
|
10789
11319
|
}
|
|
10790
11320
|
if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
|
|
@@ -10798,11 +11328,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
10798
11328
|
await unauthorized(response, createRefusal);
|
|
10799
11329
|
return;
|
|
10800
11330
|
}
|
|
10801
|
-
const body =
|
|
11331
|
+
const body = parseJsonObject3(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10802
11332
|
const session = {
|
|
10803
11333
|
id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
|
|
10804
11334
|
createdAt: deps.now(),
|
|
10805
|
-
title:
|
|
11335
|
+
title: nonEmptyString3(body, "title"),
|
|
10806
11336
|
workerJwts: /* @__PURE__ */ new Set(),
|
|
10807
11337
|
sequenceNum: 0,
|
|
10808
11338
|
events: [],
|
|
@@ -10810,6 +11340,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10810
11340
|
workerStreams: /* @__PURE__ */ new Set(),
|
|
10811
11341
|
clientStreams: /* @__PURE__ */ new Set(),
|
|
10812
11342
|
internalEvents: [],
|
|
11343
|
+
clients: /* @__PURE__ */ new Map(),
|
|
10813
11344
|
workerStatus: void 0,
|
|
10814
11345
|
externalMetadata: void 0,
|
|
10815
11346
|
lastTrafficAt: deps.now()
|
|
@@ -10854,7 +11385,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
10854
11385
|
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
10855
11386
|
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
10856
11387
|
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
10857
|
-
const body =
|
|
11388
|
+
const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
|
|
10858
11389
|
const record = deps.credentialRecord();
|
|
10859
11390
|
if (record === void 0) {
|
|
10860
11391
|
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
@@ -10940,7 +11471,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
|
|
|
10940
11471
|
var RC_SELF_HOST_RECORD_FILE = "credential.json";
|
|
10941
11472
|
var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
|
|
10942
11473
|
var SELF_HOST_SUBSCRIPTION_TYPE = "max";
|
|
10943
|
-
function
|
|
11474
|
+
function isRecord8(value) {
|
|
10944
11475
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10945
11476
|
}
|
|
10946
11477
|
function readJsonObject(fs12, file) {
|
|
@@ -10954,7 +11485,7 @@ function readJsonObject(fs12, file) {
|
|
|
10954
11485
|
} catch {
|
|
10955
11486
|
return void 0;
|
|
10956
11487
|
}
|
|
10957
|
-
return
|
|
11488
|
+
return isRecord8(parsed) ? parsed : void 0;
|
|
10958
11489
|
}
|
|
10959
11490
|
function readRcSelfHostRecord(fs12, frontdoorDir) {
|
|
10960
11491
|
const parsed = readJsonObject(fs12, path28.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
|
|
@@ -10978,7 +11509,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
10978
11509
|
const recordFile = path28.join(recordDir, RC_SELF_HOST_RECORD_FILE);
|
|
10979
11510
|
const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
|
|
10980
11511
|
const existingCredentials = readJsonObject(deps.fs, credentialsFile);
|
|
10981
|
-
const existingOauth =
|
|
11512
|
+
const existingOauth = isRecord8(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
|
|
10982
11513
|
const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
|
|
10983
11514
|
const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
|
|
10984
11515
|
if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
|
|
@@ -10995,7 +11526,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
10995
11526
|
deps.fs.writeFilePrivate(credentialsFile, `${JSON.stringify({ ...existingCredentials, claudeAiOauth: { accessToken, refreshToken, expiresAt: null, scopes: [...RC_SELF_HOST_SCOPE_LIST], subscriptionType: SELF_HOST_SUBSCRIPTION_TYPE, rateLimitTier: null } }, null, 2)}
|
|
10996
11527
|
`);
|
|
10997
11528
|
const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
|
|
10998
|
-
const existingBlock = (value) =>
|
|
11529
|
+
const existingBlock = (value) => isRecord8(value) ? value : {};
|
|
10999
11530
|
deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
|
|
11000
11531
|
...claudeJson,
|
|
11001
11532
|
oauthAccount: {
|
|
@@ -11194,7 +11725,7 @@ import path30 from "node:path";
|
|
|
11194
11725
|
// package.json
|
|
11195
11726
|
var package_default = {
|
|
11196
11727
|
name: "agent-shim",
|
|
11197
|
-
version: "8.
|
|
11728
|
+
version: "8.25.0",
|
|
11198
11729
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
11199
11730
|
license: "MIT",
|
|
11200
11731
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -12436,10 +12967,12 @@ export {
|
|
|
12436
12967
|
ProviderKindMismatchError,
|
|
12437
12968
|
ProviderNotFoundError,
|
|
12438
12969
|
ProviderSchema,
|
|
12970
|
+
RC_CONTEXT_USAGE_DETAILS,
|
|
12439
12971
|
RC_IDLE_EXPIRY_MS,
|
|
12440
12972
|
RC_ORPC_PATH_PREFIX,
|
|
12441
12973
|
RC_PENDING_DEADLINE_MS,
|
|
12442
12974
|
RC_PERMISSION_MODES,
|
|
12975
|
+
RC_READ_FILE_ENCODINGS,
|
|
12443
12976
|
RC_REQUEST_PARSE_CAP_BYTES,
|
|
12444
12977
|
RC_SELF_HOST_ENV,
|
|
12445
12978
|
RC_SELF_HOST_KEEPALIVE_MS,
|
|
@@ -12470,11 +13003,24 @@ export {
|
|
|
12470
13003
|
addPool,
|
|
12471
13004
|
addProvider,
|
|
12472
13005
|
answerRcControlRequest,
|
|
13006
|
+
authenticateRcSessionMcpServer,
|
|
12473
13007
|
buildEntryFacts,
|
|
12474
13008
|
buildLayoutPaths,
|
|
13009
|
+
buildRcControlRequestPayload,
|
|
12475
13010
|
buildRcControlResponsePayload,
|
|
13011
|
+
buildRcEndSessionPayload,
|
|
12476
13012
|
buildRcEventWriteBody,
|
|
13013
|
+
buildRcFileSuggestionsPayload,
|
|
13014
|
+
buildRcGetContextUsagePayload,
|
|
13015
|
+
buildRcGetUsagePayload,
|
|
12477
13016
|
buildRcInterruptPayload,
|
|
13017
|
+
buildRcKeepAlivePayload,
|
|
13018
|
+
buildRcMarkReadBody,
|
|
13019
|
+
buildRcMcpAuthenticatePayload,
|
|
13020
|
+
buildRcMcpOAuthCallbackUrlPayload,
|
|
13021
|
+
buildRcMcpReconnectPayload,
|
|
13022
|
+
buildRcMcpStatusPayload,
|
|
13023
|
+
buildRcReadFilePayload,
|
|
12478
13024
|
buildRcSetModelPayload,
|
|
12479
13025
|
buildRcSetPermissionModePayload,
|
|
12480
13026
|
buildRcUserMessagePayload,
|
|
@@ -12507,6 +13053,7 @@ export {
|
|
|
12507
13053
|
doorApiAuth,
|
|
12508
13054
|
doorApiNodeHandlerOf,
|
|
12509
13055
|
effectiveWindow,
|
|
13056
|
+
endRcSession,
|
|
12510
13057
|
ensureCa,
|
|
12511
13058
|
ensureFrontDoor,
|
|
12512
13059
|
ensureHeadroom,
|
|
@@ -12523,6 +13070,9 @@ export {
|
|
|
12523
13070
|
frontDoorRcApiClient,
|
|
12524
13071
|
frontDoorRcControl,
|
|
12525
13072
|
generateCa,
|
|
13073
|
+
getRcSessionContextUsage,
|
|
13074
|
+
getRcSessionMcpStatus,
|
|
13075
|
+
getRcSessionUsage,
|
|
12526
13076
|
headroomSocketTarget,
|
|
12527
13077
|
identityExists,
|
|
12528
13078
|
injectRcUserMessage,
|
|
@@ -12530,7 +13080,9 @@ export {
|
|
|
12530
13080
|
isIdentityDirectoryName,
|
|
12531
13081
|
isInterceptedHost,
|
|
12532
13082
|
isPrivateAddress,
|
|
13083
|
+
isRcContextUsageDetail,
|
|
12533
13084
|
isRcPermissionMode,
|
|
13085
|
+
isRcReadFileEncoding,
|
|
12534
13086
|
lateRcEventDial,
|
|
12535
13087
|
lateRcStreamDial,
|
|
12536
13088
|
listDirectoryRules,
|
|
@@ -12564,6 +13116,7 @@ export {
|
|
|
12564
13116
|
readProfile,
|
|
12565
13117
|
readProvider,
|
|
12566
13118
|
readRcSelfHostRecord,
|
|
13119
|
+
readRcSessionFile,
|
|
12567
13120
|
readUsageSnapshot,
|
|
12568
13121
|
realConnectCertStore,
|
|
12569
13122
|
realConnectEffects,
|
|
@@ -12571,6 +13124,7 @@ export {
|
|
|
12571
13124
|
realRcEventDial,
|
|
12572
13125
|
realRcStreamDial,
|
|
12573
13126
|
realRcWebFetch,
|
|
13127
|
+
reconnectRcSessionMcpServer,
|
|
12574
13128
|
recoverFarm,
|
|
12575
13129
|
recoveryDiagnostics,
|
|
12576
13130
|
removeDirectoryRule,
|
|
@@ -12590,6 +13144,7 @@ export {
|
|
|
12590
13144
|
runFrontDoorSupervisor,
|
|
12591
13145
|
runLauncher,
|
|
12592
13146
|
runSupervisor,
|
|
13147
|
+
sendRcKeepAlive,
|
|
12593
13148
|
servedByPipeline,
|
|
12594
13149
|
setAllowAmbientCredential,
|
|
12595
13150
|
setDefaultConfigProfile,
|
|
@@ -12605,6 +13160,9 @@ export {
|
|
|
12605
13160
|
snapshotPath,
|
|
12606
13161
|
spawnClaude,
|
|
12607
13162
|
startConnectServer,
|
|
13163
|
+
submitRcSessionMcpOAuthCallbackUrl,
|
|
13164
|
+
suggestRcSessionFiles,
|
|
13165
|
+
teleportRcSession,
|
|
12608
13166
|
topLevelNames,
|
|
12609
13167
|
updateDirectoryRule,
|
|
12610
13168
|
updateProvider,
|