agent-shim 8.8.0 → 8.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cli.cjs +5927 -2018
- package/dist/index.cjs +1919 -1292
- package/dist/index.mjs +1908 -1294
- package/dist/types/index.d.ts +365 -21
- package/package.json +3 -1
package/dist/index.mjs
CHANGED
|
@@ -3066,1326 +3066,1916 @@ function realConnectCertStore(paths) {
|
|
|
3066
3066
|
// src/frontdoor/connectEffects.ts
|
|
3067
3067
|
import dns from "node:dns";
|
|
3068
3068
|
import * as http from "node:http";
|
|
3069
|
-
import * as
|
|
3069
|
+
import * as https2 from "node:https";
|
|
3070
3070
|
import * as net2 from "node:net";
|
|
3071
3071
|
import * as tls from "node:tls";
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
var
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3072
|
+
|
|
3073
|
+
// src/codex/http.ts
|
|
3074
|
+
var HTTP_STATUS = {
|
|
3075
|
+
ok: 200,
|
|
3076
|
+
badRequest: 400,
|
|
3077
|
+
unauthorized: 401,
|
|
3078
|
+
forbidden: 403,
|
|
3079
|
+
notFound: 404,
|
|
3080
|
+
methodNotAllowed: 405,
|
|
3081
|
+
payloadTooLarge: 413,
|
|
3082
|
+
tooManyRequests: 429,
|
|
3083
|
+
/** Not sent to anyone: the client is gone. Recorded for a response abandoned because the client disconnected, following the convention nginx uses for the same case. */
|
|
3084
|
+
clientClosedRequest: 499,
|
|
3085
|
+
internalServerError: 500,
|
|
3086
|
+
badGateway: 502
|
|
3087
|
+
};
|
|
3088
|
+
|
|
3089
|
+
// src/frontdoor/rcControl.ts
|
|
3090
|
+
import * as https from "node:https";
|
|
3091
|
+
|
|
3092
|
+
// src/frontdoor/rcSessions.ts
|
|
3093
|
+
var RC_SESSIONS_PATH_PREFIX = "/v1/code/sessions";
|
|
3094
|
+
var RC_SESSION_ID_PREFIX = "cse_";
|
|
3095
|
+
var RC_IDLE_EXPIRY_MS = 45e3;
|
|
3096
|
+
var RC_CREATE_RESPONSE_CAP_BYTES = 8192;
|
|
3097
|
+
var CONFLICT_REASON_HEADER = "x-ccr-conflict-reason";
|
|
3098
|
+
var RC_STREAM_WORKER_SOURCE = "worker";
|
|
3099
|
+
var RC_PENDING_DEADLINE_MS = 14e3;
|
|
3100
|
+
var RC_REQUEST_PARSE_CAP_BYTES = 262144;
|
|
3101
|
+
var RC_PENDING_SUMMARY_EXCERPT_CHARS = 300;
|
|
3102
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE = 300;
|
|
3103
|
+
function isSuccessful(status) {
|
|
3104
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3080
3105
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3106
|
+
function singleHeader(headers, name) {
|
|
3107
|
+
const value = headers[name];
|
|
3108
|
+
return typeof value === "string" ? value : void 0;
|
|
3109
|
+
}
|
|
3110
|
+
var OAUTH_AUTHORIZATION_PREFIX = "Bearer sk-ant-oat";
|
|
3111
|
+
function oauthAuthorizationOf(headers) {
|
|
3112
|
+
const value = singleHeader(headers, "authorization");
|
|
3113
|
+
return value?.startsWith(OAUTH_AUTHORIZATION_PREFIX) === true ? value : void 0;
|
|
3114
|
+
}
|
|
3115
|
+
function sessionCreateId(body) {
|
|
3116
|
+
let parsed;
|
|
3117
|
+
try {
|
|
3118
|
+
parsed = JSON.parse(body);
|
|
3119
|
+
} catch {
|
|
3120
|
+
return void 0;
|
|
3085
3121
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
}
|
|
3096
|
-
callback(null, [{ address: first, family: 4 }]);
|
|
3097
|
-
});
|
|
3098
|
-
};
|
|
3099
|
-
var HTTP_PORT = 80;
|
|
3100
|
-
var HELD_PORT_ERRNOS = /* @__PURE__ */ new Set(["EADDRINUSE", "EADDRNOTAVAIL"]);
|
|
3101
|
-
async function dialOnce(connectOptions) {
|
|
3102
|
-
return await new Promise((resolve) => {
|
|
3103
|
-
const socket = net2.connect(connectOptions);
|
|
3104
|
-
socket.once("connect", () => {
|
|
3105
|
-
resolve({ error: null, socket });
|
|
3106
|
-
});
|
|
3107
|
-
socket.once("error", (error) => {
|
|
3108
|
-
resolve({ error, socket });
|
|
3109
|
-
});
|
|
3110
|
-
});
|
|
3122
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed)) {
|
|
3123
|
+
return void 0;
|
|
3124
|
+
}
|
|
3125
|
+
const session = parsed.session;
|
|
3126
|
+
if (typeof session !== "object" || session === null || !("id" in session)) {
|
|
3127
|
+
return void 0;
|
|
3128
|
+
}
|
|
3129
|
+
const id = session.id;
|
|
3130
|
+
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3111
3131
|
}
|
|
3112
|
-
function
|
|
3113
|
-
return
|
|
3132
|
+
function isRecord2(value) {
|
|
3133
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3114
3134
|
}
|
|
3115
|
-
function
|
|
3116
|
-
|
|
3135
|
+
function parseJsonObject(body) {
|
|
3136
|
+
let parsed;
|
|
3137
|
+
try {
|
|
3138
|
+
parsed = JSON.parse(body);
|
|
3139
|
+
} catch {
|
|
3140
|
+
return void 0;
|
|
3141
|
+
}
|
|
3142
|
+
return isRecord2(parsed) ? parsed : void 0;
|
|
3117
3143
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3144
|
+
function nonEmptyString(record, field) {
|
|
3145
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3146
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
3147
|
+
}
|
|
3148
|
+
function finiteNumber(record, field) {
|
|
3149
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
3150
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3151
|
+
}
|
|
3152
|
+
function excerpt(text) {
|
|
3153
|
+
return text.length <= RC_PENDING_SUMMARY_EXCERPT_CHARS ? text : `${text.slice(0, RC_PENDING_SUMMARY_EXCERPT_CHARS)}...`;
|
|
3154
|
+
}
|
|
3155
|
+
function payloadEventsOf(body) {
|
|
3156
|
+
const parsed = parseJsonObject(body);
|
|
3157
|
+
const events = parsed === void 0 ? void 0 : parsed.events;
|
|
3158
|
+
if (!Array.isArray(events)) {
|
|
3159
|
+
return [];
|
|
3160
|
+
}
|
|
3161
|
+
const payloads = [];
|
|
3162
|
+
for (const event of events) {
|
|
3163
|
+
if (!isRecord2(event)) {
|
|
3164
|
+
continue;
|
|
3123
3165
|
}
|
|
3124
|
-
const
|
|
3125
|
-
if (!
|
|
3126
|
-
|
|
3166
|
+
const payload = event.payload;
|
|
3167
|
+
if (!isRecord2(payload)) {
|
|
3168
|
+
continue;
|
|
3127
3169
|
}
|
|
3170
|
+
payloads.push(payload);
|
|
3128
3171
|
}
|
|
3172
|
+
return payloads;
|
|
3129
3173
|
}
|
|
3130
|
-
function
|
|
3131
|
-
|
|
3132
|
-
}
|
|
3133
|
-
var ExemptHttpAgent = class extends http.Agent {
|
|
3134
|
-
// A reserved-port dial that has to retry cannot produce its socket synchronously, so it settles first and arrives through the callback (node's documented asynchronous createConnection form); a literal dial has nothing to retry and keeps node's own synchronous shape.
|
|
3135
|
-
createConnection(options, oncreate) {
|
|
3136
|
-
const target = dialTarget(options, HTTP_PORT);
|
|
3137
|
-
if (net2.isIP(target.host) !== 0) {
|
|
3138
|
-
return literalDial(target);
|
|
3139
|
-
}
|
|
3140
|
-
void reservedDial(target).then((outcome) => {
|
|
3141
|
-
oncreate?.(outcome.error, outcome.socket);
|
|
3142
|
-
});
|
|
3174
|
+
function pendingOfControlRequestPayload(payload) {
|
|
3175
|
+
if (payload.type !== "control_request") {
|
|
3143
3176
|
return void 0;
|
|
3144
3177
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
// The same split as the plain sibling: a literal dial wraps TLS around the socket node's own agent would have returned, a reserved dial settles (retries included) first and arrives through the callback, which is exactly where node's own agent wraps its own dials.
|
|
3148
|
-
createConnection(options, oncreate) {
|
|
3149
|
-
const target = dialTarget(options, HTTPS_PORT);
|
|
3150
|
-
if (net2.isIP(target.host) !== 0) {
|
|
3151
|
-
return tls.connect({ socket: literalDial(target), servername: requestServername(options, target.host), ...tlsFacts(options) });
|
|
3152
|
-
}
|
|
3153
|
-
void reservedDial(target).then((outcome) => {
|
|
3154
|
-
if (outcome.error !== null) {
|
|
3155
|
-
oncreate?.(outcome.error, outcome.socket);
|
|
3156
|
-
return;
|
|
3157
|
-
}
|
|
3158
|
-
oncreate?.(null, tls.connect({ socket: outcome.socket, servername: requestServername(options, target.host), ...tlsFacts(options) }));
|
|
3159
|
-
});
|
|
3178
|
+
const requestId = nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3179
|
+
if (requestId === void 0) {
|
|
3160
3180
|
return void 0;
|
|
3161
3181
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
const
|
|
3165
|
-
|
|
3166
|
-
|
|
3182
|
+
const request4 = isRecord2(payload.request) ? payload.request : void 0;
|
|
3183
|
+
const subtype = nonEmptyString(request4, "subtype");
|
|
3184
|
+
const type = subtype ?? "control_request";
|
|
3185
|
+
let summary = "";
|
|
3186
|
+
if (subtype === "can_use_tool") {
|
|
3187
|
+
const toolName = nonEmptyString(request4, "tool_name") ?? "unknown tool";
|
|
3188
|
+
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3189
|
+
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3190
|
+
} else if (request4 !== void 0) {
|
|
3191
|
+
summary = excerpt(JSON.stringify(request4));
|
|
3167
3192
|
}
|
|
3168
|
-
|
|
3169
|
-
|
|
3193
|
+
return { requestId, type, summary };
|
|
3194
|
+
}
|
|
3195
|
+
function answeredRequestIdsOfControlResponsePayload(payload) {
|
|
3196
|
+
if (payload.type !== "control_response") {
|
|
3197
|
+
return [];
|
|
3170
3198
|
}
|
|
3171
|
-
|
|
3199
|
+
const response = isRecord2(payload.response) ? payload.response : void 0;
|
|
3200
|
+
const id = nonEmptyString(response, "request_id") ?? nonEmptyString(payload, "request_id") ?? nonEmptyString(payload, "uuid");
|
|
3201
|
+
return id === void 0 ? [] : [id];
|
|
3172
3202
|
}
|
|
3173
|
-
function
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
};
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3203
|
+
function parseRcRequestBody(kind, body) {
|
|
3204
|
+
if (kind === "heartbeat") {
|
|
3205
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: finiteNumber(parseJsonObject(body), "idle_seconds"), workerStatus: void 0 };
|
|
3206
|
+
}
|
|
3207
|
+
if (kind === "worker-registration") {
|
|
3208
|
+
return { controlRequests: [], answeredRequestIds: [], idleSeconds: void 0, workerStatus: nonEmptyString(parseJsonObject(body), "status") };
|
|
3209
|
+
}
|
|
3210
|
+
const controlRequests = [];
|
|
3211
|
+
const answeredRequestIds = [];
|
|
3212
|
+
for (const payload of payloadEventsOf(body)) {
|
|
3213
|
+
const pending = pendingOfControlRequestPayload(payload);
|
|
3214
|
+
if (pending !== void 0) {
|
|
3215
|
+
controlRequests.push({ requestId: pending.requestId, type: pending.type, summary: pending.summary });
|
|
3216
|
+
continue;
|
|
3217
|
+
}
|
|
3218
|
+
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3219
|
+
}
|
|
3220
|
+
return { controlRequests, answeredRequestIds, idleSeconds: void 0, workerStatus: void 0 };
|
|
3221
|
+
}
|
|
3222
|
+
function createRcSessionTracker(deps) {
|
|
3223
|
+
const entries = /* @__PURE__ */ new Map();
|
|
3224
|
+
const expire = (id) => {
|
|
3225
|
+
entries.delete(id);
|
|
3226
|
+
};
|
|
3227
|
+
const expirePendings = (entry, now) => {
|
|
3228
|
+
for (const [requestId, pending] of entry.pending) {
|
|
3229
|
+
if (now - pending.observedAt >= RC_PENDING_DEADLINE_MS) {
|
|
3230
|
+
entry.pending.delete(requestId);
|
|
3189
3231
|
}
|
|
3190
|
-
|
|
3191
|
-
|
|
3232
|
+
}
|
|
3233
|
+
};
|
|
3234
|
+
const sweep = () => {
|
|
3235
|
+
for (const [id, entry] of entries) {
|
|
3236
|
+
if (deps.now() - entry.lastSeenAt >= deps.idleMs) {
|
|
3237
|
+
expire(id);
|
|
3238
|
+
continue;
|
|
3192
3239
|
}
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
response.on("data", (chunk) => {
|
|
3196
|
-
chunks.push(chunk);
|
|
3197
|
-
});
|
|
3198
|
-
response.on("end", () => {
|
|
3199
|
-
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
3200
|
-
});
|
|
3201
|
-
});
|
|
3202
|
-
request4.once("error", reject);
|
|
3203
|
-
request4.end(body);
|
|
3204
|
-
})
|
|
3240
|
+
expirePendings(entry, deps.now());
|
|
3241
|
+
}
|
|
3205
3242
|
};
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
port: bound,
|
|
3229
|
-
close: async () => {
|
|
3230
|
-
await new Promise((closeResolve) => {
|
|
3231
|
-
server.close(() => {
|
|
3232
|
-
closeResolve(void 0);
|
|
3233
|
-
});
|
|
3234
|
-
for (const socket of accepted) {
|
|
3235
|
-
socket.destroy();
|
|
3236
|
-
}
|
|
3237
|
-
});
|
|
3238
|
-
}
|
|
3239
|
-
});
|
|
3240
|
-
});
|
|
3241
|
-
});
|
|
3242
|
-
return {
|
|
3243
|
-
listenLoopback: async (preferredPort, onSocket) => {
|
|
3244
|
-
if (preferredPort !== void 0) {
|
|
3245
|
-
try {
|
|
3246
|
-
return await listenOnce(preferredPort, onSocket);
|
|
3247
|
-
} catch {
|
|
3248
|
-
}
|
|
3243
|
+
const timer = setInterval(sweep, deps.idleMs);
|
|
3244
|
+
timer.unref();
|
|
3245
|
+
const birth = (id, facts) => {
|
|
3246
|
+
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0, sequenceNum: void 0 };
|
|
3247
|
+
entries.set(id, record);
|
|
3248
|
+
return record;
|
|
3249
|
+
};
|
|
3250
|
+
const observeExchange = (request4) => {
|
|
3251
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
3252
|
+
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3253
|
+
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3254
|
+
return void 0;
|
|
3255
|
+
}
|
|
3256
|
+
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3257
|
+
const facts = {
|
|
3258
|
+
oauthAuthorization: oauthAuthorizationOf(request4.headers),
|
|
3259
|
+
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3260
|
+
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3261
|
+
};
|
|
3262
|
+
if (tail === "") {
|
|
3263
|
+
if (method !== "POST") {
|
|
3264
|
+
return void 0;
|
|
3249
3265
|
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
let pendingContext = "";
|
|
3254
|
-
const server = tls.createServer(
|
|
3255
|
-
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
3256
|
-
(secure) => {
|
|
3257
|
-
onSecure(secure, pendingContext);
|
|
3258
|
-
}
|
|
3259
|
-
);
|
|
3260
|
-
server.on("clientError", (_error, socket) => {
|
|
3261
|
-
socket.destroy();
|
|
3262
|
-
});
|
|
3266
|
+
let accepted2 = false;
|
|
3267
|
+
let buffered2 = 0;
|
|
3268
|
+
let body = "";
|
|
3263
3269
|
return {
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
pendingContext = context;
|
|
3267
|
-
server.emit("connection", socket);
|
|
3270
|
+
onResponse: (status) => {
|
|
3271
|
+
accepted2 = isSuccessful(status);
|
|
3268
3272
|
},
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
};
|
|
3273
|
-
},
|
|
3274
|
-
createSniTlsAcceptor: (hosts, leafFor, onSecure) => {
|
|
3275
|
-
const contexts = new Map(
|
|
3276
|
-
hosts.map((host) => {
|
|
3277
|
-
const leaf = leafFor(host);
|
|
3278
|
-
return [host, tls.createSecureContext({ key: leaf.keyPem, cert: leaf.certPem })];
|
|
3279
|
-
})
|
|
3280
|
-
);
|
|
3281
|
-
const server = tls.createServer(
|
|
3282
|
-
{
|
|
3283
|
-
// No default key or cert exists, so the SNI selection is the only source of credentials: an unserved name fails its handshake here, and a client that sent no SNI at all never reaches the selection and fails for want of any certificate, which is exactly the fail-closed shape this surface wants.
|
|
3284
|
-
SNICallback: (servername, callback) => {
|
|
3285
|
-
const context = contexts.get(servername);
|
|
3286
|
-
callback(context === void 0 ? new Error(`the transparent surface serves no host named ${servername}`) : null, context);
|
|
3273
|
+
onBodyChunk: (chunk) => {
|
|
3274
|
+
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3275
|
+
return;
|
|
3287
3276
|
}
|
|
3277
|
+
const text = Buffer.from(chunk).toString("utf8");
|
|
3278
|
+
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3279
|
+
buffered2 = body.length;
|
|
3288
3280
|
},
|
|
3289
|
-
(
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
accept: (socket) => {
|
|
3299
|
-
server.emit("connection", socket);
|
|
3300
|
-
},
|
|
3301
|
-
close: () => {
|
|
3302
|
-
server.close();
|
|
3281
|
+
onEnd: () => {
|
|
3282
|
+
if (!accepted2) {
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const id2 = sessionCreateId(body);
|
|
3286
|
+
if (id2 === void 0) {
|
|
3287
|
+
return;
|
|
3288
|
+
}
|
|
3289
|
+
birth(id2, facts);
|
|
3303
3290
|
}
|
|
3304
3291
|
};
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3292
|
+
}
|
|
3293
|
+
const rest = tail.slice(1);
|
|
3294
|
+
const [id] = rest.split("/");
|
|
3295
|
+
if (id?.startsWith(RC_SESSION_ID_PREFIX) !== true) {
|
|
3296
|
+
return void 0;
|
|
3297
|
+
}
|
|
3298
|
+
const entry = entries.get(id) ?? birth(id, facts);
|
|
3299
|
+
entry.lastSeenAt = deps.now();
|
|
3300
|
+
entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
|
|
3301
|
+
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3302
|
+
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3303
|
+
const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
|
|
3304
|
+
let chunks = [];
|
|
3305
|
+
let buffered = 0;
|
|
3306
|
+
let oversized = false;
|
|
3307
|
+
let requestEnded = false;
|
|
3308
|
+
let accepted;
|
|
3309
|
+
let filed = false;
|
|
3310
|
+
const file = () => {
|
|
3311
|
+
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3312
|
+
return;
|
|
3315
3313
|
}
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3314
|
+
filed = true;
|
|
3315
|
+
const factsOfBody = oversized ? void 0 : parseRcRequestBody(bodyKind, Buffer.concat(chunks).toString("utf8"));
|
|
3316
|
+
chunks = [];
|
|
3317
|
+
const entryNow = entries.get(id);
|
|
3318
|
+
if (entryNow === void 0 || factsOfBody === void 0) {
|
|
3319
|
+
return;
|
|
3320
|
+
}
|
|
3321
|
+
const now = deps.now();
|
|
3322
|
+
for (const controlRequest of factsOfBody.controlRequests) {
|
|
3323
|
+
entryNow.pending.set(controlRequest.requestId, { sessionId: id, ...controlRequest, observedAt: now });
|
|
3324
|
+
}
|
|
3325
|
+
for (const requestId of factsOfBody.answeredRequestIds) {
|
|
3326
|
+
entryNow.pending.delete(requestId);
|
|
3327
|
+
}
|
|
3328
|
+
if (factsOfBody.idleSeconds !== void 0) {
|
|
3329
|
+
entryNow.workerIdleSeconds = { value: factsOfBody.idleSeconds, observedAt: now };
|
|
3330
|
+
}
|
|
3331
|
+
if (factsOfBody.workerStatus !== void 0) {
|
|
3332
|
+
entryNow.workerState = { value: factsOfBody.workerStatus, observedAt: now };
|
|
3333
|
+
}
|
|
3334
|
+
};
|
|
3335
|
+
const archiving = method === "POST" && rest === `${id}/archive`;
|
|
3336
|
+
return {
|
|
3337
|
+
...bodyKind === void 0 ? {} : {
|
|
3338
|
+
onRequestChunk: (chunk) => {
|
|
3339
|
+
if (oversized) {
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
chunks.push(Buffer.from(chunk));
|
|
3343
|
+
buffered += chunk.length;
|
|
3344
|
+
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3345
|
+
oversized = true;
|
|
3346
|
+
chunks = [];
|
|
3347
|
+
}
|
|
3319
3348
|
},
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3349
|
+
onRequestEnd: () => {
|
|
3350
|
+
requestEnded = true;
|
|
3351
|
+
file();
|
|
3323
3352
|
}
|
|
3324
|
-
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3353
|
+
},
|
|
3354
|
+
onResponse: (status, headers) => {
|
|
3355
|
+
if (singleHeader(headers, CONFLICT_REASON_HEADER) !== void 0 || archiving && isSuccessful(status)) {
|
|
3356
|
+
expire(id);
|
|
3357
|
+
return;
|
|
3358
|
+
}
|
|
3359
|
+
accepted = isSuccessful(status);
|
|
3360
|
+
file();
|
|
3361
|
+
},
|
|
3362
|
+
onBodyChunk: () => {
|
|
3363
|
+
},
|
|
3364
|
+
onEnd: () => {
|
|
3330
3365
|
}
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3366
|
+
};
|
|
3367
|
+
};
|
|
3368
|
+
const summaryOf = ({ id, createdAt, lastSeenAt }) => ({ id, createdAt, lastSeenAt });
|
|
3369
|
+
const pendingSummaryOf = ({ sessionId, requestId, type, summary, observedAt }) => ({ sessionId, requestId, type, summary, observedAt });
|
|
3370
|
+
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3371
|
+
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3372
|
+
return {
|
|
3373
|
+
observeExchange,
|
|
3374
|
+
list: () => {
|
|
3375
|
+
sweep();
|
|
3376
|
+
return sortedEntries().map(summaryOf);
|
|
3338
3377
|
},
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
const
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
agent: target.tls ? tlsAgent : plainAgent
|
|
3356
|
-
};
|
|
3357
|
-
if (target.tls) {
|
|
3358
|
-
options.servername = target.servername ?? target.host;
|
|
3359
|
-
if (target.ca !== void 0) {
|
|
3360
|
-
options.ca = [...target.ca];
|
|
3361
|
-
}
|
|
3362
|
-
if (target.rejectUnauthorized !== void 0) {
|
|
3363
|
-
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
3364
|
-
}
|
|
3378
|
+
statusOf: (sessionId) => {
|
|
3379
|
+
sweep();
|
|
3380
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).map((entry) => ({ ...summaryOf(entry), workerState: entry.workerState, workerIdleSeconds: entry.workerIdleSeconds, pending: pendingSorted(entry) }));
|
|
3381
|
+
},
|
|
3382
|
+
pendingOf: (sessionId) => {
|
|
3383
|
+
sweep();
|
|
3384
|
+
return sortedEntries().filter((entry) => sessionId === void 0 || entry.id === sessionId).flatMap((entry) => pendingSorted(entry));
|
|
3385
|
+
},
|
|
3386
|
+
credentialOf: (sessionId) => {
|
|
3387
|
+
const entry = entries.get(sessionId);
|
|
3388
|
+
return entry === void 0 ? void 0 : { authorization: entry.oauthAuthorization, anthropicVersion: entry.anthropicVersion, anthropicClientPlatform: entry.anthropicClientPlatform };
|
|
3389
|
+
},
|
|
3390
|
+
noteSequenceNums: (sessionId, sequenceNums) => {
|
|
3391
|
+
const entry = entries.get(sessionId);
|
|
3392
|
+
if (entry === void 0) {
|
|
3393
|
+
return;
|
|
3365
3394
|
}
|
|
3366
|
-
const
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3395
|
+
for (const sequenceNum of sequenceNums) {
|
|
3396
|
+
entry.sequenceNum = entry.sequenceNum === void 0 || sequenceNum > entry.sequenceNum ? sequenceNum : entry.sequenceNum;
|
|
3397
|
+
}
|
|
3398
|
+
},
|
|
3399
|
+
sequenceNumOf: (sessionId) => entries.get(sessionId)?.sequenceNum,
|
|
3400
|
+
fileStreamEvent: (sessionId, event) => {
|
|
3401
|
+
const entry = entries.get(sessionId);
|
|
3402
|
+
if (entry === void 0) {
|
|
3403
|
+
return;
|
|
3404
|
+
}
|
|
3405
|
+
entry.sequenceNum = entry.sequenceNum === void 0 || event.sequence_num > entry.sequenceNum ? event.sequence_num : entry.sequenceNum;
|
|
3406
|
+
if (!isRecord2(event.payload)) {
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
const pending = pendingOfControlRequestPayload(event.payload);
|
|
3410
|
+
if (pending !== void 0) {
|
|
3411
|
+
if (event.source === RC_STREAM_WORKER_SOURCE) {
|
|
3412
|
+
entry.pending.set(pending.requestId, { sessionId, ...pending, observedAt: deps.now() });
|
|
3382
3413
|
}
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3414
|
+
return;
|
|
3415
|
+
}
|
|
3416
|
+
for (const requestId of answeredRequestIdsOfControlResponsePayload(event.payload)) {
|
|
3417
|
+
entry.pending.delete(requestId);
|
|
3418
|
+
}
|
|
3419
|
+
},
|
|
3420
|
+
completePending: (sessionId, requestId) => {
|
|
3421
|
+
entries.get(sessionId)?.pending.delete(requestId);
|
|
3422
|
+
},
|
|
3423
|
+
close: () => {
|
|
3424
|
+
clearInterval(timer);
|
|
3425
|
+
}
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
function observingRoutedRoute(route, tracker, hooks) {
|
|
3429
|
+
return {
|
|
3430
|
+
...route,
|
|
3431
|
+
serve: async (request4, response) => {
|
|
3432
|
+
const observed = tracker.observeExchange({ method: request4.method, url: request4.url, headers: request4.headers });
|
|
3433
|
+
if (observed?.onRequestChunk !== void 0) {
|
|
3434
|
+
request4.body.on("data", (chunk) => {
|
|
3435
|
+
observed.onRequestChunk?.(chunk);
|
|
3390
3436
|
});
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
observer.onRequestEnd?.();
|
|
3394
|
-
});
|
|
3395
|
-
}
|
|
3396
|
-
response.on("close", () => {
|
|
3397
|
-
observer.onEnd();
|
|
3437
|
+
request4.body.on("end", () => {
|
|
3438
|
+
observed.onRequestEnd?.();
|
|
3398
3439
|
});
|
|
3399
3440
|
}
|
|
3400
|
-
|
|
3401
|
-
request4.socket.setNoDelay(true);
|
|
3402
|
-
upstream.on("socket", (socket) => {
|
|
3403
|
-
socket.setNoDelay(true);
|
|
3404
|
-
});
|
|
3441
|
+
await route.serve(request4, observed === void 0 ? response : teeRoutedResponse(response, observed, hooks?.onExchangeSettled));
|
|
3405
3442
|
}
|
|
3406
3443
|
};
|
|
3407
3444
|
}
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
}
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
function isSuccessful(status) {
|
|
3436
|
-
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE;
|
|
3445
|
+
function teeRoutedResponse(response, observer, onSettled) {
|
|
3446
|
+
return {
|
|
3447
|
+
get headersSent() {
|
|
3448
|
+
return response.headersSent;
|
|
3449
|
+
},
|
|
3450
|
+
start: (status, headers) => {
|
|
3451
|
+
observer.onResponse(status, headers);
|
|
3452
|
+
response.start(status, headers);
|
|
3453
|
+
},
|
|
3454
|
+
flush: () => {
|
|
3455
|
+
response.flush();
|
|
3456
|
+
},
|
|
3457
|
+
write: async (chunk) => {
|
|
3458
|
+
observer.onBodyChunk(typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk);
|
|
3459
|
+
await response.write(chunk);
|
|
3460
|
+
},
|
|
3461
|
+
end: () => {
|
|
3462
|
+
response.end();
|
|
3463
|
+
observer.onEnd();
|
|
3464
|
+
onSettled?.();
|
|
3465
|
+
},
|
|
3466
|
+
destroy: () => {
|
|
3467
|
+
response.destroy();
|
|
3468
|
+
observer.onEnd();
|
|
3469
|
+
onSettled?.();
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3437
3472
|
}
|
|
3438
|
-
function
|
|
3439
|
-
|
|
3440
|
-
|
|
3473
|
+
function rcObserverAsPassthrough(observer) {
|
|
3474
|
+
return {
|
|
3475
|
+
onRequestChunk: (chunk) => {
|
|
3476
|
+
observer.onRequestChunk?.(chunk);
|
|
3477
|
+
},
|
|
3478
|
+
...observer.onRequestEnd === void 0 ? {} : {
|
|
3479
|
+
onRequestEnd: () => {
|
|
3480
|
+
observer.onRequestEnd?.();
|
|
3481
|
+
}
|
|
3482
|
+
},
|
|
3483
|
+
onResponse: (status, headers) => {
|
|
3484
|
+
observer.onResponse(status, headers);
|
|
3485
|
+
},
|
|
3486
|
+
onResponseChunk: (chunk) => {
|
|
3487
|
+
observer.onBodyChunk(chunk);
|
|
3488
|
+
},
|
|
3489
|
+
onEnd: () => {
|
|
3490
|
+
observer.onEnd();
|
|
3491
|
+
}
|
|
3492
|
+
};
|
|
3441
3493
|
}
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3494
|
+
function buildRcUserMessagePayload(uuid, sessionId, text) {
|
|
3495
|
+
return {
|
|
3496
|
+
type: "user",
|
|
3497
|
+
uuid,
|
|
3498
|
+
session_id: sessionId,
|
|
3499
|
+
parent_tool_use_id: null,
|
|
3500
|
+
message: { role: "user", content: text }
|
|
3501
|
+
};
|
|
3446
3502
|
}
|
|
3447
|
-
function
|
|
3503
|
+
function buildRcEventWriteBody(payload) {
|
|
3504
|
+
return { events: [{ payload }] };
|
|
3505
|
+
}
|
|
3506
|
+
function sequenceNumsOf(body) {
|
|
3448
3507
|
let parsed;
|
|
3449
3508
|
try {
|
|
3450
3509
|
parsed = JSON.parse(body);
|
|
3451
3510
|
} catch {
|
|
3452
3511
|
return void 0;
|
|
3453
3512
|
}
|
|
3454
|
-
if (typeof parsed !== "object" || parsed === null || !("
|
|
3513
|
+
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3455
3514
|
return void 0;
|
|
3456
3515
|
}
|
|
3457
|
-
const
|
|
3458
|
-
|
|
3459
|
-
|
|
3516
|
+
const sequenceNums = [];
|
|
3517
|
+
const results = parsed.results;
|
|
3518
|
+
for (const result of results) {
|
|
3519
|
+
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3520
|
+
return void 0;
|
|
3521
|
+
}
|
|
3522
|
+
const sequenceNum = result.sequence_num;
|
|
3523
|
+
if (typeof sequenceNum === "number") {
|
|
3524
|
+
sequenceNums.push(sequenceNum);
|
|
3525
|
+
} else if (typeof sequenceNum === "string" && /^[0-9]+$/.test(sequenceNum)) {
|
|
3526
|
+
sequenceNums.push(Number(sequenceNum));
|
|
3527
|
+
} else {
|
|
3528
|
+
return void 0;
|
|
3529
|
+
}
|
|
3460
3530
|
}
|
|
3461
|
-
|
|
3462
|
-
return typeof id === "string" && id.startsWith(RC_SESSION_ID_PREFIX) ? id : void 0;
|
|
3463
|
-
}
|
|
3464
|
-
function isRecord2(value) {
|
|
3465
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3531
|
+
return sequenceNums.length === 0 ? void 0 : sequenceNums;
|
|
3466
3532
|
}
|
|
3467
|
-
function
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
return
|
|
3533
|
+
function rcEventWriteResultFromAnswer(answer) {
|
|
3534
|
+
if (!isSuccessful(answer.status)) {
|
|
3535
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3536
|
+
return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
|
|
3537
|
+
}
|
|
3538
|
+
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3473
3539
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
return
|
|
3540
|
+
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3541
|
+
if (sequenceNums === void 0) {
|
|
3542
|
+
return { ok: false, message: `the API accepted the injected event but its answer named no results[].sequence_num, so delivery cannot be confirmed: ${answer.body}` };
|
|
3543
|
+
}
|
|
3544
|
+
return { ok: true, sequenceNums };
|
|
3479
3545
|
}
|
|
3480
|
-
function
|
|
3481
|
-
|
|
3482
|
-
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3546
|
+
function noOAuthCredentialResult(sessionId) {
|
|
3547
|
+
return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
|
|
3483
3548
|
}
|
|
3484
|
-
function
|
|
3485
|
-
return
|
|
3549
|
+
function rcSessionNotObservedMessage(sessionId) {
|
|
3550
|
+
return `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)`;
|
|
3486
3551
|
}
|
|
3487
|
-
function
|
|
3488
|
-
const
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
return [];
|
|
3492
|
-
}
|
|
3493
|
-
const payloads = [];
|
|
3494
|
-
for (const event of events) {
|
|
3495
|
-
if (!isRecord2(event)) {
|
|
3496
|
-
continue;
|
|
3497
|
-
}
|
|
3498
|
-
const payload = event.payload;
|
|
3499
|
-
if (!isRecord2(payload)) {
|
|
3500
|
-
continue;
|
|
3501
|
-
}
|
|
3502
|
-
payloads.push(payload);
|
|
3552
|
+
function rcWriteHeaders(credential) {
|
|
3553
|
+
const headers = { "content-type": "application/json" };
|
|
3554
|
+
if (credential.authorization !== void 0) {
|
|
3555
|
+
headers.authorization = credential.authorization;
|
|
3503
3556
|
}
|
|
3504
|
-
|
|
3557
|
+
if (credential.anthropicVersion !== void 0) {
|
|
3558
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3559
|
+
}
|
|
3560
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
3561
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3562
|
+
}
|
|
3563
|
+
return headers;
|
|
3505
3564
|
}
|
|
3506
|
-
function
|
|
3507
|
-
|
|
3508
|
-
|
|
3565
|
+
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3566
|
+
const credential = deps.credentialOf(sessionId);
|
|
3567
|
+
if (credential === void 0) {
|
|
3568
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3509
3569
|
}
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
return void 0;
|
|
3570
|
+
if (credential.authorization === void 0) {
|
|
3571
|
+
return noOAuthCredentialResult(sessionId);
|
|
3513
3572
|
}
|
|
3514
|
-
const
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
const input = request4 === void 0 ? void 0 : request4.input;
|
|
3521
|
-
summary = input === void 0 ? toolName : `${toolName} ${excerpt(JSON.stringify(input))}`;
|
|
3522
|
-
} else if (request4 !== void 0) {
|
|
3523
|
-
summary = excerpt(JSON.stringify(request4));
|
|
3573
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3574
|
+
let answer;
|
|
3575
|
+
try {
|
|
3576
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3524
3579
|
}
|
|
3525
|
-
|
|
3580
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3581
|
+
if (result.ok) {
|
|
3582
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3583
|
+
}
|
|
3584
|
+
return result;
|
|
3526
3585
|
}
|
|
3527
|
-
function
|
|
3528
|
-
|
|
3529
|
-
|
|
3586
|
+
function buildRcControlResponsePayload(requestId, decision) {
|
|
3587
|
+
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
3588
|
+
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
3589
|
+
}
|
|
3590
|
+
var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
|
|
3591
|
+
function isRcPermissionMode(value) {
|
|
3592
|
+
return RC_PERMISSION_MODES.some((mode) => mode === value);
|
|
3593
|
+
}
|
|
3594
|
+
function buildRcInterruptPayload(requestId) {
|
|
3595
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
|
|
3596
|
+
}
|
|
3597
|
+
function buildRcSetModelPayload(requestId, model) {
|
|
3598
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "set_model", model } };
|
|
3599
|
+
}
|
|
3600
|
+
function buildRcSetPermissionModePayload(requestId, mode) {
|
|
3601
|
+
return { type: "control_request", request_id: requestId, request: { subtype: "set_permission_mode", mode } };
|
|
3602
|
+
}
|
|
3603
|
+
async function answerRcControlRequest(deps, sessionId, requestId, decision) {
|
|
3604
|
+
const credential = deps.credentialOf(sessionId);
|
|
3605
|
+
if (credential === void 0) {
|
|
3606
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3530
3607
|
}
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3608
|
+
if (credential.authorization === void 0) {
|
|
3609
|
+
return noOAuthCredentialResult(sessionId);
|
|
3610
|
+
}
|
|
3611
|
+
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
3612
|
+
return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
|
|
3613
|
+
}
|
|
3614
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
3615
|
+
let answer;
|
|
3616
|
+
try {
|
|
3617
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3618
|
+
} catch (error) {
|
|
3619
|
+
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3620
|
+
}
|
|
3621
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3622
|
+
if (result.ok) {
|
|
3623
|
+
deps.completePending(sessionId, requestId);
|
|
3624
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3625
|
+
}
|
|
3626
|
+
return result;
|
|
3534
3627
|
}
|
|
3535
|
-
function
|
|
3536
|
-
|
|
3537
|
-
|
|
3628
|
+
async function interruptRcSession(deps, sessionId) {
|
|
3629
|
+
const credential = deps.credentialOf(sessionId);
|
|
3630
|
+
if (credential === void 0) {
|
|
3631
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3538
3632
|
}
|
|
3539
|
-
if (
|
|
3540
|
-
return
|
|
3633
|
+
if (credential.authorization === void 0) {
|
|
3634
|
+
return noOAuthCredentialResult(sessionId);
|
|
3541
3635
|
}
|
|
3542
|
-
const
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
continue;
|
|
3549
|
-
}
|
|
3550
|
-
answeredRequestIds.push(...answeredRequestIdsOfControlResponsePayload(payload));
|
|
3636
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
|
|
3637
|
+
let answer;
|
|
3638
|
+
try {
|
|
3639
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3640
|
+
} catch (error) {
|
|
3641
|
+
return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3551
3642
|
}
|
|
3552
|
-
|
|
3643
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3644
|
+
if (result.ok) {
|
|
3645
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3646
|
+
}
|
|
3647
|
+
return result;
|
|
3553
3648
|
}
|
|
3554
|
-
function
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3649
|
+
async function setRcSessionModel(deps, sessionId, model) {
|
|
3650
|
+
if (model === "") {
|
|
3651
|
+
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" };
|
|
3652
|
+
}
|
|
3653
|
+
const credential = deps.credentialOf(sessionId);
|
|
3654
|
+
if (credential === void 0) {
|
|
3655
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3656
|
+
}
|
|
3657
|
+
if (credential.authorization === void 0) {
|
|
3658
|
+
return noOAuthCredentialResult(sessionId);
|
|
3659
|
+
}
|
|
3660
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
|
|
3661
|
+
let answer;
|
|
3662
|
+
try {
|
|
3663
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3664
|
+
} catch (error) {
|
|
3665
|
+
return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3666
|
+
}
|
|
3667
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3668
|
+
if (result.ok) {
|
|
3669
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3670
|
+
}
|
|
3671
|
+
return result;
|
|
3672
|
+
}
|
|
3673
|
+
async function setRcSessionPermissionMode(deps, sessionId, mode) {
|
|
3674
|
+
const credential = deps.credentialOf(sessionId);
|
|
3675
|
+
if (credential === void 0) {
|
|
3676
|
+
return { ok: false, message: rcSessionNotObservedMessage(sessionId) };
|
|
3677
|
+
}
|
|
3678
|
+
if (credential.authorization === void 0) {
|
|
3679
|
+
return noOAuthCredentialResult(sessionId);
|
|
3680
|
+
}
|
|
3681
|
+
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetPermissionModePayload(deps.newUuid(), mode)));
|
|
3682
|
+
let answer;
|
|
3683
|
+
try {
|
|
3684
|
+
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3685
|
+
} catch (error) {
|
|
3686
|
+
return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3687
|
+
}
|
|
3688
|
+
const result = rcEventWriteResultFromAnswer(answer);
|
|
3689
|
+
if (result.ok) {
|
|
3690
|
+
deps.noteSequenceNums(sessionId, result.sequenceNums);
|
|
3691
|
+
}
|
|
3692
|
+
return result;
|
|
3693
|
+
}
|
|
3694
|
+
|
|
3695
|
+
// src/frontdoor/rcControl.ts
|
|
3696
|
+
var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
|
|
3697
|
+
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3698
|
+
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
3699
|
+
function answerJson(response, status, body) {
|
|
3700
|
+
const text = JSON.stringify(body);
|
|
3701
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3702
|
+
response.end(text);
|
|
3703
|
+
}
|
|
3704
|
+
async function readBody(request4) {
|
|
3705
|
+
return await new Promise((resolve, reject) => {
|
|
3706
|
+
const chunks = [];
|
|
3707
|
+
let total = 0;
|
|
3708
|
+
request4.on("data", (chunk) => {
|
|
3709
|
+
total += chunk.length;
|
|
3710
|
+
if (total > CONTROL_BODY_CAP_BYTES) {
|
|
3711
|
+
request4.destroy();
|
|
3712
|
+
reject(new Error("too large"));
|
|
3713
|
+
return;
|
|
3563
3714
|
}
|
|
3715
|
+
chunks.push(chunk);
|
|
3716
|
+
});
|
|
3717
|
+
request4.on("end", () => {
|
|
3718
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
3719
|
+
});
|
|
3720
|
+
request4.on("error", () => {
|
|
3721
|
+
reject(new Error("the request body ended early"));
|
|
3722
|
+
});
|
|
3723
|
+
});
|
|
3724
|
+
}
|
|
3725
|
+
function createRcControlHandler(deps) {
|
|
3726
|
+
const sessionsPath = `${CONTROL_PATH_PREFIX}/sessions`;
|
|
3727
|
+
const statusPath = `${CONTROL_PATH_PREFIX}/status`;
|
|
3728
|
+
const pendingPath = `${CONTROL_PATH_PREFIX}/pending`;
|
|
3729
|
+
const injectPath = `${CONTROL_PATH_PREFIX}/inject`;
|
|
3730
|
+
const answerPath = `${CONTROL_PATH_PREFIX}/answer`;
|
|
3731
|
+
const interruptPath = `${CONTROL_PATH_PREFIX}/interrupt`;
|
|
3732
|
+
const setModelPath = `${CONTROL_PATH_PREFIX}/set-model`;
|
|
3733
|
+
const setPermissionModePath = `${CONTROL_PATH_PREFIX}/set-permission-mode`;
|
|
3734
|
+
return (request4, response) => {
|
|
3735
|
+
const presented = request4.headers.authorization;
|
|
3736
|
+
const bearerPrefix = "bearer ".length;
|
|
3737
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
3738
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
3739
|
+
answerJson(response, HTTP_STATUS.unauthorized, { error: "the front door's control routes demand this generation's control token as a Bearer credential" });
|
|
3740
|
+
return;
|
|
3564
3741
|
}
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3742
|
+
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
3743
|
+
const handle = async () => {
|
|
3744
|
+
if (url.pathname === sessionsPath) {
|
|
3745
|
+
if (request4.method !== "GET") {
|
|
3746
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
3750
|
+
return;
|
|
3571
3751
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
timer.unref();
|
|
3577
|
-
const birth = (id, facts) => {
|
|
3578
|
-
const record = { id, createdAt: deps.now(), lastSeenAt: deps.now(), ...facts, pending: /* @__PURE__ */ new Map(), workerState: void 0, workerIdleSeconds: void 0 };
|
|
3579
|
-
entries.set(id, record);
|
|
3580
|
-
return record;
|
|
3581
|
-
};
|
|
3582
|
-
const observeExchange = (request4) => {
|
|
3583
|
-
const method = (request4.method ?? "").toUpperCase();
|
|
3584
|
-
const path29 = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
3585
|
-
if (!path29.startsWith(RC_SESSIONS_PATH_PREFIX)) {
|
|
3586
|
-
return void 0;
|
|
3587
|
-
}
|
|
3588
|
-
const tail = path29.slice(RC_SESSIONS_PATH_PREFIX.length);
|
|
3589
|
-
const facts = {
|
|
3590
|
-
oauthAuthorization: oauthAuthorizationOf(request4.headers),
|
|
3591
|
-
anthropicVersion: singleHeader(request4.headers, "anthropic-version"),
|
|
3592
|
-
anthropicClientPlatform: singleHeader(request4.headers, "anthropic-client-platform")
|
|
3593
|
-
};
|
|
3594
|
-
if (tail === "") {
|
|
3595
|
-
if (method !== "POST") {
|
|
3596
|
-
return void 0;
|
|
3597
|
-
}
|
|
3598
|
-
let accepted2 = false;
|
|
3599
|
-
let buffered2 = 0;
|
|
3600
|
-
let body = "";
|
|
3601
|
-
return {
|
|
3602
|
-
onResponse: (status) => {
|
|
3603
|
-
accepted2 = isSuccessful(status);
|
|
3604
|
-
},
|
|
3605
|
-
onBodyChunk: (chunk) => {
|
|
3606
|
-
if (!accepted2 || buffered2 >= RC_CREATE_RESPONSE_CAP_BYTES) {
|
|
3607
|
-
return;
|
|
3608
|
-
}
|
|
3609
|
-
const text = Buffer.from(chunk).toString("utf8");
|
|
3610
|
-
body = body + text.slice(0, RC_CREATE_RESPONSE_CAP_BYTES - buffered2);
|
|
3611
|
-
buffered2 = body.length;
|
|
3612
|
-
},
|
|
3613
|
-
onEnd: () => {
|
|
3614
|
-
if (!accepted2) {
|
|
3615
|
-
return;
|
|
3616
|
-
}
|
|
3617
|
-
const id2 = sessionCreateId(body);
|
|
3618
|
-
if (id2 === void 0) {
|
|
3619
|
-
return;
|
|
3620
|
-
}
|
|
3621
|
-
birth(id2, facts);
|
|
3752
|
+
if (url.pathname === statusPath || url.pathname === pendingPath) {
|
|
3753
|
+
if (request4.method !== "GET") {
|
|
3754
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
3755
|
+
return;
|
|
3622
3756
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
}
|
|
3630
|
-
const entry = entries.get(id) ?? birth(id, facts);
|
|
3631
|
-
entry.lastSeenAt = deps.now();
|
|
3632
|
-
entry.oauthAuthorization = facts.oauthAuthorization ?? entry.oauthAuthorization;
|
|
3633
|
-
entry.anthropicVersion = facts.anthropicVersion ?? entry.anthropicVersion;
|
|
3634
|
-
entry.anthropicClientPlatform = facts.anthropicClientPlatform ?? entry.anthropicClientPlatform;
|
|
3635
|
-
const bodyKind = method === "POST" && rest === `${id}/worker/events` ? "worker-events" : method === "POST" && rest === `${id}/events` ? "client-events" : method === "POST" && rest === `${id}/worker/heartbeat` ? "heartbeat" : method === "PUT" && rest === `${id}/worker` ? "worker-registration" : void 0;
|
|
3636
|
-
let chunks = [];
|
|
3637
|
-
let buffered = 0;
|
|
3638
|
-
let oversized = false;
|
|
3639
|
-
let requestEnded = false;
|
|
3640
|
-
let accepted;
|
|
3641
|
-
let filed = false;
|
|
3642
|
-
const file = () => {
|
|
3643
|
-
if (filed || accepted !== true || !requestEnded || bodyKind === void 0) {
|
|
3757
|
+
const session = url.searchParams.get("session") ?? void 0;
|
|
3758
|
+
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
3759
|
+
answerJson(response, HTTP_STATUS.notFound, { error: rcSessionNotObservedMessage(session) });
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
3644
3763
|
return;
|
|
3645
3764
|
}
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3765
|
+
if (url.pathname === injectPath) {
|
|
3766
|
+
if (request4.method !== "POST") {
|
|
3767
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the inject operation is a POST" });
|
|
3768
|
+
return;
|
|
3769
|
+
}
|
|
3770
|
+
let parsed;
|
|
3771
|
+
try {
|
|
3772
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3773
|
+
} catch {
|
|
3774
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body is not readable JSON within the control body cap" });
|
|
3775
|
+
return;
|
|
3776
|
+
}
|
|
3777
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("text" in parsed) || typeof parsed.session !== "string" || typeof parsed.text !== "string" || parsed.session === "" || parsed.text === "") {
|
|
3778
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the inject body must be JSON naming a non-empty session id and text" });
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
const result = await deps.inject(parsed.session, parsed.text);
|
|
3782
|
+
if (result.ok) {
|
|
3783
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3784
|
+
return;
|
|
3785
|
+
}
|
|
3786
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3651
3787
|
return;
|
|
3652
3788
|
}
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3789
|
+
if (url.pathname === answerPath) {
|
|
3790
|
+
if (request4.method !== "POST") {
|
|
3791
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the answer operation is a POST" });
|
|
3792
|
+
return;
|
|
3793
|
+
}
|
|
3794
|
+
let parsed;
|
|
3795
|
+
try {
|
|
3796
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3797
|
+
} catch {
|
|
3798
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body is not readable JSON within the control body cap" });
|
|
3799
|
+
return;
|
|
3800
|
+
}
|
|
3801
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("request" in parsed) || !("approve" in parsed) || typeof parsed.session !== "string" || typeof parsed.request !== "string" || typeof parsed.approve !== "boolean" || parsed.session === "" || parsed.request === "") {
|
|
3802
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the answer body must be JSON naming a non-empty session id, request id, and an approve boolean" });
|
|
3803
|
+
return;
|
|
3804
|
+
}
|
|
3805
|
+
const text = "text" in parsed && typeof parsed.text === "string" ? parsed.text : void 0;
|
|
3806
|
+
if (parsed.approve && text !== void 0 && text !== "") {
|
|
3807
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
const result = await deps.answer(parsed.session, parsed.request, { approve: parsed.approve, message: parsed.approve ? void 0 : text });
|
|
3811
|
+
if (result.ok) {
|
|
3812
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
3813
|
+
return;
|
|
3814
|
+
}
|
|
3815
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3816
|
+
return;
|
|
3659
3817
|
}
|
|
3660
|
-
if (
|
|
3661
|
-
|
|
3818
|
+
if (url.pathname === interruptPath) {
|
|
3819
|
+
if (request4.method !== "POST") {
|
|
3820
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the interrupt operation is a POST" });
|
|
3821
|
+
return;
|
|
3822
|
+
}
|
|
3823
|
+
let parsed;
|
|
3824
|
+
try {
|
|
3825
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3826
|
+
} catch {
|
|
3827
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
|
|
3828
|
+
return;
|
|
3829
|
+
}
|
|
3830
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
|
|
3831
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
|
|
3832
|
+
return;
|
|
3833
|
+
}
|
|
3834
|
+
const result = await deps.interrupt(parsed.session);
|
|
3835
|
+
if (result.ok) {
|
|
3836
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3840
|
+
return;
|
|
3662
3841
|
}
|
|
3663
|
-
if (
|
|
3664
|
-
|
|
3842
|
+
if (url.pathname === setModelPath) {
|
|
3843
|
+
if (request4.method !== "POST") {
|
|
3844
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-model operation is a POST" });
|
|
3845
|
+
return;
|
|
3846
|
+
}
|
|
3847
|
+
let parsed;
|
|
3848
|
+
try {
|
|
3849
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3850
|
+
} catch {
|
|
3851
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body is not readable JSON within the control body cap" });
|
|
3852
|
+
return;
|
|
3853
|
+
}
|
|
3854
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("model" in parsed) || typeof parsed.session !== "string" || typeof parsed.model !== "string" || parsed.session === "" || parsed.model === "") {
|
|
3855
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-model body must be JSON naming a non-empty session id and model id" });
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
const result = await deps.setModel(parsed.session, parsed.model);
|
|
3859
|
+
if (result.ok) {
|
|
3860
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, model: parsed.model, sequenceNums: result.sequenceNums });
|
|
3861
|
+
return;
|
|
3862
|
+
}
|
|
3863
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3864
|
+
return;
|
|
3665
3865
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
onRequestChunk: (chunk) => {
|
|
3671
|
-
if (oversized) {
|
|
3672
|
-
return;
|
|
3673
|
-
}
|
|
3674
|
-
chunks.push(Buffer.from(chunk));
|
|
3675
|
-
buffered += chunk.length;
|
|
3676
|
-
if (buffered > RC_REQUEST_PARSE_CAP_BYTES) {
|
|
3677
|
-
oversized = true;
|
|
3678
|
-
chunks = [];
|
|
3679
|
-
}
|
|
3680
|
-
},
|
|
3681
|
-
onRequestEnd: () => {
|
|
3682
|
-
requestEnded = true;
|
|
3683
|
-
file();
|
|
3866
|
+
if (url.pathname === setPermissionModePath) {
|
|
3867
|
+
if (request4.method !== "POST") {
|
|
3868
|
+
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the set-permission-mode operation is a POST" });
|
|
3869
|
+
return;
|
|
3684
3870
|
}
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3871
|
+
let parsed;
|
|
3872
|
+
try {
|
|
3873
|
+
parsed = JSON.parse(await readBody(request4));
|
|
3874
|
+
} catch {
|
|
3875
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: "the set-permission-mode body is not readable JSON within the control body cap" });
|
|
3689
3876
|
return;
|
|
3690
3877
|
}
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3878
|
+
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || !("mode" in parsed) || typeof parsed.session !== "string" || parsed.session === "" || !isRcPermissionMode(parsed.mode)) {
|
|
3879
|
+
answerJson(response, HTTP_STATUS.badRequest, { error: `the set-permission-mode body must be JSON naming a non-empty session id and a mode the SDK's own type permits (${RC_PERMISSION_MODE_LIST})` });
|
|
3880
|
+
return;
|
|
3881
|
+
}
|
|
3882
|
+
const result = await deps.setPermissionMode(parsed.session, parsed.mode);
|
|
3883
|
+
if (result.ok) {
|
|
3884
|
+
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, mode: parsed.mode, sequenceNums: result.sequenceNums });
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
3888
|
+
return;
|
|
3697
3889
|
}
|
|
3890
|
+
answerJson(response, HTTP_STATUS.notFound, { error: `no such control route: ${url.pathname}` });
|
|
3698
3891
|
};
|
|
3892
|
+
handle().catch((error) => {
|
|
3893
|
+
answerJson(response, HTTP_STATUS.internalServerError, { error: `the control route failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
3894
|
+
});
|
|
3699
3895
|
};
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
const sortedEntries = () => [...entries.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1));
|
|
3703
|
-
const pendingSorted = (entry) => [...entry.pending.values()].sort((left, right) => left.observedAt - right.observedAt || (left.requestId < right.requestId ? -1 : 1)).map(pendingSummaryOf);
|
|
3896
|
+
}
|
|
3897
|
+
function realRcControlTransport(port, ca) {
|
|
3704
3898
|
return {
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3899
|
+
request: async (options) => await new Promise((resolve, reject) => {
|
|
3900
|
+
const request4 = https.request(
|
|
3901
|
+
{
|
|
3902
|
+
host: "127.0.0.1",
|
|
3903
|
+
port,
|
|
3904
|
+
method: options.method,
|
|
3905
|
+
path: options.path,
|
|
3906
|
+
headers: options.body === void 0 ? options.headers : { ...options.headers, "content-length": String(Buffer.byteLength(options.body, "utf8")) },
|
|
3907
|
+
agent: false,
|
|
3908
|
+
ca: [ca]
|
|
3909
|
+
},
|
|
3910
|
+
(response) => {
|
|
3911
|
+
const chunks = [];
|
|
3912
|
+
response.on("data", (chunk) => {
|
|
3913
|
+
chunks.push(chunk);
|
|
3914
|
+
});
|
|
3915
|
+
response.on("end", () => {
|
|
3916
|
+
resolve({ status: response.statusCode ?? 0, body: Buffer.concat(chunks).toString("utf8") });
|
|
3917
|
+
});
|
|
3918
|
+
}
|
|
3919
|
+
);
|
|
3920
|
+
request4.once("error", reject);
|
|
3921
|
+
if (options.body === void 0) {
|
|
3922
|
+
request4.end();
|
|
3923
|
+
} else {
|
|
3924
|
+
request4.end(options.body);
|
|
3925
|
+
}
|
|
3926
|
+
})
|
|
3728
3927
|
};
|
|
3729
3928
|
}
|
|
3730
|
-
function
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3929
|
+
function controlErrorOf(body) {
|
|
3930
|
+
try {
|
|
3931
|
+
const parsed = JSON.parse(body);
|
|
3932
|
+
if (typeof parsed === "object" && parsed !== null && "error" in parsed && typeof parsed.error === "string") {
|
|
3933
|
+
return parsed.error;
|
|
3934
|
+
}
|
|
3935
|
+
} catch {
|
|
3936
|
+
return body;
|
|
3937
|
+
}
|
|
3938
|
+
return void 0;
|
|
3939
|
+
}
|
|
3940
|
+
function isRcSessionSummary(value) {
|
|
3941
|
+
if (typeof value !== "object" || value === null) {
|
|
3942
|
+
return false;
|
|
3943
|
+
}
|
|
3944
|
+
if (!("id" in value) || !("createdAt" in value) || !("lastSeenAt" in value)) {
|
|
3945
|
+
return false;
|
|
3946
|
+
}
|
|
3947
|
+
return typeof value.id === "string" && typeof value.createdAt === "number" && typeof value.lastSeenAt === "number";
|
|
3948
|
+
}
|
|
3949
|
+
function isRcWorkerFact(value, valueIs) {
|
|
3950
|
+
if (typeof value !== "object" || value === null) {
|
|
3951
|
+
return false;
|
|
3952
|
+
}
|
|
3953
|
+
if (!("value" in value) || !("observedAt" in value)) {
|
|
3954
|
+
return false;
|
|
3955
|
+
}
|
|
3956
|
+
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
3957
|
+
}
|
|
3958
|
+
function isRcSessionStatus(value) {
|
|
3959
|
+
if (!isRcSessionSummary(value)) {
|
|
3960
|
+
return false;
|
|
3961
|
+
}
|
|
3962
|
+
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
3963
|
+
return false;
|
|
3964
|
+
}
|
|
3965
|
+
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
3966
|
+
return false;
|
|
3967
|
+
}
|
|
3968
|
+
if ("workerIdleSeconds" in value && value.workerIdleSeconds !== void 0 && !isRcWorkerFact(value.workerIdleSeconds, (candidate) => typeof candidate === "number")) {
|
|
3969
|
+
return false;
|
|
3970
|
+
}
|
|
3971
|
+
return value.pending.every((candidate) => isRcPendingRequestSummary(candidate));
|
|
3972
|
+
}
|
|
3973
|
+
function isRcPendingRequestSummary(value) {
|
|
3974
|
+
if (typeof value !== "object" || value === null) {
|
|
3975
|
+
return false;
|
|
3976
|
+
}
|
|
3977
|
+
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
3978
|
+
return false;
|
|
3979
|
+
}
|
|
3980
|
+
return typeof value.sessionId === "string" && typeof value.requestId === "string" && typeof value.type === "string" && typeof value.summary === "string" && typeof value.observedAt === "number";
|
|
3981
|
+
}
|
|
3982
|
+
function sequenceNumsOfBody(body) {
|
|
3983
|
+
let parsed;
|
|
3984
|
+
try {
|
|
3985
|
+
parsed = JSON.parse(body);
|
|
3986
|
+
} catch {
|
|
3987
|
+
return void 0;
|
|
3988
|
+
}
|
|
3989
|
+
if (typeof parsed !== "object" || parsed === null || !("sequenceNums" in parsed) || !Array.isArray(parsed.sequenceNums)) {
|
|
3990
|
+
return void 0;
|
|
3991
|
+
}
|
|
3992
|
+
const sequenceNums = [];
|
|
3993
|
+
const values = parsed.sequenceNums;
|
|
3994
|
+
for (const value of values) {
|
|
3995
|
+
if (typeof value !== "number") {
|
|
3996
|
+
return void 0;
|
|
3997
|
+
}
|
|
3998
|
+
sequenceNums.push(value);
|
|
3999
|
+
}
|
|
4000
|
+
return sequenceNums;
|
|
4001
|
+
}
|
|
4002
|
+
function frontDoorRcControl(transport, token) {
|
|
4003
|
+
const headers = { authorization: `Bearer ${token}` };
|
|
4004
|
+
const listAnswerOf = async (path29, what) => {
|
|
4005
|
+
try {
|
|
4006
|
+
return await transport.request({ method: "GET", path: path29, headers });
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
throw new Error(`${what}: could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
const sessionsOf = (answer, what) => {
|
|
4012
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4013
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4014
|
+
}
|
|
4015
|
+
let parsed;
|
|
4016
|
+
try {
|
|
4017
|
+
parsed = JSON.parse(answer.body);
|
|
4018
|
+
} catch {
|
|
4019
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4020
|
+
}
|
|
4021
|
+
if (typeof parsed !== "object" || parsed === null || !("sessions" in parsed) || !Array.isArray(parsed.sessions)) {
|
|
4022
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4023
|
+
}
|
|
4024
|
+
const candidates = parsed.sessions;
|
|
4025
|
+
const sessions = [];
|
|
4026
|
+
for (const value of candidates) {
|
|
4027
|
+
if (!isRcSessionSummary(value)) {
|
|
4028
|
+
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
3742
4029
|
}
|
|
3743
|
-
|
|
4030
|
+
sessions.push(value);
|
|
3744
4031
|
}
|
|
4032
|
+
return sessions;
|
|
3745
4033
|
};
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
}
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
observer.onEnd();
|
|
3766
|
-
},
|
|
3767
|
-
destroy: () => {
|
|
3768
|
-
response.destroy();
|
|
3769
|
-
observer.onEnd();
|
|
4034
|
+
const statusesOf = (answer, what) => {
|
|
4035
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4036
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4037
|
+
}
|
|
4038
|
+
let parsed;
|
|
4039
|
+
try {
|
|
4040
|
+
parsed = JSON.parse(answer.body);
|
|
4041
|
+
} catch {
|
|
4042
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4043
|
+
}
|
|
4044
|
+
if (typeof parsed !== "object" || parsed === null || !("statuses" in parsed) || !Array.isArray(parsed.statuses)) {
|
|
4045
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4046
|
+
}
|
|
4047
|
+
const statuses = [];
|
|
4048
|
+
for (const value of parsed.statuses) {
|
|
4049
|
+
if (!isRcSessionStatus(value)) {
|
|
4050
|
+
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4051
|
+
}
|
|
4052
|
+
statuses.push(value);
|
|
3770
4053
|
}
|
|
4054
|
+
return statuses;
|
|
3771
4055
|
};
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
4056
|
+
const pendingOfAnswer = (answer, what) => {
|
|
4057
|
+
if (answer.status !== HTTP_STATUS.ok) {
|
|
4058
|
+
throw new Error(`${what} failed (HTTP ${String(answer.status)}): ${controlErrorOf(answer.body) ?? answer.body}`);
|
|
4059
|
+
}
|
|
4060
|
+
let parsed;
|
|
4061
|
+
try {
|
|
4062
|
+
parsed = JSON.parse(answer.body);
|
|
4063
|
+
} catch {
|
|
4064
|
+
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4065
|
+
}
|
|
4066
|
+
if (typeof parsed !== "object" || parsed === null || !("pending" in parsed) || !Array.isArray(parsed.pending)) {
|
|
4067
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
4068
|
+
}
|
|
4069
|
+
const pending = [];
|
|
4070
|
+
for (const value of parsed.pending) {
|
|
4071
|
+
if (!isRcPendingRequestSummary(value)) {
|
|
4072
|
+
throw new Error(`${what}: the door's answer named no pending list: ${answer.body}`);
|
|
3781
4073
|
}
|
|
3782
|
-
|
|
3783
|
-
onResponse: (status, headers) => {
|
|
3784
|
-
observer.onResponse(status, headers);
|
|
3785
|
-
},
|
|
3786
|
-
onResponseChunk: (chunk) => {
|
|
3787
|
-
observer.onBodyChunk(chunk);
|
|
3788
|
-
},
|
|
3789
|
-
onEnd: () => {
|
|
3790
|
-
observer.onEnd();
|
|
4074
|
+
pending.push(value);
|
|
3791
4075
|
}
|
|
4076
|
+
return pending;
|
|
3792
4077
|
};
|
|
3793
|
-
|
|
3794
|
-
|
|
4078
|
+
const writeOf = async (path29, body, describeFailure) => {
|
|
4079
|
+
let answer;
|
|
4080
|
+
try {
|
|
4081
|
+
answer = await transport.request({ method: "POST", path: path29, headers: { ...headers, "content-type": "application/json" }, body });
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4084
|
+
}
|
|
4085
|
+
if (answer.status === HTTP_STATUS.ok) {
|
|
4086
|
+
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4087
|
+
if (sequenceNums === void 0) {
|
|
4088
|
+
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4089
|
+
}
|
|
4090
|
+
return { ok: true, sequenceNums };
|
|
4091
|
+
}
|
|
4092
|
+
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4093
|
+
};
|
|
4094
|
+
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
3795
4095
|
return {
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
4096
|
+
listSessions: async () => sessionsOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/sessions`, "listing the door's Remote Control sessions"), "listing the door's Remote Control sessions"),
|
|
4097
|
+
statusOf: async (sessionId) => statusesOf(await listAnswerOf(`${CONTROL_PATH_PREFIX}/status${sessionQuery(sessionId)}`, "reading the door's Remote Control session status"), "reading the door's Remote Control session status"),
|
|
4098
|
+
pendingOf: async (sessionId) => pendingOfAnswer(await listAnswerOf(`${CONTROL_PATH_PREFIX}/pending${sessionQuery(sessionId)}`, "listing the door's pending Remote Control control requests"), "listing the door's pending Remote Control control requests"),
|
|
4099
|
+
sendPrompt: async (sessionId, text) => await writeOf(`${CONTROL_PATH_PREFIX}/inject`, JSON.stringify({ session: sessionId, text }), (status, message) => `injecting into ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4100
|
+
answerRequest: async (sessionId, requestId, decision) => await writeOf(
|
|
4101
|
+
`${CONTROL_PATH_PREFIX}/answer`,
|
|
4102
|
+
JSON.stringify({ session: sessionId, request: requestId, approve: decision.approve, ...decision.approve || decision.message === void 0 ? {} : { text: decision.message } }),
|
|
4103
|
+
(status, message) => `answering control request ${requestId} on ${sessionId} failed (HTTP ${String(status)}): ${message}`
|
|
4104
|
+
),
|
|
4105
|
+
interruptSession: async (sessionId) => await writeOf(`${CONTROL_PATH_PREFIX}/interrupt`, JSON.stringify({ session: sessionId }), (status, message) => `interrupting ${sessionId} failed (HTTP ${String(status)}): ${message}`),
|
|
4106
|
+
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}`),
|
|
4107
|
+
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}`)
|
|
3801
4108
|
};
|
|
3802
4109
|
}
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
} catch {
|
|
3811
|
-
return void 0;
|
|
3812
|
-
}
|
|
3813
|
-
if (typeof parsed !== "object" || parsed === null || !("results" in parsed) || !Array.isArray(parsed.results)) {
|
|
3814
|
-
return void 0;
|
|
3815
|
-
}
|
|
3816
|
-
const sequenceNums = [];
|
|
3817
|
-
const results = parsed.results;
|
|
3818
|
-
for (const result of results) {
|
|
3819
|
-
if (typeof result !== "object" || result === null || !("sequence_num" in result)) {
|
|
3820
|
-
return void 0;
|
|
3821
|
-
}
|
|
3822
|
-
const sequenceNum = result.sequence_num;
|
|
3823
|
-
if (typeof sequenceNum === "number") {
|
|
3824
|
-
sequenceNums.push(sequenceNum);
|
|
3825
|
-
} else if (typeof sequenceNum === "string" && /^[0-9]+$/.test(sequenceNum)) {
|
|
3826
|
-
sequenceNums.push(Number(sequenceNum));
|
|
3827
|
-
} else {
|
|
3828
|
-
return void 0;
|
|
4110
|
+
|
|
4111
|
+
// src/frontdoor/server.ts
|
|
4112
|
+
async function* upstreamChunks(stream) {
|
|
4113
|
+
for await (const chunk of stream) {
|
|
4114
|
+
if (typeof chunk === "string") {
|
|
4115
|
+
yield Buffer.from(chunk, "utf8");
|
|
4116
|
+
continue;
|
|
3829
4117
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
}
|
|
3833
|
-
function rcEventWriteResultFromAnswer(answer) {
|
|
3834
|
-
if (!isSuccessful(answer.status)) {
|
|
3835
|
-
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
3836
|
-
return { ok: false, message: `the API refused the observed Authorization bearer for this session (HTTP 401): the bearer went stale after the door last saw the session's traffic. The session's own client refreshes it on its next call, which the door will observe, so retry once Remote Control has been active again. API answer: ${answer.body}` };
|
|
4118
|
+
if (chunk instanceof Uint8Array) {
|
|
4119
|
+
yield chunk;
|
|
3837
4120
|
}
|
|
3838
|
-
return { ok: false, message: `the API answered HTTP ${String(answer.status)} to the injected event: ${answer.body}` };
|
|
3839
|
-
}
|
|
3840
|
-
const sequenceNums = sequenceNumsOf(answer.body);
|
|
3841
|
-
if (sequenceNums === void 0) {
|
|
3842
|
-
return { ok: false, message: `the API accepted the injected event but its answer named no results[].sequence_num, so delivery cannot be confirmed: ${answer.body}` };
|
|
3843
|
-
}
|
|
3844
|
-
return { ok: true, sequenceNums };
|
|
3845
|
-
}
|
|
3846
|
-
function noOAuthCredentialResult(sessionId) {
|
|
3847
|
-
return { ok: false, message: `no claude.ai OAuth bearer has been observed for session ${sessionId}: only worker calls crossed this door, and the worker JWT they carry is answered 401 on the client half. Reconnect Remote Control through this door and the create's own bearer will be observed` };
|
|
3848
|
-
}
|
|
3849
|
-
function rcWriteHeaders(credential) {
|
|
3850
|
-
const headers = { "content-type": "application/json" };
|
|
3851
|
-
if (credential.authorization !== void 0) {
|
|
3852
|
-
headers.authorization = credential.authorization;
|
|
3853
|
-
}
|
|
3854
|
-
if (credential.anthropicVersion !== void 0) {
|
|
3855
|
-
headers["anthropic-version"] = credential.anthropicVersion;
|
|
3856
|
-
}
|
|
3857
|
-
if (credential.anthropicClientPlatform !== void 0) {
|
|
3858
|
-
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
3859
|
-
}
|
|
3860
|
-
return headers;
|
|
3861
|
-
}
|
|
3862
|
-
async function injectRcUserMessage(deps, sessionId, text) {
|
|
3863
|
-
const credential = deps.credentialOf(sessionId);
|
|
3864
|
-
if (credential === void 0) {
|
|
3865
|
-
return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
|
|
3866
|
-
}
|
|
3867
|
-
if (credential.authorization === void 0) {
|
|
3868
|
-
return noOAuthCredentialResult(sessionId);
|
|
3869
|
-
}
|
|
3870
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcUserMessagePayload(deps.newUuid(), sessionId, text)));
|
|
3871
|
-
let answer;
|
|
3872
|
-
try {
|
|
3873
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3874
|
-
} catch (error) {
|
|
3875
|
-
return { ok: false, message: `the door could not reach the API host to inject into session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3876
4121
|
}
|
|
3877
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
3878
|
-
}
|
|
3879
|
-
function buildRcControlResponsePayload(requestId, decision) {
|
|
3880
|
-
const result = decision.approve ? { behavior: "allow" } : { behavior: "deny", message: decision.message ?? "" };
|
|
3881
|
-
return { type: "control_response", response: { subtype: "success", request_id: requestId, response: result } };
|
|
3882
|
-
}
|
|
3883
|
-
var RC_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
|
|
3884
|
-
function isRcPermissionMode(value) {
|
|
3885
|
-
return RC_PERMISSION_MODES.some((mode) => mode === value);
|
|
3886
|
-
}
|
|
3887
|
-
function buildRcInterruptPayload(requestId) {
|
|
3888
|
-
return { type: "control_request", request_id: requestId, request: { subtype: "interrupt" } };
|
|
3889
4122
|
}
|
|
3890
|
-
|
|
3891
|
-
|
|
4123
|
+
|
|
4124
|
+
// src/frontdoor/connectEffects.ts
|
|
4125
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE2 = 300;
|
|
4126
|
+
function isSuccessful2(status) {
|
|
4127
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE2;
|
|
3892
4128
|
}
|
|
3893
|
-
|
|
3894
|
-
|
|
4129
|
+
var UPSTREAM_LOCAL_PORT_START = 47900;
|
|
4130
|
+
var UPSTREAM_LOCAL_PORT_END = 47919;
|
|
4131
|
+
var UPSTREAM_LOCAL_PORT_COUNT = UPSTREAM_LOCAL_PORT_END - UPSTREAM_LOCAL_PORT_START + 1;
|
|
4132
|
+
var nextUpstreamLocalPort = UPSTREAM_LOCAL_PORT_START;
|
|
4133
|
+
function upstreamLocalPort() {
|
|
4134
|
+
const port = nextUpstreamLocalPort;
|
|
4135
|
+
nextUpstreamLocalPort = nextUpstreamLocalPort === UPSTREAM_LOCAL_PORT_END ? UPSTREAM_LOCAL_PORT_START : nextUpstreamLocalPort + 1;
|
|
4136
|
+
return port;
|
|
3895
4137
|
}
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
return
|
|
3900
|
-
}
|
|
3901
|
-
if (credential.authorization === void 0) {
|
|
3902
|
-
return noOAuthCredentialResult(sessionId);
|
|
3903
|
-
}
|
|
3904
|
-
if (!deps.pendingOf(sessionId).some((pending) => pending.requestId === requestId)) {
|
|
3905
|
-
return { ok: false, message: `the front door has not observed control request ${requestId} pending on session ${sessionId}: it was answered already (by this door or by an attached client), it passed its answer deadline (the protocol's permission round-trip bound), or it never passed through this door` };
|
|
3906
|
-
}
|
|
3907
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcControlResponsePayload(requestId, decision)));
|
|
3908
|
-
let answer;
|
|
3909
|
-
try {
|
|
3910
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3911
|
-
} catch (error) {
|
|
3912
|
-
return { ok: false, message: `the door could not reach the API host to answer on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3913
|
-
}
|
|
3914
|
-
const result = rcEventWriteResultFromAnswer(answer);
|
|
3915
|
-
if (result.ok) {
|
|
3916
|
-
deps.completePending(sessionId, requestId);
|
|
4138
|
+
var realAddressLookup = (host, _options, callback) => {
|
|
4139
|
+
if (net2.isIP(host) !== 0) {
|
|
4140
|
+
callback(null, [{ address: host, family: net2.isIP(host) }]);
|
|
4141
|
+
return;
|
|
3917
4142
|
}
|
|
3918
|
-
|
|
4143
|
+
dns.resolve4(host, (error, addresses) => {
|
|
4144
|
+
if (error !== null) {
|
|
4145
|
+
callback(error, [{ address: "", family: 4 }]);
|
|
4146
|
+
return;
|
|
4147
|
+
}
|
|
4148
|
+
const first = addresses[0];
|
|
4149
|
+
if (first === void 0) {
|
|
4150
|
+
callback(new Error(`no A record for ${host}`), [{ address: "", family: 4 }]);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
callback(null, [{ address: first, family: 4 }]);
|
|
4154
|
+
});
|
|
4155
|
+
};
|
|
4156
|
+
var HTTP_PORT = 80;
|
|
4157
|
+
var HELD_PORT_ERRNOS = /* @__PURE__ */ new Set(["EADDRINUSE", "EADDRNOTAVAIL"]);
|
|
4158
|
+
async function dialOnce(connectOptions) {
|
|
4159
|
+
return await new Promise((resolve) => {
|
|
4160
|
+
const socket = net2.connect(connectOptions);
|
|
4161
|
+
socket.once("connect", () => {
|
|
4162
|
+
resolve({ error: null, socket });
|
|
4163
|
+
});
|
|
4164
|
+
socket.once("error", (error) => {
|
|
4165
|
+
resolve({ error, socket });
|
|
4166
|
+
});
|
|
4167
|
+
});
|
|
3919
4168
|
}
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
if (credential === void 0) {
|
|
3923
|
-
return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
|
|
3924
|
-
}
|
|
3925
|
-
if (credential.authorization === void 0) {
|
|
3926
|
-
return noOAuthCredentialResult(sessionId);
|
|
3927
|
-
}
|
|
3928
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcInterruptPayload(deps.newUuid())));
|
|
3929
|
-
let answer;
|
|
3930
|
-
try {
|
|
3931
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3932
|
-
} catch (error) {
|
|
3933
|
-
return { ok: false, message: `the door could not reach the API host to interrupt session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3934
|
-
}
|
|
3935
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
4169
|
+
function dialTarget(options, defaultPort) {
|
|
4170
|
+
return { host: typeof options.host === "string" ? options.host : "", port: typeof options.port === "number" ? options.port : defaultPort };
|
|
3936
4171
|
}
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
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" };
|
|
3940
|
-
}
|
|
3941
|
-
const credential = deps.credentialOf(sessionId);
|
|
3942
|
-
if (credential === void 0) {
|
|
3943
|
-
return { ok: false, message: `the front door has not observed Remote Control session ${sessionId}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` };
|
|
3944
|
-
}
|
|
3945
|
-
if (credential.authorization === void 0) {
|
|
3946
|
-
return noOAuthCredentialResult(sessionId);
|
|
3947
|
-
}
|
|
3948
|
-
const body = JSON.stringify(buildRcEventWriteBody(buildRcSetModelPayload(deps.newUuid(), model)));
|
|
3949
|
-
let answer;
|
|
3950
|
-
try {
|
|
3951
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3952
|
-
} catch (error) {
|
|
3953
|
-
return { ok: false, message: `the door could not reach the API host to set the model on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
3954
|
-
}
|
|
3955
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
4172
|
+
function literalDial(target) {
|
|
4173
|
+
return net2.connect({ host: target.host, port: target.port });
|
|
3956
4174
|
}
|
|
3957
|
-
async function
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
try {
|
|
3968
|
-
answer = await deps.dial.writeEvents(sessionId, rcWriteHeaders(credential), body);
|
|
3969
|
-
} catch (error) {
|
|
3970
|
-
return { ok: false, message: `the door could not reach the API host to set the permission mode on session ${sessionId}: ${error instanceof Error ? error.message : String(error)}` };
|
|
4175
|
+
async function reservedDial(target) {
|
|
4176
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
4177
|
+
const outcome = await dialOnce({ host: target.host, port: target.port, localPort: upstreamLocalPort(), lookup: realAddressLookup });
|
|
4178
|
+
if (outcome.error === null) {
|
|
4179
|
+
return outcome;
|
|
4180
|
+
}
|
|
4181
|
+
const held = outcome.error.code !== void 0 && HELD_PORT_ERRNOS.has(outcome.error.code);
|
|
4182
|
+
if (!held || attempt === UPSTREAM_LOCAL_PORT_COUNT - 1) {
|
|
4183
|
+
return outcome;
|
|
4184
|
+
}
|
|
3971
4185
|
}
|
|
3972
|
-
return rcEventWriteResultFromAnswer(answer);
|
|
3973
4186
|
}
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
import * as https2 from "node:https";
|
|
3977
|
-
var RC_PERMISSION_MODE_LIST = RC_PERMISSION_MODES.join(", ");
|
|
3978
|
-
var CONTROL_PATH_PREFIX = "/__agent-shim/rc";
|
|
3979
|
-
var CONTROL_BODY_CAP_BYTES = 10485760;
|
|
3980
|
-
function answerJson(response, status, body) {
|
|
3981
|
-
const text = JSON.stringify(body);
|
|
3982
|
-
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
3983
|
-
response.end(text);
|
|
4187
|
+
function requestServername(options, fallback) {
|
|
4188
|
+
return "servername" in options && typeof options.servername === "string" && options.servername !== "" ? options.servername : fallback;
|
|
3984
4189
|
}
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
4190
|
+
var ExemptHttpAgent = class extends http.Agent {
|
|
4191
|
+
// A reserved-port dial that has to retry cannot produce its socket synchronously, so it settles first and arrives through the callback (node's documented asynchronous createConnection form); a literal dial has nothing to retry and keeps node's own synchronous shape.
|
|
4192
|
+
createConnection(options, oncreate) {
|
|
4193
|
+
const target = dialTarget(options, HTTP_PORT);
|
|
4194
|
+
if (net2.isIP(target.host) !== 0) {
|
|
4195
|
+
return literalDial(target);
|
|
4196
|
+
}
|
|
4197
|
+
void reservedDial(target).then((outcome) => {
|
|
4198
|
+
oncreate?.(outcome.error, outcome.socket);
|
|
4199
|
+
});
|
|
4200
|
+
return void 0;
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4203
|
+
var ExemptTlsAgent = class extends https2.Agent {
|
|
4204
|
+
// The same split as the plain sibling: a literal dial wraps TLS around the socket node's own agent would have returned, a reserved dial settles (retries included) first and arrives through the callback, which is exactly where node's own agent wraps its own dials.
|
|
4205
|
+
createConnection(options, oncreate) {
|
|
4206
|
+
const target = dialTarget(options, HTTPS_PORT);
|
|
4207
|
+
if (net2.isIP(target.host) !== 0) {
|
|
4208
|
+
return tls.connect({ socket: literalDial(target), servername: requestServername(options, target.host), ...tlsFacts(options) });
|
|
4209
|
+
}
|
|
4210
|
+
void reservedDial(target).then((outcome) => {
|
|
4211
|
+
if (outcome.error !== null) {
|
|
4212
|
+
oncreate?.(outcome.error, outcome.socket);
|
|
3994
4213
|
return;
|
|
3995
4214
|
}
|
|
3996
|
-
|
|
3997
|
-
});
|
|
3998
|
-
request4.on("end", () => {
|
|
3999
|
-
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
4000
|
-
});
|
|
4001
|
-
request4.on("error", () => {
|
|
4002
|
-
reject(new Error("the request body ended early"));
|
|
4215
|
+
oncreate?.(null, tls.connect({ socket: outcome.socket, servername: requestServername(options, target.host), ...tlsFacts(options) }));
|
|
4003
4216
|
});
|
|
4004
|
-
|
|
4217
|
+
return void 0;
|
|
4218
|
+
}
|
|
4219
|
+
};
|
|
4220
|
+
function tlsFacts(options) {
|
|
4221
|
+
const facts = {};
|
|
4222
|
+
if ("ca" in options && Array.isArray(options.ca)) {
|
|
4223
|
+
facts.ca = options.ca;
|
|
4224
|
+
}
|
|
4225
|
+
if ("rejectUnauthorized" in options && typeof options.rejectUnauthorized === "boolean") {
|
|
4226
|
+
facts.rejectUnauthorized = options.rejectUnauthorized;
|
|
4227
|
+
}
|
|
4228
|
+
return facts;
|
|
4005
4229
|
}
|
|
4006
|
-
function
|
|
4007
|
-
const
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
}
|
|
4023
|
-
const url = new URL(request4.url ?? "/", "http://127.0.0.1");
|
|
4024
|
-
const handle = async () => {
|
|
4025
|
-
if (url.pathname === sessionsPath) {
|
|
4026
|
-
if (request4.method !== "GET") {
|
|
4027
|
-
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the session list is a GET" });
|
|
4028
|
-
return;
|
|
4029
|
-
}
|
|
4030
|
-
answerJson(response, HTTP_STATUS.ok, { sessions: deps.list() });
|
|
4031
|
-
return;
|
|
4230
|
+
function realRcEventDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
4231
|
+
const agent = new ExemptTlsAgent();
|
|
4232
|
+
return {
|
|
4233
|
+
writeEvents: async (sessionId, headers, body) => await new Promise((resolve, reject) => {
|
|
4234
|
+
const options = {
|
|
4235
|
+
host: target.host,
|
|
4236
|
+
port: target.port,
|
|
4237
|
+
method: "POST",
|
|
4238
|
+
path: `/v1/code/sessions/${encodeURIComponent(sessionId)}/events`,
|
|
4239
|
+
// 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.
|
|
4240
|
+
headers: { ...headers, host: target.servername ?? target.host, "content-length": String(Buffer.byteLength(body, "utf8")) },
|
|
4241
|
+
agent
|
|
4242
|
+
};
|
|
4243
|
+
options.servername = target.servername ?? target.host;
|
|
4244
|
+
if (target.ca !== void 0) {
|
|
4245
|
+
options.ca = [...target.ca];
|
|
4032
4246
|
}
|
|
4033
|
-
if (
|
|
4034
|
-
|
|
4035
|
-
answerJson(response, HTTP_STATUS.methodNotAllowed, { error: "the status and pending reads are GETs" });
|
|
4036
|
-
return;
|
|
4037
|
-
}
|
|
4038
|
-
const session = url.searchParams.get("session") ?? void 0;
|
|
4039
|
-
if (session !== void 0 && !deps.statusOf(session).some((entry) => entry.id === session)) {
|
|
4040
|
-
answerJson(response, HTTP_STATUS.notFound, { error: `the front door has not observed Remote Control session ${session}: it may never have passed through this door, or it ended or expired (an entry lives only a bounded idle period past its last observed traffic)` });
|
|
4041
|
-
return;
|
|
4042
|
-
}
|
|
4043
|
-
answerJson(response, HTTP_STATUS.ok, url.pathname === statusPath ? { statuses: deps.statusOf(session) } : { pending: deps.pendingOf(session) });
|
|
4044
|
-
return;
|
|
4247
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4248
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4045
4249
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4250
|
+
const request4 = https2.request(options, (response) => {
|
|
4251
|
+
const chunks = [];
|
|
4252
|
+
response.on("data", (chunk) => {
|
|
4253
|
+
chunks.push(chunk);
|
|
4254
|
+
});
|
|
4255
|
+
response.on("end", () => {
|
|
4256
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, body: Buffer.concat(chunks).toString("utf8") });
|
|
4257
|
+
});
|
|
4258
|
+
});
|
|
4259
|
+
request4.once("error", reject);
|
|
4260
|
+
request4.end(body);
|
|
4261
|
+
})
|
|
4262
|
+
};
|
|
4263
|
+
}
|
|
4264
|
+
var NO_CHUNKS = {
|
|
4265
|
+
[Symbol.asyncIterator]: () => ({
|
|
4266
|
+
next: async () => await Promise.resolve({ done: true, value: void 0 })
|
|
4267
|
+
})
|
|
4268
|
+
};
|
|
4269
|
+
function realRcStreamDial(target = { host: CONNECT_INTERCEPT_HOST, port: HTTPS_PORT }) {
|
|
4270
|
+
const agent = new ExemptTlsAgent();
|
|
4271
|
+
const request4 = async (method, path29, headers, body, signal) => await new Promise((resolve, reject) => {
|
|
4272
|
+
const options = {
|
|
4273
|
+
host: target.host,
|
|
4274
|
+
port: target.port,
|
|
4275
|
+
method,
|
|
4276
|
+
path: path29,
|
|
4277
|
+
// 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.
|
|
4278
|
+
headers: { ...headers, host: target.servername ?? target.host, ...body === void 0 ? {} : { "content-length": String(Buffer.byteLength(body, "utf8")) } },
|
|
4279
|
+
agent,
|
|
4280
|
+
signal
|
|
4281
|
+
};
|
|
4282
|
+
options.servername = target.servername ?? target.host;
|
|
4283
|
+
if (target.ca !== void 0) {
|
|
4284
|
+
options.ca = [...target.ca];
|
|
4285
|
+
}
|
|
4286
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4287
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4288
|
+
}
|
|
4289
|
+
const sent = https2.request(options, (response) => {
|
|
4290
|
+
resolve({ status: response.statusCode ?? HTTP_BAD_GATEWAY, response });
|
|
4291
|
+
});
|
|
4292
|
+
sent.once("error", reject);
|
|
4293
|
+
if (body === void 0) {
|
|
4294
|
+
sent.end();
|
|
4295
|
+
} else {
|
|
4296
|
+
sent.end(body);
|
|
4297
|
+
}
|
|
4298
|
+
});
|
|
4299
|
+
const readAll = async (response) => {
|
|
4300
|
+
const chunks = [];
|
|
4301
|
+
for await (const chunk of upstreamChunks(response)) {
|
|
4302
|
+
chunks.push(Buffer.from(chunk));
|
|
4303
|
+
}
|
|
4304
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
4305
|
+
};
|
|
4306
|
+
return {
|
|
4307
|
+
announcePresence: async (sessionId, headers, clientId) => {
|
|
4308
|
+
const body = JSON.stringify({ client_id: clientId, clear: false });
|
|
4309
|
+
const { status, response } = await request4("POST", `/v1/code/sessions/${encodeURIComponent(sessionId)}/client/presence`, { ...headers, "content-type": "application/json" }, body, new AbortController().signal);
|
|
4310
|
+
return { status, body: await readAll(response) };
|
|
4311
|
+
},
|
|
4312
|
+
openStream: async (sessionId, headers, resume, signal) => {
|
|
4313
|
+
const path29 = resume === void 0 ? `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream` : `/v1/code/sessions/${encodeURIComponent(sessionId)}/events/stream?from_sequence_num=${String(resume.fromSequenceNum)}`;
|
|
4314
|
+
const streamHeaders = resume === void 0 ? headers : { ...headers, "last-event-id": String(resume.fromSequenceNum) };
|
|
4315
|
+
const { status, response } = await request4("GET", path29, streamHeaders, void 0, signal);
|
|
4316
|
+
if (!isSuccessful2(status)) {
|
|
4317
|
+
await readAll(response);
|
|
4318
|
+
return { status, chunks: NO_CHUNKS };
|
|
4069
4319
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
if (result.ok) {
|
|
4093
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, request: parsed.request, sequenceNums: result.sequenceNums });
|
|
4094
|
-
return;
|
|
4095
|
-
}
|
|
4096
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4320
|
+
return { status, chunks: response };
|
|
4321
|
+
}
|
|
4322
|
+
};
|
|
4323
|
+
}
|
|
4324
|
+
function realConnectEffects() {
|
|
4325
|
+
const plainAgent = new ExemptHttpAgent();
|
|
4326
|
+
const tlsAgent = new ExemptTlsAgent();
|
|
4327
|
+
const listenOnce = async (port, onSocket) => await new Promise((resolve, reject) => {
|
|
4328
|
+
const accepted = /* @__PURE__ */ new Set();
|
|
4329
|
+
const server = net2.createServer((socket) => {
|
|
4330
|
+
accepted.add(socket);
|
|
4331
|
+
socket.on("close", () => {
|
|
4332
|
+
accepted.delete(socket);
|
|
4333
|
+
});
|
|
4334
|
+
onSocket(socket);
|
|
4335
|
+
});
|
|
4336
|
+
server.once("error", reject);
|
|
4337
|
+
server.listen(port, "127.0.0.1", () => {
|
|
4338
|
+
const address = server.address();
|
|
4339
|
+
const bound = typeof address === "object" && address !== null ? address.port : 0;
|
|
4340
|
+
if (bound === 0) {
|
|
4341
|
+
reject(new Error("could not bind a loopback port"));
|
|
4097
4342
|
return;
|
|
4098
4343
|
}
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4344
|
+
resolve({
|
|
4345
|
+
port: bound,
|
|
4346
|
+
close: async () => {
|
|
4347
|
+
await new Promise((closeResolve) => {
|
|
4348
|
+
server.close(() => {
|
|
4349
|
+
closeResolve(void 0);
|
|
4350
|
+
});
|
|
4351
|
+
for (const socket of accepted) {
|
|
4352
|
+
socket.destroy();
|
|
4353
|
+
}
|
|
4354
|
+
});
|
|
4103
4355
|
}
|
|
4104
|
-
|
|
4356
|
+
});
|
|
4357
|
+
});
|
|
4358
|
+
});
|
|
4359
|
+
return {
|
|
4360
|
+
listenLoopback: async (preferredPort, onSocket) => {
|
|
4361
|
+
if (preferredPort !== void 0) {
|
|
4105
4362
|
try {
|
|
4106
|
-
|
|
4363
|
+
return await listenOnce(preferredPort, onSocket);
|
|
4107
4364
|
} catch {
|
|
4108
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body is not readable JSON within the control body cap" });
|
|
4109
|
-
return;
|
|
4110
|
-
}
|
|
4111
|
-
if (typeof parsed !== "object" || parsed === null || !("session" in parsed) || typeof parsed.session !== "string" || parsed.session === "") {
|
|
4112
|
-
answerJson(response, HTTP_STATUS.badRequest, { error: "the interrupt body must be JSON naming a non-empty session id" });
|
|
4113
|
-
return;
|
|
4114
|
-
}
|
|
4115
|
-
const result = await deps.interrupt(parsed.session);
|
|
4116
|
-
if (result.ok) {
|
|
4117
|
-
answerJson(response, HTTP_STATUS.ok, { session: parsed.session, sequenceNums: result.sequenceNums });
|
|
4118
|
-
return;
|
|
4119
4365
|
}
|
|
4120
|
-
answerJson(response, HTTP_STATUS.badGateway, { error: result.message });
|
|
4121
|
-
return;
|
|
4122
4366
|
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4367
|
+
return await listenOnce(0, onSocket);
|
|
4368
|
+
},
|
|
4369
|
+
createTlsAcceptor: (leaf, onSecure, alpnProtocols) => {
|
|
4370
|
+
let pendingContext = "";
|
|
4371
|
+
const server = tls.createServer(
|
|
4372
|
+
{ key: leaf.keyPem, cert: leaf.certPem, ...alpnProtocols === void 0 ? {} : { ALPNProtocols: [...alpnProtocols] } },
|
|
4373
|
+
(secure) => {
|
|
4374
|
+
onSecure(secure, pendingContext);
|
|
4127
4375
|
}
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4376
|
+
);
|
|
4377
|
+
server.on("clientError", (_error, socket) => {
|
|
4378
|
+
socket.destroy();
|
|
4379
|
+
});
|
|
4380
|
+
return {
|
|
4381
|
+
// tls.Server is a net.Server whose connection listener wraps the raw socket in the TLS handshake, and emitting the event by hand is what runs that listener on a socket this process already owns (the CONNECT half of the connection) rather than one the server accepted itself.
|
|
4382
|
+
accept: (socket, context) => {
|
|
4383
|
+
pendingContext = context;
|
|
4384
|
+
server.emit("connection", socket);
|
|
4385
|
+
},
|
|
4386
|
+
close: () => {
|
|
4387
|
+
server.close();
|
|
4134
4388
|
}
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4389
|
+
};
|
|
4390
|
+
},
|
|
4391
|
+
createSniTlsAcceptor: (hosts, leafFor, onSecure) => {
|
|
4392
|
+
const contexts = new Map(
|
|
4393
|
+
hosts.map((host) => {
|
|
4394
|
+
const leaf = leafFor(host);
|
|
4395
|
+
return [host, tls.createSecureContext({ key: leaf.keyPem, cert: leaf.certPem })];
|
|
4396
|
+
})
|
|
4397
|
+
);
|
|
4398
|
+
const server = tls.createServer(
|
|
4399
|
+
{
|
|
4400
|
+
// No default key or cert exists, so the SNI selection is the only source of credentials: an unserved name fails its handshake here, and a client that sent no SNI at all never reaches the selection and fails for want of any certificate, which is exactly the fail-closed shape this surface wants.
|
|
4401
|
+
SNICallback: (servername, callback) => {
|
|
4402
|
+
const context = contexts.get(servername);
|
|
4403
|
+
callback(context === void 0 ? new Error(`the transparent surface serves no host named ${servername}`) : null, context);
|
|
4404
|
+
}
|
|
4405
|
+
},
|
|
4406
|
+
(secure) => {
|
|
4407
|
+
onSecure(secure, typeof secure.servername === "string" ? secure.servername : "");
|
|
4138
4408
|
}
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4409
|
+
);
|
|
4410
|
+
server.on("clientError", (_error, socket) => {
|
|
4411
|
+
socket.destroy();
|
|
4412
|
+
});
|
|
4413
|
+
return {
|
|
4414
|
+
// The same hand-off the single-leaf acceptor uses: emitting the connection event runs the TLS server's own connection listener on a socket this process already owns (the redirect half of the connection) rather than one the server accepted itself.
|
|
4415
|
+
accept: (socket) => {
|
|
4416
|
+
server.emit("connection", socket);
|
|
4417
|
+
},
|
|
4418
|
+
close: () => {
|
|
4419
|
+
server.close();
|
|
4143
4420
|
}
|
|
4144
|
-
|
|
4145
|
-
|
|
4421
|
+
};
|
|
4422
|
+
},
|
|
4423
|
+
createHttpSession: (handler, onUpgrade) => {
|
|
4424
|
+
const server = http.createServer(handler);
|
|
4425
|
+
server.on("clientError", (_error, socket) => {
|
|
4426
|
+
socket.destroy();
|
|
4427
|
+
});
|
|
4428
|
+
if (onUpgrade !== void 0) {
|
|
4429
|
+
server.on("upgrade", (request4, socket, head) => {
|
|
4430
|
+
onUpgrade(request4, socket, head);
|
|
4431
|
+
});
|
|
4146
4432
|
}
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4433
|
+
return {
|
|
4434
|
+
serve: (socket) => {
|
|
4435
|
+
server.emit("connection", socket);
|
|
4436
|
+
},
|
|
4437
|
+
close: () => {
|
|
4438
|
+
server.close();
|
|
4439
|
+
server.closeAllConnections();
|
|
4151
4440
|
}
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4441
|
+
};
|
|
4442
|
+
},
|
|
4443
|
+
connectTlsUpstream: async (host, port, clientAlpn) => {
|
|
4444
|
+
const dialled = await reservedDial({ host, port });
|
|
4445
|
+
if (dialled.error !== null) {
|
|
4446
|
+
throw dialled.error;
|
|
4447
|
+
}
|
|
4448
|
+
return await new Promise((resolve, reject) => {
|
|
4449
|
+
const secure = tls.connect({ socket: dialled.socket, servername: host, ...clientAlpn === void 0 ? {} : { ALPNProtocols: [clientAlpn] } });
|
|
4450
|
+
secure.once("secureConnect", () => {
|
|
4451
|
+
resolve(secure);
|
|
4452
|
+
});
|
|
4453
|
+
secure.once("error", reject);
|
|
4454
|
+
});
|
|
4455
|
+
},
|
|
4456
|
+
connectTcp: async (host, port) => await new Promise((resolve, reject) => {
|
|
4457
|
+
const socket = net2.connect({ host, port });
|
|
4458
|
+
socket.once("connect", () => {
|
|
4459
|
+
resolve(socket);
|
|
4460
|
+
});
|
|
4461
|
+
socket.once("error", reject);
|
|
4462
|
+
}),
|
|
4463
|
+
forwardHttp: (target, request4, response, observer) => {
|
|
4464
|
+
const headers = forwardableHeaders(request4.headers);
|
|
4465
|
+
const options = {
|
|
4466
|
+
host: target.host,
|
|
4467
|
+
port: target.port,
|
|
4468
|
+
lookup: realAddressLookup,
|
|
4469
|
+
method: request4.method,
|
|
4470
|
+
path: request4.url,
|
|
4471
|
+
headers,
|
|
4472
|
+
agent: target.tls ? tlsAgent : plainAgent
|
|
4473
|
+
};
|
|
4474
|
+
if (target.tls) {
|
|
4475
|
+
options.servername = target.servername ?? target.host;
|
|
4476
|
+
if (target.ca !== void 0) {
|
|
4477
|
+
options.ca = [...target.ca];
|
|
4158
4478
|
}
|
|
4159
|
-
if (
|
|
4160
|
-
|
|
4161
|
-
return;
|
|
4479
|
+
if (target.rejectUnauthorized !== void 0) {
|
|
4480
|
+
options.rejectUnauthorized = target.rejectUnauthorized;
|
|
4162
4481
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4482
|
+
}
|
|
4483
|
+
const onUpstreamResponse = (upstreamResponse) => {
|
|
4484
|
+
observer?.onResponse(upstreamResponse.statusCode ?? HTTP_BAD_GATEWAY, upstreamResponse.headers);
|
|
4485
|
+
response.writeHead(upstreamResponse.statusCode ?? HTTP_BAD_GATEWAY, forwardableHeaders(upstreamResponse.headers));
|
|
4486
|
+
response.flushHeaders();
|
|
4487
|
+
if (observer !== void 0) {
|
|
4488
|
+
upstreamResponse.on("data", (chunk) => {
|
|
4489
|
+
observer.onResponseChunk(chunk);
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
upstreamResponse.pipe(response);
|
|
4493
|
+
};
|
|
4494
|
+
const upstream = target.tls ? https2.request(options, onUpstreamResponse) : http.request(options, onUpstreamResponse);
|
|
4495
|
+
upstream.on("error", (error) => {
|
|
4496
|
+
if (response.headersSent) {
|
|
4497
|
+
response.destroy(error);
|
|
4166
4498
|
return;
|
|
4167
4499
|
}
|
|
4168
|
-
|
|
4169
|
-
|
|
4500
|
+
const body = `agent-shim headroom: upstream unreachable (${error.message})`;
|
|
4501
|
+
response.writeHead(HTTP_BAD_GATEWAY, { "content-type": "text/plain", "content-length": String(body.length) });
|
|
4502
|
+
response.end(body);
|
|
4503
|
+
});
|
|
4504
|
+
if (observer !== void 0) {
|
|
4505
|
+
request4.on("data", (chunk) => {
|
|
4506
|
+
observer.onRequestChunk(chunk);
|
|
4507
|
+
});
|
|
4508
|
+
if (observer.onRequestEnd !== void 0) {
|
|
4509
|
+
request4.on("end", () => {
|
|
4510
|
+
observer.onRequestEnd?.();
|
|
4511
|
+
});
|
|
4512
|
+
}
|
|
4513
|
+
response.on("close", () => {
|
|
4514
|
+
observer.onEnd();
|
|
4515
|
+
});
|
|
4170
4516
|
}
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4517
|
+
request4.pipe(upstream);
|
|
4518
|
+
request4.socket.setNoDelay(true);
|
|
4519
|
+
upstream.on("socket", (socket) => {
|
|
4520
|
+
socket.setNoDelay(true);
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4176
4523
|
};
|
|
4177
4524
|
}
|
|
4178
|
-
|
|
4525
|
+
|
|
4526
|
+
// src/frontdoor/rcApi.ts
|
|
4527
|
+
import { createORPCClient, ORPCError } from "@orpc/client";
|
|
4528
|
+
import { RPCLink } from "@orpc/client/fetch";
|
|
4529
|
+
import { eventIterator, os, withEventMeta } from "@orpc/server";
|
|
4530
|
+
import { BodyLimitPlugin, RPCHandler } from "@orpc/server/node";
|
|
4531
|
+
import { Agent as Agent3, fetch as undiciFetch } from "undici";
|
|
4532
|
+
|
|
4533
|
+
// src/frontdoor/rcSchemas.ts
|
|
4534
|
+
import { z as z4 } from "zod";
|
|
4535
|
+
var RcPermissionModeSchema = z4.enum(RC_PERMISSION_MODES);
|
|
4536
|
+
var RcSessionSummarySchema = z4.strictObject({
|
|
4537
|
+
id: z4.string(),
|
|
4538
|
+
createdAt: z4.number(),
|
|
4539
|
+
lastSeenAt: z4.number()
|
|
4540
|
+
});
|
|
4541
|
+
function workerFactSchema(value) {
|
|
4542
|
+
return z4.strictObject({ value, observedAt: z4.number() });
|
|
4543
|
+
}
|
|
4544
|
+
var RcPendingRequestSummarySchema = z4.strictObject({
|
|
4545
|
+
sessionId: z4.string(),
|
|
4546
|
+
requestId: z4.string(),
|
|
4547
|
+
type: z4.string(),
|
|
4548
|
+
summary: z4.string(),
|
|
4549
|
+
observedAt: z4.number()
|
|
4550
|
+
});
|
|
4551
|
+
var RcSessionStatusSchema = z4.strictObject({
|
|
4552
|
+
...RcSessionSummarySchema.shape,
|
|
4553
|
+
workerState: workerFactSchema(z4.string()).optional(),
|
|
4554
|
+
workerIdleSeconds: workerFactSchema(z4.number()).optional(),
|
|
4555
|
+
pending: z4.readonly(z4.array(RcPendingRequestSummarySchema))
|
|
4556
|
+
});
|
|
4557
|
+
var RcStreamEnvelopeSchema = z4.looseObject({
|
|
4558
|
+
/** The envelope's own event id, when the host sent one. */
|
|
4559
|
+
event_id: z4.string().optional(),
|
|
4560
|
+
/** The envelope's event type, the discriminator a consumer switches on (`control_request`, `user`, and kin). */
|
|
4561
|
+
event_type: z4.string(),
|
|
4562
|
+
/** The sequence number the host assigned, the value a resume continues after. */
|
|
4563
|
+
sequence_num: z4.int(),
|
|
4564
|
+
/** Who emitted the event (`worker` for the CLI's half, a client id for a client's). */
|
|
4565
|
+
source: z4.string(),
|
|
4566
|
+
/** The event's payload, carried verbatim: its shape is the payload family's, narrowed by whichever consumer needs it. Optional in the schema's own type because the door forwards the host's envelope as it arrived, asserting only the fields it relies on itself. */
|
|
4567
|
+
payload: z4.unknown().optional(),
|
|
4568
|
+
/** When the host created the event; the protocol's own type for this field is not established here, so it is forwarded rather than asserted. */
|
|
4569
|
+
created_at: z4.unknown().optional()
|
|
4570
|
+
});
|
|
4571
|
+
var RcStreamEventSchema = z4.strictObject({
|
|
4572
|
+
session: z4.string(),
|
|
4573
|
+
envelope: RcStreamEnvelopeSchema
|
|
4574
|
+
});
|
|
4575
|
+
var RcSessionQuerySchema = z4.strictObject({
|
|
4576
|
+
session: z4.string().min(1).optional()
|
|
4577
|
+
});
|
|
4578
|
+
var RcSendInputSchema = z4.strictObject({
|
|
4579
|
+
session: z4.string().min(1),
|
|
4580
|
+
text: z4.string().min(1)
|
|
4581
|
+
});
|
|
4582
|
+
var RcAnswerInputSchema = z4.strictObject({
|
|
4583
|
+
session: z4.string().min(1),
|
|
4584
|
+
request: z4.string().min(1),
|
|
4585
|
+
approve: z4.boolean(),
|
|
4586
|
+
text: z4.string().optional()
|
|
4587
|
+
}).refine((input) => !input.approve || input.text === void 0 || input.text === "", { message: "an approval carries no text (the protocol's allow result has no message field); text is the denial message" });
|
|
4588
|
+
var RcInterruptInputSchema = z4.strictObject({
|
|
4589
|
+
session: z4.string().min(1)
|
|
4590
|
+
});
|
|
4591
|
+
var RcSetModelInputSchema = z4.strictObject({
|
|
4592
|
+
session: z4.string().min(1),
|
|
4593
|
+
model: z4.string().min(1)
|
|
4594
|
+
});
|
|
4595
|
+
var RcSetPermissionModeInputSchema = z4.strictObject({
|
|
4596
|
+
session: z4.string().min(1),
|
|
4597
|
+
mode: RcPermissionModeSchema
|
|
4598
|
+
});
|
|
4599
|
+
var RcListOutputSchema = z4.strictObject({ sessions: z4.readonly(z4.array(RcSessionSummarySchema)) });
|
|
4600
|
+
var RcStatusOutputSchema = z4.strictObject({ statuses: z4.readonly(z4.array(RcSessionStatusSchema)) });
|
|
4601
|
+
var RcPendingOutputSchema = z4.strictObject({ pending: z4.readonly(z4.array(RcPendingRequestSummarySchema)) });
|
|
4602
|
+
var RcWriteOutputSchema = z4.strictObject({
|
|
4603
|
+
session: z4.string(),
|
|
4604
|
+
sequenceNums: z4.readonly(z4.array(z4.number()))
|
|
4605
|
+
});
|
|
4606
|
+
var RcAnswerOutputSchema = z4.strictObject({
|
|
4607
|
+
session: z4.string(),
|
|
4608
|
+
request: z4.string(),
|
|
4609
|
+
sequenceNums: z4.readonly(z4.array(z4.number()))
|
|
4610
|
+
});
|
|
4611
|
+
|
|
4612
|
+
// src/frontdoor/rcApi.ts
|
|
4613
|
+
var RC_ORPC_PATH_PREFIX = "/__agent-shim/orpc";
|
|
4614
|
+
var RC_SUBSCRIBER_BUFFER_EVENTS = 100;
|
|
4615
|
+
function createRcApiRouter(deps) {
|
|
4616
|
+
const authed = os.$context().use(({ context, next }) => {
|
|
4617
|
+
const presented = context.headers.authorization;
|
|
4618
|
+
const bearerPrefix = "bearer ".length;
|
|
4619
|
+
const token = typeof presented === "string" && presented.slice(0, bearerPrefix).toLowerCase() === "bearer " ? presented.slice(bearerPrefix) : void 0;
|
|
4620
|
+
if (token === void 0 || !isLiveCapability(token, [deps.expectedToken])) {
|
|
4621
|
+
throw new ORPCError("UNAUTHORIZED", { message: "the front door's typed API demands this generation's control token as a Bearer credential" });
|
|
4622
|
+
}
|
|
4623
|
+
return next();
|
|
4624
|
+
});
|
|
4625
|
+
const delivered = (sessionId, result) => {
|
|
4626
|
+
if (!result.ok) {
|
|
4627
|
+
throw new ORPCError("BAD_GATEWAY", { message: result.message });
|
|
4628
|
+
}
|
|
4629
|
+
return result.sequenceNums;
|
|
4630
|
+
};
|
|
4179
4631
|
return {
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
(
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4632
|
+
rc: {
|
|
4633
|
+
list: authed.output(RcListOutputSchema).handler(() => ({ sessions: deps.list() })),
|
|
4634
|
+
status: authed.input(RcSessionQuerySchema).output(RcStatusOutputSchema).handler(({ input }) => {
|
|
4635
|
+
const statuses = deps.statusOf(input.session);
|
|
4636
|
+
if (input.session !== void 0 && !statuses.some((entry) => entry.id === input.session)) {
|
|
4637
|
+
throw new ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
|
|
4638
|
+
}
|
|
4639
|
+
return { statuses };
|
|
4640
|
+
}),
|
|
4641
|
+
pending: authed.input(RcSessionQuerySchema).output(RcPendingOutputSchema).handler(({ input }) => {
|
|
4642
|
+
const pending = deps.pendingOf(input.session);
|
|
4643
|
+
if (input.session !== void 0 && deps.statusOf(input.session).length === 0) {
|
|
4644
|
+
throw new ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
|
|
4645
|
+
}
|
|
4646
|
+
return { pending };
|
|
4647
|
+
}),
|
|
4648
|
+
send: authed.input(RcSendInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.inject(input.session, input.text)) })),
|
|
4649
|
+
answer: authed.input(RcAnswerInputSchema).output(RcAnswerOutputSchema).handler(async ({ input }) => {
|
|
4650
|
+
const decision = { approve: input.approve, message: input.approve || input.text === void 0 ? void 0 : input.text };
|
|
4651
|
+
return { session: input.session, request: input.request, sequenceNums: delivered(input.session, await deps.answer(input.session, input.request, decision)) };
|
|
4652
|
+
}),
|
|
4653
|
+
interrupt: authed.input(RcInterruptInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.interrupt(input.session)) })),
|
|
4654
|
+
setModel: authed.input(RcSetModelInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setModel(input.session, input.model)) })),
|
|
4655
|
+
setPermissionMode: authed.input(RcSetPermissionModeInputSchema).output(RcWriteOutputSchema).handler(async ({ input }) => ({ session: input.session, sequenceNums: delivered(input.session, await deps.setPermissionMode(input.session, input.mode)) })),
|
|
4656
|
+
subscribe: authed.input(RcSessionQuerySchema).output(eventIterator(RcStreamEventSchema)).handler(async function* ({ input, signal }) {
|
|
4657
|
+
const queue = [];
|
|
4658
|
+
let wake;
|
|
4659
|
+
const detach = deps.fanout.subscribe(input.session, (event) => {
|
|
4660
|
+
if (queue.length >= RC_SUBSCRIBER_BUFFER_EVENTS) {
|
|
4661
|
+
queue.shift();
|
|
4662
|
+
}
|
|
4663
|
+
queue.push(event);
|
|
4664
|
+
wake?.();
|
|
4665
|
+
});
|
|
4666
|
+
const ended = new Promise((resolve) => {
|
|
4667
|
+
signal?.addEventListener("abort", () => {
|
|
4668
|
+
resolve();
|
|
4669
|
+
}, { once: true });
|
|
4670
|
+
});
|
|
4671
|
+
try {
|
|
4672
|
+
for (; ; ) {
|
|
4673
|
+
signal?.throwIfAborted();
|
|
4674
|
+
const event = queue.shift();
|
|
4675
|
+
if (event !== void 0) {
|
|
4676
|
+
yield withEventMeta(event, { id: String(event.envelope.sequence_num) });
|
|
4677
|
+
continue;
|
|
4678
|
+
}
|
|
4679
|
+
await Promise.race([
|
|
4680
|
+
new Promise((resolve) => {
|
|
4681
|
+
wake = resolve;
|
|
4682
|
+
}),
|
|
4683
|
+
ended
|
|
4684
|
+
]);
|
|
4685
|
+
}
|
|
4686
|
+
} finally {
|
|
4687
|
+
detach();
|
|
4199
4688
|
}
|
|
4200
|
-
)
|
|
4201
|
-
|
|
4202
|
-
if (options.body === void 0) {
|
|
4203
|
-
request4.end();
|
|
4204
|
-
} else {
|
|
4205
|
-
request4.end(options.body);
|
|
4206
|
-
}
|
|
4207
|
-
})
|
|
4689
|
+
})
|
|
4690
|
+
}
|
|
4208
4691
|
};
|
|
4209
4692
|
}
|
|
4210
|
-
function
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4693
|
+
function createRcApiNodeHandler(deps) {
|
|
4694
|
+
const handler = new RPCHandler(createRcApiRouter(deps), { plugins: [new BodyLimitPlugin({ maxBodySize: CONTROL_BODY_CAP_BYTES })] });
|
|
4695
|
+
return {
|
|
4696
|
+
pathPrefix: RC_ORPC_PATH_PREFIX,
|
|
4697
|
+
handle: async (request4, response) => await handler.handle(request4, response, { context: { headers: request4.headers }, prefix: RC_ORPC_PATH_PREFIX })
|
|
4698
|
+
};
|
|
4699
|
+
}
|
|
4700
|
+
function frontDoorRcApiClient(port, ca, token) {
|
|
4701
|
+
const dispatcher = new Agent3({ connect: { ca } });
|
|
4702
|
+
const link = new RPCLink({
|
|
4703
|
+
url: `https://127.0.0.1:${String(port)}${RC_ORPC_PATH_PREFIX}`,
|
|
4704
|
+
headers: { authorization: `Bearer ${token}` },
|
|
4705
|
+
// undici's own Request and Response types and the global ones are distinct declarations of the same standard shapes (the package's bundled types sit beside @types/node's own), and its fetch refuses the global Request instance outright, so the call is rebuilt from the request's parts and the answer re-wrapped through the global constructors, never cast between the two. The request body is one bounded JSON object (the same protocol cap the handler enforces), so reading it whole costs nothing; it is the answer that streams.
|
|
4706
|
+
fetch: async (request4, _init, options) => {
|
|
4707
|
+
const body = request4.body === null ? void 0 : Buffer.from(await request4.arrayBuffer());
|
|
4708
|
+
const answered = await undiciFetch(request4.url, {
|
|
4709
|
+
method: request4.method,
|
|
4710
|
+
headers: [...request4.headers],
|
|
4711
|
+
...body === void 0 ? {} : { body },
|
|
4712
|
+
dispatcher,
|
|
4713
|
+
...options.signal === void 0 ? {} : { signal: options.signal }
|
|
4714
|
+
});
|
|
4715
|
+
return new Response(answered.body, { status: answered.status, statusText: answered.statusText, headers: [...answered.headers] });
|
|
4215
4716
|
}
|
|
4216
|
-
}
|
|
4217
|
-
|
|
4218
|
-
}
|
|
4219
|
-
return void 0;
|
|
4717
|
+
});
|
|
4718
|
+
return createORPCClient(link);
|
|
4220
4719
|
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
return
|
|
4720
|
+
|
|
4721
|
+
// src/frontdoor/rcStream.ts
|
|
4722
|
+
var RC_STREAM_EVENT_NAME = "client_event";
|
|
4723
|
+
var SSE_BYTE_ORDER_MARK = 65279;
|
|
4724
|
+
var RC_STREAM_BACKOFF_MS = 45e3;
|
|
4725
|
+
var SUCCESS_STATUS_MAX_EXCLUSIVE3 = 300;
|
|
4726
|
+
function isSuccessful3(status) {
|
|
4727
|
+
return status >= HTTP_STATUS.ok && status < SUCCESS_STATUS_MAX_EXCLUSIVE3;
|
|
4728
|
+
}
|
|
4729
|
+
function createSseParser() {
|
|
4730
|
+
let buffer = "";
|
|
4731
|
+
let eventName;
|
|
4732
|
+
let eventId;
|
|
4733
|
+
let dataLines = [];
|
|
4734
|
+
let seenAnyText = false;
|
|
4735
|
+
return {
|
|
4736
|
+
write: (text) => {
|
|
4737
|
+
let incoming = text;
|
|
4738
|
+
if (!seenAnyText) {
|
|
4739
|
+
seenAnyText = true;
|
|
4740
|
+
incoming = incoming.charCodeAt(0) === SSE_BYTE_ORDER_MARK ? incoming.slice(1) : incoming;
|
|
4741
|
+
}
|
|
4742
|
+
buffer += incoming;
|
|
4743
|
+
const completed = [];
|
|
4744
|
+
for (; ; ) {
|
|
4745
|
+
const carriage = buffer.indexOf("\r");
|
|
4746
|
+
const lineFeed = buffer.indexOf("\n");
|
|
4747
|
+
const end = carriage === -1 ? lineFeed : lineFeed === -1 ? carriage : carriage < lineFeed ? carriage : lineFeed;
|
|
4748
|
+
if (end === -1) {
|
|
4749
|
+
break;
|
|
4750
|
+
}
|
|
4751
|
+
if (buffer[end] === "\r" && end === buffer.length - 1) {
|
|
4752
|
+
break;
|
|
4753
|
+
}
|
|
4754
|
+
const after = buffer[end] === "\r" && buffer[end + 1] === "\n" ? end + 2 : end + 1;
|
|
4755
|
+
const line = buffer.slice(0, end);
|
|
4756
|
+
buffer = buffer.slice(after);
|
|
4757
|
+
if (line === "") {
|
|
4758
|
+
if (dataLines.length > 0) {
|
|
4759
|
+
completed.push({ event: eventName, id: eventId, data: dataLines.join("\n") });
|
|
4760
|
+
}
|
|
4761
|
+
eventName = void 0;
|
|
4762
|
+
dataLines = [];
|
|
4763
|
+
continue;
|
|
4764
|
+
}
|
|
4765
|
+
if (line.startsWith(":")) {
|
|
4766
|
+
continue;
|
|
4767
|
+
}
|
|
4768
|
+
const colon = line.indexOf(":");
|
|
4769
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
4770
|
+
const value = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
4771
|
+
if (field === "event") {
|
|
4772
|
+
eventName = value;
|
|
4773
|
+
} else if (field === "data") {
|
|
4774
|
+
dataLines.push(value);
|
|
4775
|
+
} else if (field === "id") {
|
|
4776
|
+
eventId = value;
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4779
|
+
return completed;
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4229
4782
|
}
|
|
4230
|
-
function
|
|
4231
|
-
|
|
4232
|
-
return false;
|
|
4233
|
-
}
|
|
4234
|
-
if (!("value" in value) || !("observedAt" in value)) {
|
|
4235
|
-
return false;
|
|
4236
|
-
}
|
|
4237
|
-
return valueIs(value.value) && typeof value.observedAt === "number";
|
|
4783
|
+
function isRecord3(value) {
|
|
4784
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4238
4785
|
}
|
|
4239
|
-
function
|
|
4240
|
-
if (
|
|
4241
|
-
return
|
|
4242
|
-
}
|
|
4243
|
-
if (!("pending" in value) || !Array.isArray(value.pending)) {
|
|
4244
|
-
return false;
|
|
4245
|
-
}
|
|
4246
|
-
if ("workerState" in value && value.workerState !== void 0 && !isRcWorkerFact(value.workerState, (candidate) => typeof candidate === "string")) {
|
|
4247
|
-
return false;
|
|
4786
|
+
function sequenceNumOf(value) {
|
|
4787
|
+
if (typeof value === "number" && Number.isFinite(value) && Number.isInteger(value)) {
|
|
4788
|
+
return value;
|
|
4248
4789
|
}
|
|
4249
|
-
if (
|
|
4250
|
-
return
|
|
4790
|
+
if (typeof value === "string" && /^[0-9]+$/.test(value)) {
|
|
4791
|
+
return Number(value);
|
|
4251
4792
|
}
|
|
4252
|
-
return
|
|
4793
|
+
return void 0;
|
|
4253
4794
|
}
|
|
4254
|
-
function
|
|
4255
|
-
if (
|
|
4256
|
-
return
|
|
4257
|
-
}
|
|
4258
|
-
if (!("sessionId" in value) || !("requestId" in value) || !("type" in value) || !("summary" in value) || !("observedAt" in value)) {
|
|
4259
|
-
return false;
|
|
4795
|
+
function parseRcStreamEnvelope(parsed) {
|
|
4796
|
+
if (parsed.event !== RC_STREAM_EVENT_NAME) {
|
|
4797
|
+
return void 0;
|
|
4260
4798
|
}
|
|
4261
|
-
|
|
4262
|
-
}
|
|
4263
|
-
function sequenceNumsOfBody(body) {
|
|
4264
|
-
let parsed;
|
|
4799
|
+
let data;
|
|
4265
4800
|
try {
|
|
4266
|
-
|
|
4801
|
+
data = JSON.parse(parsed.data);
|
|
4267
4802
|
} catch {
|
|
4268
4803
|
return void 0;
|
|
4269
4804
|
}
|
|
4270
|
-
if (
|
|
4805
|
+
if (!isRecord3(data)) {
|
|
4271
4806
|
return void 0;
|
|
4272
4807
|
}
|
|
4273
|
-
const
|
|
4274
|
-
const
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
}
|
|
4279
|
-
sequenceNums.push(value);
|
|
4808
|
+
const eventType = data.event_type;
|
|
4809
|
+
const sequenceNum = sequenceNumOf(data.sequence_num);
|
|
4810
|
+
const source = data.source;
|
|
4811
|
+
if (typeof eventType !== "string" || sequenceNum === void 0 || typeof source !== "string") {
|
|
4812
|
+
return void 0;
|
|
4280
4813
|
}
|
|
4281
|
-
return
|
|
4814
|
+
return { ...data, event_type: eventType, sequence_num: sequenceNum, source };
|
|
4282
4815
|
}
|
|
4283
|
-
function
|
|
4284
|
-
const
|
|
4285
|
-
const
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4816
|
+
function createRcEventFanout() {
|
|
4817
|
+
const perSession = /* @__PURE__ */ new Map();
|
|
4818
|
+
const every = /* @__PURE__ */ new Set();
|
|
4819
|
+
return {
|
|
4820
|
+
publish: (event) => {
|
|
4821
|
+
for (const listener of every) {
|
|
4822
|
+
listener(event);
|
|
4823
|
+
}
|
|
4824
|
+
for (const listener of perSession.get(event.session) ?? []) {
|
|
4825
|
+
listener(event);
|
|
4826
|
+
}
|
|
4827
|
+
},
|
|
4828
|
+
subscribe: (session, listener) => {
|
|
4829
|
+
if (session === void 0) {
|
|
4830
|
+
every.add(listener);
|
|
4831
|
+
return () => {
|
|
4832
|
+
every.delete(listener);
|
|
4833
|
+
};
|
|
4834
|
+
}
|
|
4835
|
+
const listeners = perSession.get(session) ?? /* @__PURE__ */ new Set();
|
|
4836
|
+
perSession.set(session, listeners);
|
|
4837
|
+
listeners.add(listener);
|
|
4838
|
+
return () => {
|
|
4839
|
+
listeners.delete(listener);
|
|
4840
|
+
if (listeners.size === 0) {
|
|
4841
|
+
perSession.delete(session);
|
|
4842
|
+
}
|
|
4843
|
+
};
|
|
4290
4844
|
}
|
|
4291
4845
|
};
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
const sessions = [];
|
|
4307
|
-
for (const value of candidates) {
|
|
4308
|
-
if (!isRcSessionSummary(value)) {
|
|
4309
|
-
throw new Error(`${what}: the door's answer named no sessions list: ${answer.body}`);
|
|
4846
|
+
}
|
|
4847
|
+
function createRcStreamHub(deps) {
|
|
4848
|
+
const attachments = /* @__PURE__ */ new Map();
|
|
4849
|
+
const birth = (sessionId) => {
|
|
4850
|
+
const controller = new AbortController();
|
|
4851
|
+
let stopped = false;
|
|
4852
|
+
return {
|
|
4853
|
+
sessionId,
|
|
4854
|
+
clientId: deps.newClientId(),
|
|
4855
|
+
controller,
|
|
4856
|
+
isStopped: () => stopped,
|
|
4857
|
+
stop: () => {
|
|
4858
|
+
stopped = true;
|
|
4859
|
+
controller.abort();
|
|
4310
4860
|
}
|
|
4311
|
-
|
|
4312
|
-
}
|
|
4313
|
-
return sessions;
|
|
4861
|
+
};
|
|
4314
4862
|
};
|
|
4315
|
-
const
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
let parsed;
|
|
4320
|
-
try {
|
|
4321
|
-
parsed = JSON.parse(answer.body);
|
|
4322
|
-
} catch {
|
|
4323
|
-
throw new Error(`${what}: the door's answer was not JSON: ${answer.body}`);
|
|
4863
|
+
const streamHeaders = (credential) => {
|
|
4864
|
+
const headers = { accept: "text/event-stream" };
|
|
4865
|
+
if (credential.authorization !== void 0) {
|
|
4866
|
+
headers.authorization = credential.authorization;
|
|
4324
4867
|
}
|
|
4325
|
-
if (
|
|
4326
|
-
|
|
4868
|
+
if (credential.anthropicVersion !== void 0) {
|
|
4869
|
+
headers["anthropic-version"] = credential.anthropicVersion;
|
|
4327
4870
|
}
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
if (!isRcSessionStatus(value)) {
|
|
4331
|
-
throw new Error(`${what}: the door's answer named no status list: ${answer.body}`);
|
|
4332
|
-
}
|
|
4333
|
-
statuses.push(value);
|
|
4871
|
+
if (credential.anthropicClientPlatform !== void 0) {
|
|
4872
|
+
headers["anthropic-client-platform"] = credential.anthropicClientPlatform;
|
|
4334
4873
|
}
|
|
4335
|
-
return
|
|
4874
|
+
return headers;
|
|
4336
4875
|
};
|
|
4337
|
-
const
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4876
|
+
const attempt = async (attachment, credential) => {
|
|
4877
|
+
const headers = streamHeaders(credential);
|
|
4878
|
+
const presence = await deps.dial.announcePresence(attachment.sessionId, headers, attachment.clientId);
|
|
4879
|
+
if (presence.status === HTTP_STATUS.unauthorized) {
|
|
4880
|
+
return "unauthorized";
|
|
4881
|
+
}
|
|
4882
|
+
if (!isSuccessful3(presence.status)) {
|
|
4883
|
+
throw new Error(`the presence call answered HTTP ${String(presence.status)}: ${presence.body}`);
|
|
4884
|
+
}
|
|
4885
|
+
const cursor = deps.sequenceNumOf(attachment.sessionId);
|
|
4886
|
+
const resume = cursor === void 0 ? void 0 : { fromSequenceNum: cursor };
|
|
4887
|
+
const answer = await deps.dial.openStream(attachment.sessionId, headers, resume, attachment.controller.signal);
|
|
4888
|
+
if (answer.status === HTTP_STATUS.unauthorized) {
|
|
4889
|
+
return "unauthorized";
|
|
4346
4890
|
}
|
|
4347
|
-
if (
|
|
4348
|
-
throw new Error(
|
|
4891
|
+
if (!isSuccessful3(answer.status)) {
|
|
4892
|
+
throw new Error(`the client read stream answered HTTP ${String(answer.status)}`);
|
|
4349
4893
|
}
|
|
4350
|
-
const
|
|
4351
|
-
for (const
|
|
4352
|
-
|
|
4353
|
-
|
|
4894
|
+
const parser = createSseParser();
|
|
4895
|
+
for await (const chunk of answer.chunks) {
|
|
4896
|
+
attachment.controller.signal.throwIfAborted();
|
|
4897
|
+
for (const parsed of parser.write(Buffer.from(chunk).toString("utf8"))) {
|
|
4898
|
+
const envelope = parseRcStreamEnvelope(parsed);
|
|
4899
|
+
if (envelope === void 0) {
|
|
4900
|
+
continue;
|
|
4901
|
+
}
|
|
4902
|
+
deps.fileStreamEvent(attachment.sessionId, envelope);
|
|
4903
|
+
deps.fanout.publish({ session: attachment.sessionId, envelope });
|
|
4354
4904
|
}
|
|
4355
|
-
pending.push(value);
|
|
4356
4905
|
}
|
|
4357
|
-
return
|
|
4906
|
+
return "dropped";
|
|
4358
4907
|
};
|
|
4359
|
-
const
|
|
4360
|
-
let
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
return { ok: false, message: `could not reach the front door's control listener: ${error instanceof Error ? error.message : String(error)}` };
|
|
4365
|
-
}
|
|
4366
|
-
if (answer.status === HTTP_STATUS.ok) {
|
|
4367
|
-
const sequenceNums = sequenceNumsOfBody(answer.body);
|
|
4368
|
-
if (sequenceNums === void 0) {
|
|
4369
|
-
return { ok: false, message: `the door accepted the event but its answer named no sequence numbers: ${answer.body}` };
|
|
4908
|
+
const runLoop = async (attachment) => {
|
|
4909
|
+
let retriedUnauthorized = false;
|
|
4910
|
+
for (; ; ) {
|
|
4911
|
+
if (attachment.isStopped()) {
|
|
4912
|
+
return;
|
|
4370
4913
|
}
|
|
4371
|
-
|
|
4914
|
+
const live = deps.trackedSessions().includes(attachment.sessionId);
|
|
4915
|
+
const credential = deps.credentialOf(attachment.sessionId);
|
|
4916
|
+
if (!live || credential?.authorization === void 0) {
|
|
4917
|
+
if (attachments.get(attachment.sessionId) === attachment) {
|
|
4918
|
+
attachments.delete(attachment.sessionId);
|
|
4919
|
+
}
|
|
4920
|
+
return;
|
|
4921
|
+
}
|
|
4922
|
+
let outcome;
|
|
4923
|
+
try {
|
|
4924
|
+
outcome = await attempt(attachment, credential);
|
|
4925
|
+
} catch (error) {
|
|
4926
|
+
if (attachment.isStopped()) {
|
|
4927
|
+
return;
|
|
4928
|
+
}
|
|
4929
|
+
deps.log?.(`rc stream ${attachment.sessionId}: attachment failed (${error instanceof Error ? error.message : String(error)}); backing off`);
|
|
4930
|
+
await deps.sleep(deps.backoffMs);
|
|
4931
|
+
retriedUnauthorized = false;
|
|
4932
|
+
continue;
|
|
4933
|
+
}
|
|
4934
|
+
if (attachment.isStopped()) {
|
|
4935
|
+
return;
|
|
4936
|
+
}
|
|
4937
|
+
if (outcome === "dropped") {
|
|
4938
|
+
retriedUnauthorized = false;
|
|
4939
|
+
continue;
|
|
4940
|
+
}
|
|
4941
|
+
if (!retriedUnauthorized) {
|
|
4942
|
+
retriedUnauthorized = true;
|
|
4943
|
+
continue;
|
|
4944
|
+
}
|
|
4945
|
+
deps.log?.(`rc stream ${attachment.sessionId}: the client read stream refused the observed Authorization bearer past one retry; backing off`);
|
|
4946
|
+
await deps.sleep(deps.backoffMs);
|
|
4947
|
+
retriedUnauthorized = false;
|
|
4372
4948
|
}
|
|
4373
|
-
return { ok: false, message: describeFailure(answer.status, controlErrorOf(answer.body) ?? answer.body) };
|
|
4374
4949
|
};
|
|
4375
|
-
const sessionQuery = (sessionId) => sessionId === void 0 ? "" : `?session=${encodeURIComponent(sessionId)}`;
|
|
4376
4950
|
return {
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4951
|
+
reconcile: () => {
|
|
4952
|
+
const live = new Set(deps.trackedSessions());
|
|
4953
|
+
for (const [sessionId, attachment] of attachments) {
|
|
4954
|
+
if (!live.has(sessionId)) {
|
|
4955
|
+
attachment.stop();
|
|
4956
|
+
attachments.delete(sessionId);
|
|
4957
|
+
}
|
|
4958
|
+
}
|
|
4959
|
+
for (const sessionId of live) {
|
|
4960
|
+
if (attachments.has(sessionId)) {
|
|
4961
|
+
continue;
|
|
4962
|
+
}
|
|
4963
|
+
if (deps.credentialOf(sessionId)?.authorization === void 0) {
|
|
4964
|
+
continue;
|
|
4965
|
+
}
|
|
4966
|
+
const attachment = birth(sessionId);
|
|
4967
|
+
attachments.set(sessionId, attachment);
|
|
4968
|
+
void runLoop(attachment).catch((error) => {
|
|
4969
|
+
deps.log?.(`rc stream ${sessionId}: the attachment loop ended unexpectedly (${error instanceof Error ? error.message : String(error)})`);
|
|
4970
|
+
});
|
|
4971
|
+
}
|
|
4972
|
+
},
|
|
4973
|
+
close: () => {
|
|
4974
|
+
for (const attachment of attachments.values()) {
|
|
4975
|
+
attachment.stop();
|
|
4976
|
+
}
|
|
4977
|
+
attachments.clear();
|
|
4978
|
+
}
|
|
4389
4979
|
};
|
|
4390
4980
|
}
|
|
4391
4981
|
|
|
@@ -4395,31 +4985,31 @@ import { randomUUID as randomUUID3 } from "node:crypto";
|
|
|
4395
4985
|
// src/headroom/state.ts
|
|
4396
4986
|
import { createHash as createHash3 } from "node:crypto";
|
|
4397
4987
|
import path8 from "node:path";
|
|
4398
|
-
import { z as
|
|
4988
|
+
import { z as z5 } from "zod";
|
|
4399
4989
|
var HEADROOM_ANTHROPIC_UPSTREAM = "https://api.anthropic.com";
|
|
4400
4990
|
var HEADROOM_STATE_SCHEMA_VERSION = 2;
|
|
4401
|
-
var HeadroomStateSchema =
|
|
4991
|
+
var HeadroomStateSchema = z5.strictObject({
|
|
4402
4992
|
/** The supervisor process owning the daemon. Alive means someone is keeping headroom running. */
|
|
4403
|
-
supervisorPid:
|
|
4993
|
+
supervisorPid: z5.number().int().positive().optional(),
|
|
4404
4994
|
/** The headroom proxy process itself. Absent while the supervisor is between restarts. */
|
|
4405
|
-
headroomPid:
|
|
4995
|
+
headroomPid: z5.number().int().positive().optional(),
|
|
4406
4996
|
/**
|
|
4407
4997
|
* The unix socket the proxy serves on, inside the owner-only socket directory. Absent until the proxy has passed its readiness check, so "socketPath is set" is itself the ready signal a launcher polls for. The front door reads it on every hop, so a restart (which keeps the generation's path) is followed without anything being relaunched.
|
|
4408
4998
|
*/
|
|
4409
|
-
socketPath:
|
|
4999
|
+
socketPath: z5.string().min(1).optional(),
|
|
4410
5000
|
/** The `headroom --version` output of the running install. */
|
|
4411
|
-
version:
|
|
5001
|
+
version: z5.string().optional(),
|
|
4412
5002
|
/** Hash of the allowlist the running proxy was started with, so a provider-file change is detected as drift. */
|
|
4413
|
-
allowlistHash:
|
|
5003
|
+
allowlistHash: z5.string().optional(),
|
|
4414
5004
|
/** Hash of the token-saving settings the running proxy was started with, so a settings change is detected as drift. */
|
|
4415
|
-
settingsHash:
|
|
5005
|
+
settingsHash: z5.string().optional(),
|
|
4416
5006
|
/** The install spec the running install was last installed from, so a `source` config change is detected as drift. */
|
|
4417
|
-
installedSource:
|
|
5007
|
+
installedSource: z5.string().optional(),
|
|
4418
5008
|
/** The last fatal error, kept across shutdowns so `headroom status` can explain a daemon that is not running. */
|
|
4419
|
-
lastError:
|
|
5009
|
+
lastError: z5.string().optional()
|
|
4420
5010
|
});
|
|
4421
|
-
var HeadroomSessionSchema =
|
|
4422
|
-
var HeadroomLockSchema =
|
|
5011
|
+
var HeadroomSessionSchema = z5.strictObject({ pid: z5.number().int().positive(), startedAt: z5.number(), supervisorPid: z5.number().int().positive() });
|
|
5012
|
+
var HeadroomLockSchema = z5.strictObject({ pid: z5.number().int().positive(), at: z5.number() });
|
|
4423
5013
|
function parseJson(raw, schema) {
|
|
4424
5014
|
if (raw === void 0 || raw.trim() === "") {
|
|
4425
5015
|
return void 0;
|
|
@@ -4599,118 +5189,118 @@ import path10 from "node:path";
|
|
|
4599
5189
|
import path9 from "node:path";
|
|
4600
5190
|
|
|
4601
5191
|
// src/usage/schema.ts
|
|
4602
|
-
import { z as
|
|
5192
|
+
import { z as z6 } from "zod";
|
|
4603
5193
|
var USAGE_SCHEMA_VERSION = 1;
|
|
4604
|
-
var VersionSchema =
|
|
4605
|
-
var InstantSchema =
|
|
4606
|
-
var TokenCountSchema =
|
|
4607
|
-
var TokenUsageSchema =
|
|
5194
|
+
var VersionSchema = z6.literal(USAGE_SCHEMA_VERSION);
|
|
5195
|
+
var InstantSchema = z6.iso.datetime();
|
|
5196
|
+
var TokenCountSchema = z6.number().int().nonnegative();
|
|
5197
|
+
var TokenUsageSchema = z6.strictObject({
|
|
4608
5198
|
inputTokens: TokenCountSchema.optional(),
|
|
4609
5199
|
outputTokens: TokenCountSchema.optional(),
|
|
4610
5200
|
cacheCreationInputTokens: TokenCountSchema.optional(),
|
|
4611
5201
|
cacheReadInputTokens: TokenCountSchema.optional()
|
|
4612
5202
|
});
|
|
4613
5203
|
var LIMIT_KINDS = ["rate-limited", "quota-exhausted"];
|
|
4614
|
-
var LimitClassificationSchema =
|
|
4615
|
-
kind:
|
|
5204
|
+
var LimitClassificationSchema = z6.strictObject({
|
|
5205
|
+
kind: z6.enum(LIMIT_KINDS),
|
|
4616
5206
|
/** Seconds to wait before retrying, from the response's `retry-after`. */
|
|
4617
|
-
retryAfterSeconds:
|
|
5207
|
+
retryAfterSeconds: z6.number().nonnegative().optional(),
|
|
4618
5208
|
/** When the limiting window resets, from the upstream's own reset header. */
|
|
4619
5209
|
resetAt: InstantSchema.optional(),
|
|
4620
5210
|
/** The window the upstream named as the binding one (`anthropic-ratelimit-unified-representative-claim`), such as `five_hour` or `seven_day`. */
|
|
4621
|
-
window:
|
|
5211
|
+
window: z6.string().optional(),
|
|
4622
5212
|
/** The signals the classification rests on, each `<name>=<value>` from a response header or the error body's `type` or `code`. Never the error message. */
|
|
4623
|
-
evidence:
|
|
5213
|
+
evidence: z6.array(z6.string())
|
|
4624
5214
|
});
|
|
4625
|
-
var UsageRecordSchema =
|
|
5215
|
+
var UsageRecordSchema = z6.strictObject({
|
|
4626
5216
|
schemaVersion: VersionSchema,
|
|
4627
5217
|
/** When the front door received the request. */
|
|
4628
5218
|
at: InstantSchema,
|
|
4629
5219
|
/** The launching identity, when the launch resolved one. */
|
|
4630
|
-
identity:
|
|
5220
|
+
identity: z6.string().optional(),
|
|
4631
5221
|
/** `anthropic` for an OAuth session's request to Claude Code's own API, otherwise the provider's name. */
|
|
4632
|
-
provider:
|
|
5222
|
+
provider: z6.string(),
|
|
4633
5223
|
/** The launch's session id, generated per launch. */
|
|
4634
|
-
sessionId:
|
|
5224
|
+
sessionId: z6.string().optional(),
|
|
4635
5225
|
/** The project the launch ran in (its git repository root), when the launch sent one: headroom launches do. */
|
|
4636
|
-
project:
|
|
5226
|
+
project: z6.string().optional(),
|
|
4637
5227
|
/** The front-door route that served the request. */
|
|
4638
|
-
route:
|
|
4639
|
-
method:
|
|
5228
|
+
route: z6.string(),
|
|
5229
|
+
method: z6.string(),
|
|
4640
5230
|
/** The API path as the upstream sees it (`/v1/messages`), without the provider prefix or a query string. */
|
|
4641
|
-
endpoint:
|
|
5231
|
+
endpoint: z6.string(),
|
|
4642
5232
|
/** The model the response named. */
|
|
4643
|
-
model:
|
|
4644
|
-
status:
|
|
5233
|
+
model: z6.string().optional(),
|
|
5234
|
+
status: z6.number().int(),
|
|
4645
5235
|
/** Milliseconds from the request's arrival to its response head. */
|
|
4646
|
-
latencyMs:
|
|
5236
|
+
latencyMs: z6.number().int().nonnegative(),
|
|
4647
5237
|
/** Milliseconds from the request's arrival to the end of its response body. */
|
|
4648
|
-
durationMs:
|
|
5238
|
+
durationMs: z6.number().int().nonnegative(),
|
|
4649
5239
|
/** Whether the client received the whole body. */
|
|
4650
|
-
outcome:
|
|
5240
|
+
outcome: z6.enum(["completed", "aborted"]),
|
|
4651
5241
|
usage: TokenUsageSchema.optional(),
|
|
4652
5242
|
/** The response's quota and rate-limit headers, by name (see `isRateLimitHeader`). */
|
|
4653
|
-
rateLimitHeaders:
|
|
5243
|
+
rateLimitHeaders: z6.record(z6.string(), z6.string()).optional(),
|
|
4654
5244
|
limit: LimitClassificationSchema.optional(),
|
|
4655
5245
|
/** The upstream's request id (`request-id`), for matching a record to the provider's own logs. */
|
|
4656
|
-
requestId:
|
|
5246
|
+
requestId: z6.string().optional()
|
|
4657
5247
|
});
|
|
4658
|
-
var QuotaWindowSchema =
|
|
5248
|
+
var QuotaWindowSchema = z6.strictObject({
|
|
4659
5249
|
/** The fraction of the window used, as the upstream reports it (0 to 1). */
|
|
4660
|
-
utilization:
|
|
5250
|
+
utilization: z6.number().nonnegative().optional(),
|
|
4661
5251
|
resetsAt: InstantSchema.optional(),
|
|
4662
5252
|
/** The window's own status (`allowed`, `allowed_warning`, `rejected`). */
|
|
4663
|
-
status:
|
|
5253
|
+
status: z6.string().optional()
|
|
4664
5254
|
});
|
|
4665
|
-
var UnifiedRateLimitSchema =
|
|
5255
|
+
var UnifiedRateLimitSchema = z6.strictObject({
|
|
4666
5256
|
/** The overall status: `allowed`, `allowed_warning` or `rejected`. */
|
|
4667
|
-
status:
|
|
5257
|
+
status: z6.string().optional(),
|
|
4668
5258
|
fiveHour: QuotaWindowSchema.optional(),
|
|
4669
5259
|
sevenDay: QuotaWindowSchema.optional(),
|
|
4670
5260
|
/** The window that currently binds. */
|
|
4671
|
-
representativeClaim:
|
|
5261
|
+
representativeClaim: z6.string().optional(),
|
|
4672
5262
|
/** When the binding window resets. */
|
|
4673
5263
|
resetAt: InstantSchema.optional(),
|
|
4674
5264
|
/** Whether usage beyond the plan (extra usage) is available: `allowed` or `rejected`. */
|
|
4675
|
-
overageStatus:
|
|
5265
|
+
overageStatus: z6.string().optional()
|
|
4676
5266
|
});
|
|
4677
|
-
var RateLimitStateSchema =
|
|
5267
|
+
var RateLimitStateSchema = z6.strictObject({
|
|
4678
5268
|
observedAt: InstantSchema,
|
|
4679
5269
|
/** Every quota and rate-limit header on that response, by name. */
|
|
4680
|
-
headers:
|
|
5270
|
+
headers: z6.record(z6.string(), z6.string()),
|
|
4681
5271
|
unified: UnifiedRateLimitSchema.optional()
|
|
4682
5272
|
});
|
|
4683
5273
|
var LimitEventSchema = LimitClassificationSchema.extend({
|
|
4684
5274
|
observedAt: InstantSchema,
|
|
4685
|
-
status:
|
|
5275
|
+
status: z6.number().int()
|
|
4686
5276
|
});
|
|
4687
|
-
var ProviderQuotaWindowSchema =
|
|
5277
|
+
var ProviderQuotaWindowSchema = z6.strictObject({
|
|
4688
5278
|
/** What the window measures, in the provider's terms (`tokens`, `tool-calls`). */
|
|
4689
|
-
measures:
|
|
5279
|
+
measures: z6.string(),
|
|
4690
5280
|
/** The window's length in milliseconds, when the provider's unit is one this project knows; absent for an unrecognised unit, with `period` carrying the raw code. */
|
|
4691
|
-
periodMs:
|
|
5281
|
+
periodMs: z6.number().int().positive().optional(),
|
|
4692
5282
|
/** The provider's own description of the period when `periodMs` could not be derived, such as `unit 4 x 1`. */
|
|
4693
|
-
period:
|
|
5283
|
+
period: z6.string().optional(),
|
|
4694
5284
|
/** The fraction of the window used (0 to 1). */
|
|
4695
|
-
utilization:
|
|
5285
|
+
utilization: z6.number().nonnegative(),
|
|
4696
5286
|
resetsAt: InstantSchema.optional(),
|
|
4697
5287
|
/** Absolute counts, when the provider reports them for this window. */
|
|
4698
|
-
limit:
|
|
4699
|
-
used:
|
|
4700
|
-
remaining:
|
|
5288
|
+
limit: z6.number().nonnegative().optional(),
|
|
5289
|
+
used: z6.number().nonnegative().optional(),
|
|
5290
|
+
remaining: z6.number().nonnegative().optional()
|
|
4701
5291
|
});
|
|
4702
|
-
var ProviderQuotaSchema =
|
|
5292
|
+
var ProviderQuotaSchema = z6.strictObject({
|
|
4703
5293
|
observedAt: InstantSchema,
|
|
4704
5294
|
/** Which usage endpoint produced it (`z.ai`). */
|
|
4705
|
-
source:
|
|
5295
|
+
source: z6.string(),
|
|
4706
5296
|
/** The subscription level the endpoint reports, when it does. */
|
|
4707
|
-
level:
|
|
4708
|
-
windows:
|
|
5297
|
+
level: z6.string().optional(),
|
|
5298
|
+
windows: z6.array(ProviderQuotaWindowSchema)
|
|
4709
5299
|
});
|
|
4710
|
-
var ProviderUsageStateSchema =
|
|
5300
|
+
var ProviderUsageStateSchema = z6.strictObject({
|
|
4711
5301
|
lastRequestAt: InstantSchema,
|
|
4712
|
-
lastStatus:
|
|
4713
|
-
lastModel:
|
|
5302
|
+
lastStatus: z6.number().int(),
|
|
5303
|
+
lastModel: z6.string().optional(),
|
|
4714
5304
|
/** The latest rate-limit state, from the last response that carried any rate-limit header. */
|
|
4715
5305
|
rateLimit: RateLimitStateSchema.optional(),
|
|
4716
5306
|
/** The latest classified refusal. It stays after later successes, so a reader compares its `resetAt` (or `observedAt` plus `retryAfterSeconds`) with the current time. */
|
|
@@ -4718,31 +5308,31 @@ var ProviderUsageStateSchema = z5.strictObject({
|
|
|
4718
5308
|
/** The latest quota the provider's usage endpoint reported, for providers whose quota is not visible in response headers. */
|
|
4719
5309
|
quota: ProviderQuotaSchema.optional()
|
|
4720
5310
|
});
|
|
4721
|
-
var AccountMetadataSchema =
|
|
4722
|
-
accountUuid:
|
|
4723
|
-
emailAddress:
|
|
4724
|
-
displayName:
|
|
4725
|
-
organizationUuid:
|
|
4726
|
-
organizationName:
|
|
4727
|
-
organizationType:
|
|
4728
|
-
organizationRole:
|
|
4729
|
-
workspaceRole:
|
|
4730
|
-
billingType:
|
|
4731
|
-
seatTier:
|
|
4732
|
-
organizationRateLimitTier:
|
|
4733
|
-
userRateLimitTier:
|
|
4734
|
-
hasExtraUsageEnabled:
|
|
4735
|
-
subscriptionCreatedAt:
|
|
4736
|
-
accountCreatedAt:
|
|
5311
|
+
var AccountMetadataSchema = z6.strictObject({
|
|
5312
|
+
accountUuid: z6.string().optional(),
|
|
5313
|
+
emailAddress: z6.string().optional(),
|
|
5314
|
+
displayName: z6.string().optional(),
|
|
5315
|
+
organizationUuid: z6.string().optional(),
|
|
5316
|
+
organizationName: z6.string().optional(),
|
|
5317
|
+
organizationType: z6.string().optional(),
|
|
5318
|
+
organizationRole: z6.string().optional(),
|
|
5319
|
+
workspaceRole: z6.string().optional(),
|
|
5320
|
+
billingType: z6.string().optional(),
|
|
5321
|
+
seatTier: z6.string().optional(),
|
|
5322
|
+
organizationRateLimitTier: z6.string().optional(),
|
|
5323
|
+
userRateLimitTier: z6.string().optional(),
|
|
5324
|
+
hasExtraUsageEnabled: z6.boolean().optional(),
|
|
5325
|
+
subscriptionCreatedAt: z6.string().optional(),
|
|
5326
|
+
accountCreatedAt: z6.string().optional()
|
|
4737
5327
|
});
|
|
4738
|
-
var UsageSnapshotSchema =
|
|
5328
|
+
var UsageSnapshotSchema = z6.strictObject({
|
|
4739
5329
|
schemaVersion: VersionSchema,
|
|
4740
|
-
identity:
|
|
5330
|
+
identity: z6.string(),
|
|
4741
5331
|
updatedAt: InstantSchema,
|
|
4742
5332
|
/** The identity's account metadata as of `updatedAt`. */
|
|
4743
5333
|
account: AccountMetadataSchema.optional(),
|
|
4744
5334
|
/** Latest state per provider, keyed by provider name (`anthropic` for OAuth sessions). */
|
|
4745
|
-
providers:
|
|
5335
|
+
providers: z6.record(z6.string(), ProviderUsageStateSchema)
|
|
4746
5336
|
});
|
|
4747
5337
|
|
|
4748
5338
|
// src/usage/account.ts
|
|
@@ -4946,20 +5536,20 @@ function quotaWarnings(snapshot, provider, nowMs) {
|
|
|
4946
5536
|
}
|
|
4947
5537
|
|
|
4948
5538
|
// src/usage/plan.ts
|
|
4949
|
-
import { z as
|
|
4950
|
-
var PlanClassSchema =
|
|
4951
|
-
|
|
4952
|
-
kind:
|
|
5539
|
+
import { z as z7 } from "zod";
|
|
5540
|
+
var PlanClassSchema = z7.union([
|
|
5541
|
+
z7.strictObject({
|
|
5542
|
+
kind: z7.literal("subscription"),
|
|
4953
5543
|
/** The plan's size as a multiple of the smallest subscription tier (Max 5x is 5, Max 20x is 20). */
|
|
4954
|
-
capacity:
|
|
5544
|
+
capacity: z7.number().positive(),
|
|
4955
5545
|
/** False when the tier string named no multiplier and no tier this project knows, so `capacity` is the neutral 1 rather than a reading. */
|
|
4956
|
-
recognised:
|
|
5546
|
+
recognised: z7.boolean(),
|
|
4957
5547
|
/** The tier string the classification rests on, when the account had one. */
|
|
4958
|
-
tier:
|
|
5548
|
+
tier: z7.string().optional()
|
|
4959
5549
|
}),
|
|
4960
|
-
|
|
4961
|
-
kind:
|
|
4962
|
-
tier:
|
|
5550
|
+
z7.strictObject({
|
|
5551
|
+
kind: z7.literal("pay-per-use"),
|
|
5552
|
+
tier: z7.string().optional()
|
|
4963
5553
|
})
|
|
4964
5554
|
]);
|
|
4965
5555
|
var TIER_MULTIPLIER = /(?:^|_)(\d+)x$/;
|
|
@@ -5028,17 +5618,17 @@ function parseUnifiedRateLimit(headers) {
|
|
|
5028
5618
|
}
|
|
5029
5619
|
|
|
5030
5620
|
// src/usage/scan.ts
|
|
5031
|
-
import { z as
|
|
5032
|
-
var AnthropicUsageSchema =
|
|
5033
|
-
input_tokens:
|
|
5034
|
-
output_tokens:
|
|
5035
|
-
cache_creation_input_tokens:
|
|
5036
|
-
cache_read_input_tokens:
|
|
5621
|
+
import { z as z8 } from "zod";
|
|
5622
|
+
var AnthropicUsageSchema = z8.looseObject({
|
|
5623
|
+
input_tokens: z8.number().int().nonnegative().nullish(),
|
|
5624
|
+
output_tokens: z8.number().int().nonnegative().nullish(),
|
|
5625
|
+
cache_creation_input_tokens: z8.number().int().nonnegative().nullish(),
|
|
5626
|
+
cache_read_input_tokens: z8.number().int().nonnegative().nullish()
|
|
5037
5627
|
});
|
|
5038
|
-
var ErrorObjectSchema =
|
|
5039
|
-
type:
|
|
5040
|
-
code:
|
|
5041
|
-
message:
|
|
5628
|
+
var ErrorObjectSchema = z8.looseObject({
|
|
5629
|
+
type: z8.string().optional(),
|
|
5630
|
+
code: z8.union([z8.string(), z8.number()]).optional(),
|
|
5631
|
+
message: z8.string().optional()
|
|
5042
5632
|
});
|
|
5043
5633
|
|
|
5044
5634
|
// src/usage/middleware.ts
|
|
@@ -5198,7 +5788,7 @@ function rankPool(input) {
|
|
|
5198
5788
|
|
|
5199
5789
|
// src/usage/poolPick.ts
|
|
5200
5790
|
import path11 from "node:path";
|
|
5201
|
-
import { z as
|
|
5791
|
+
import { z as z9 } from "zod";
|
|
5202
5792
|
|
|
5203
5793
|
// src/usage/store.ts
|
|
5204
5794
|
var DAY_MS2 = 864e5;
|
|
@@ -5207,10 +5797,10 @@ var USAGE_RETENTION_MS = DAYS_IN_WEEK * DAY_MS2;
|
|
|
5207
5797
|
|
|
5208
5798
|
// src/usage/poolPick.ts
|
|
5209
5799
|
var SCHEMA_VERSION = 1;
|
|
5210
|
-
var PickEntrySchema =
|
|
5211
|
-
var PicksFileSchema =
|
|
5212
|
-
schemaVersion:
|
|
5213
|
-
picks:
|
|
5800
|
+
var PickEntrySchema = z9.strictObject({ identity: z9.string().min(1), at: z9.iso.datetime() });
|
|
5801
|
+
var PicksFileSchema = z9.strictObject({
|
|
5802
|
+
schemaVersion: z9.literal(SCHEMA_VERSION),
|
|
5803
|
+
picks: z9.record(z9.string().min(1), PickEntrySchema)
|
|
5214
5804
|
});
|
|
5215
5805
|
function errorMessage(error) {
|
|
5216
5806
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5410,33 +6000,33 @@ function collectPoolPick(params) {
|
|
|
5410
6000
|
}
|
|
5411
6001
|
|
|
5412
6002
|
// src/usage/pickReportSchema.ts
|
|
5413
|
-
import { z as
|
|
5414
|
-
var PoolPickCandidateViewSchema =
|
|
5415
|
-
identity:
|
|
6003
|
+
import { z as z10 } from "zod";
|
|
6004
|
+
var PoolPickCandidateViewSchema = z10.strictObject({
|
|
6005
|
+
identity: z10.string(),
|
|
5416
6006
|
/** In pick order: `scored` has usable quota data, `unknown` has none, `pay-per-use` bills by use, `ineligible` is refused right now. */
|
|
5417
|
-
class:
|
|
6007
|
+
class: z10.enum(["scored", "unknown", "pay-per-use", "ineligible"]),
|
|
5418
6008
|
/** Plan-size-weighted remaining quota per hour until its reset; present for `scored` only. */
|
|
5419
|
-
score:
|
|
6009
|
+
score: z10.number().optional(),
|
|
5420
6010
|
/** False when the five-hour window would run dry, at the observed pace, before it resets. */
|
|
5421
|
-
feasible:
|
|
6011
|
+
feasible: z10.boolean(),
|
|
5422
6012
|
/** When an `ineligible` member can be used again, as an ISO instant. */
|
|
5423
|
-
blockedUntil:
|
|
6013
|
+
blockedUntil: z10.iso.datetime().optional(),
|
|
5424
6014
|
plan: PlanClassSchema,
|
|
5425
6015
|
/** The facts behind the class and score, in the order they matter. */
|
|
5426
|
-
reasons:
|
|
6016
|
+
reasons: z10.array(z10.string())
|
|
5427
6017
|
});
|
|
5428
|
-
var PoolPickReportSchema =
|
|
5429
|
-
pool:
|
|
5430
|
-
directory:
|
|
6018
|
+
var PoolPickReportSchema = z10.strictObject({
|
|
6019
|
+
pool: z10.string(),
|
|
6020
|
+
directory: z10.string(),
|
|
5431
6021
|
/** The member a launch would run as, absent when every member is refused. */
|
|
5432
|
-
pick:
|
|
5433
|
-
candidates:
|
|
6022
|
+
pick: z10.string().optional(),
|
|
6023
|
+
candidates: z10.array(PoolPickCandidateViewSchema),
|
|
5434
6024
|
/** Members that name an identity that does not exist; a launch skips them. */
|
|
5435
|
-
missing:
|
|
6025
|
+
missing: z10.array(z10.string()),
|
|
5436
6026
|
/** When nothing can be picked, the soonest any member returns. */
|
|
5437
|
-
earliestReturn:
|
|
6027
|
+
earliestReturn: z10.strictObject({ identity: z10.string(), at: z10.iso.datetime() }).optional(),
|
|
5438
6028
|
/** Why the last-pick record could not be read, when that is the case. */
|
|
5439
|
-
stickyProblem:
|
|
6029
|
+
stickyProblem: z10.string().optional()
|
|
5440
6030
|
});
|
|
5441
6031
|
|
|
5442
6032
|
// src/headroom/ensure.ts
|
|
@@ -5599,10 +6189,10 @@ function pruneStaleHeadroomSockets(fs12, paths, isRunning) {
|
|
|
5599
6189
|
}
|
|
5600
6190
|
|
|
5601
6191
|
// src/headroom/source.ts
|
|
5602
|
-
import { z as
|
|
6192
|
+
import { z as z11 } from "zod";
|
|
5603
6193
|
var GIT_SPEC_RE = /git\+[^@\s]+(?:@([^\s#]+))?/;
|
|
5604
6194
|
var COMMIT_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
5605
|
-
var DirectUrlSchema =
|
|
6195
|
+
var DirectUrlSchema = z11.object({ vcs_info: z11.object({ commit_id: z11.string() }) });
|
|
5606
6196
|
function isMovingGitSource(spec) {
|
|
5607
6197
|
const match = GIT_SPEC_RE.exec(spec);
|
|
5608
6198
|
if (match === null) {
|
|
@@ -5915,9 +6505,9 @@ function evaluateAmbientCredentialGuard(params) {
|
|
|
5915
6505
|
|
|
5916
6506
|
// src/paths.ts
|
|
5917
6507
|
import fs3 from "node:fs";
|
|
5918
|
-
import
|
|
6508
|
+
import os2 from "node:os";
|
|
5919
6509
|
import path14 from "node:path";
|
|
5920
|
-
function resolveAgentShimHome(env = process.env, home =
|
|
6510
|
+
function resolveAgentShimHome(env = process.env, home = os2.homedir(), isDirectory = isExistingDirectory) {
|
|
5921
6511
|
const fromEnv = env.AGENT_SHIM_HOME;
|
|
5922
6512
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5923
6513
|
return fromEnv;
|
|
@@ -5934,7 +6524,7 @@ function resolveClaudeHome() {
|
|
|
5934
6524
|
if (fromEnv !== void 0 && fromEnv !== "") {
|
|
5935
6525
|
return fromEnv;
|
|
5936
6526
|
}
|
|
5937
|
-
return path14.join(
|
|
6527
|
+
return path14.join(os2.homedir(), ".claude");
|
|
5938
6528
|
}
|
|
5939
6529
|
function buildLayoutPaths(root) {
|
|
5940
6530
|
return {
|
|
@@ -6316,11 +6906,11 @@ import fs6 from "node:fs";
|
|
|
6316
6906
|
import path17 from "node:path";
|
|
6317
6907
|
|
|
6318
6908
|
// src/credentialCache.ts
|
|
6319
|
-
import { z as
|
|
6909
|
+
import { z as z12 } from "zod";
|
|
6320
6910
|
var MS_PER_UNIT = { s: 1e3, m: 6e4, h: 36e5, d: 864e5 };
|
|
6321
|
-
var CachedCredentialSchema =
|
|
6322
|
-
token:
|
|
6323
|
-
fetchedAt:
|
|
6911
|
+
var CachedCredentialSchema = z12.strictObject({
|
|
6912
|
+
token: z12.string().min(1),
|
|
6913
|
+
fetchedAt: z12.number().int().nonnegative(),
|
|
6324
6914
|
source: CredentialSourceSchema
|
|
6325
6915
|
});
|
|
6326
6916
|
function effectiveStore(cache, platform) {
|
|
@@ -6586,14 +7176,14 @@ function providerExists(paths, name) {
|
|
|
6586
7176
|
var LEGACY_PROVIDER_FIELDS = ["tokenEnv", "tokenCommand", "authScheme"];
|
|
6587
7177
|
var LEGACY_ENV_CREDENTIAL_KEYS = Object.values(CREDENTIAL_TARGET_VARS);
|
|
6588
7178
|
var LEGACY_LITERAL_PLACEHOLDER = "<the value of env.ANTHROPIC_AUTH_TOKEN>";
|
|
6589
|
-
function
|
|
7179
|
+
function isRecord4(value) {
|
|
6590
7180
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6591
7181
|
}
|
|
6592
7182
|
function legacyProviderConversion(raw) {
|
|
6593
|
-
if (!
|
|
7183
|
+
if (!isRecord4(raw) || "credential" in raw) {
|
|
6594
7184
|
return void 0;
|
|
6595
7185
|
}
|
|
6596
|
-
const env =
|
|
7186
|
+
const env = isRecord4(raw.env) ? raw.env : void 0;
|
|
6597
7187
|
const legacyEnvKeys = env === void 0 ? [] : Object.keys(env).filter((key) => LEGACY_ENV_CREDENTIAL_KEYS.includes(key));
|
|
6598
7188
|
const fields = [...LEGACY_PROVIDER_FIELDS.filter((field) => field in raw), ...legacyEnvKeys.map((key) => `env.${key}`)];
|
|
6599
7189
|
if (fields.length === 0) {
|
|
@@ -6906,7 +7496,7 @@ function removeDirectoryRule(paths, rulePath) {
|
|
|
6906
7496
|
}
|
|
6907
7497
|
|
|
6908
7498
|
// src/checkReport.ts
|
|
6909
|
-
import
|
|
7499
|
+
import os5 from "node:os";
|
|
6910
7500
|
|
|
6911
7501
|
// src/launcher/cascade.ts
|
|
6912
7502
|
import path19 from "node:path";
|
|
@@ -7079,15 +7669,15 @@ function realCredentialCacheEnv(paths) {
|
|
|
7079
7669
|
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
7080
7670
|
import { createHash as createHash5 } from "node:crypto";
|
|
7081
7671
|
import fs8 from "node:fs";
|
|
7082
|
-
import
|
|
7672
|
+
import os4 from "node:os";
|
|
7083
7673
|
import path22 from "node:path";
|
|
7084
7674
|
import { isSea } from "node:sea";
|
|
7085
7675
|
|
|
7086
7676
|
// src/versionDiscovery.ts
|
|
7087
|
-
import
|
|
7677
|
+
import os3 from "node:os";
|
|
7088
7678
|
import path21 from "node:path";
|
|
7089
7679
|
function defaultVersionsDir() {
|
|
7090
|
-
return path21.join(
|
|
7680
|
+
return path21.join(os3.homedir(), ".local", "share", "claude", "versions");
|
|
7091
7681
|
}
|
|
7092
7682
|
var NUMERIC_DOTTED_VERSION_RE = /^\d+(?:\.\d+)*$/;
|
|
7093
7683
|
function isNumericDottedVersion(name) {
|
|
@@ -7303,7 +7893,7 @@ var realRunPort = {
|
|
|
7303
7893
|
}
|
|
7304
7894
|
};
|
|
7305
7895
|
function expandHome2(filePath) {
|
|
7306
|
-
return filePath.startsWith("~/") ? path22.join(
|
|
7896
|
+
return filePath.startsWith("~/") ? path22.join(os4.homedir(), filePath.slice(1)) : filePath;
|
|
7307
7897
|
}
|
|
7308
7898
|
var MACOS_GUI_SESSION_MANAGER = "Aqua";
|
|
7309
7899
|
function hasDesktopSession() {
|
|
@@ -7557,7 +8147,7 @@ function realResolveClaudeBinary(isOwnBinary) {
|
|
|
7557
8147
|
|
|
7558
8148
|
// src/checkReport.ts
|
|
7559
8149
|
import path23 from "node:path";
|
|
7560
|
-
import { z as
|
|
8150
|
+
import { z as z13 } from "zod";
|
|
7561
8151
|
|
|
7562
8152
|
// src/cli/bool.ts
|
|
7563
8153
|
function parseBoolWord(input) {
|
|
@@ -7672,9 +8262,9 @@ function lookupKeychainService(run, farmRoot) {
|
|
|
7672
8262
|
note: serviceName === void 0 ? `A Keychain entry was found for account "${farmRoot}" but its service name could not be parsed from security's output.` : `Keychain service name for this identity: "${serviceName}".`
|
|
7673
8263
|
};
|
|
7674
8264
|
}
|
|
7675
|
-
var SettingsSecretsShapeSchema =
|
|
7676
|
-
env:
|
|
7677
|
-
hooks:
|
|
8265
|
+
var SettingsSecretsShapeSchema = z13.looseObject({
|
|
8266
|
+
env: z13.record(z13.string(), z13.unknown()).optional(),
|
|
8267
|
+
hooks: z13.record(z13.string(), z13.array(z13.unknown())).optional()
|
|
7678
8268
|
});
|
|
7679
8269
|
function hookCommandCountOf(group) {
|
|
7680
8270
|
if (typeof group !== "object" || group === null) {
|
|
@@ -7888,7 +8478,7 @@ function loadCheckProvider(paths, name) {
|
|
|
7888
8478
|
}
|
|
7889
8479
|
function collectCheckReport(params) {
|
|
7890
8480
|
const { paths, cwd } = params;
|
|
7891
|
-
const home =
|
|
8481
|
+
const home = os5.homedir();
|
|
7892
8482
|
const claudeHome = resolveClaudeHome();
|
|
7893
8483
|
const read = cosmiconfigReader();
|
|
7894
8484
|
const classification = loadClassification(paths);
|
|
@@ -7968,11 +8558,11 @@ function collectCheckReport(params) {
|
|
|
7968
8558
|
// src/claudeShim.ts
|
|
7969
8559
|
import fs9 from "node:fs";
|
|
7970
8560
|
import path24 from "node:path";
|
|
7971
|
-
import { z as
|
|
7972
|
-
var ClaudeShimStateSchema =
|
|
7973
|
-
targetPath:
|
|
7974
|
-
method:
|
|
7975
|
-
installedAtMs:
|
|
8561
|
+
import { z as z14 } from "zod";
|
|
8562
|
+
var ClaudeShimStateSchema = z14.strictObject({
|
|
8563
|
+
targetPath: z14.string().min(1),
|
|
8564
|
+
method: z14.enum(["hardlink", "copy"]),
|
|
8565
|
+
installedAtMs: z14.number()
|
|
7976
8566
|
});
|
|
7977
8567
|
function commandFilename(ownExecutablePath, commandName) {
|
|
7978
8568
|
return ownExecutablePath.toLowerCase().endsWith(".exe") ? `${commandName}.exe` : commandName;
|
|
@@ -8531,9 +9121,11 @@ var BOOLEAN_FLAGS = [
|
|
|
8531
9121
|
];
|
|
8532
9122
|
var LAUNCHER_FLAG_NAMES = [
|
|
8533
9123
|
...VALUED_FLAGS,
|
|
9124
|
+
"--native",
|
|
8534
9125
|
"--no-provider",
|
|
8535
9126
|
...BOOLEAN_FLAGS.flatMap(({ flag }) => [flag, `--no-${flag.slice(2)}`])
|
|
8536
9127
|
];
|
|
9128
|
+
var NATIVE_FLAG = "--native";
|
|
8537
9129
|
var TERMINATOR2 = "--";
|
|
8538
9130
|
function matchValuedFlag(token) {
|
|
8539
9131
|
for (const flag of VALUED_FLAGS) {
|
|
@@ -8561,6 +9153,7 @@ function parseLauncherArgv(argv) {
|
|
|
8561
9153
|
const first = argv[0];
|
|
8562
9154
|
const positionalIdentity = first !== void 0 && first.startsWith("@") && first.length > 1 ? first.slice(1) : void 0;
|
|
8563
9155
|
const remaining = positionalIdentity === void 0 ? argv : argv.slice(1);
|
|
9156
|
+
let native = false;
|
|
8564
9157
|
let flagIdentity;
|
|
8565
9158
|
let configProfile;
|
|
8566
9159
|
let provider;
|
|
@@ -8582,6 +9175,10 @@ function parseLauncherArgv(argv) {
|
|
|
8582
9175
|
provider = false;
|
|
8583
9176
|
continue;
|
|
8584
9177
|
}
|
|
9178
|
+
if (token === NATIVE_FLAG) {
|
|
9179
|
+
native = true;
|
|
9180
|
+
continue;
|
|
9181
|
+
}
|
|
8585
9182
|
const booleanFlag = matchBooleanFlag(token);
|
|
8586
9183
|
if (booleanFlag !== void 0) {
|
|
8587
9184
|
booleans[booleanFlag.key] = booleanFlag.value;
|
|
@@ -8635,6 +9232,7 @@ function parseLauncherArgv(argv) {
|
|
|
8635
9232
|
...identity === void 0 ? {} : { identity },
|
|
8636
9233
|
...configProfile === void 0 ? {} : { configProfile },
|
|
8637
9234
|
...provider === void 0 ? {} : { provider },
|
|
9235
|
+
...native ? { native: true } : {},
|
|
8638
9236
|
...booleans,
|
|
8639
9237
|
categoryFlags,
|
|
8640
9238
|
shareFlags,
|
|
@@ -9161,7 +9759,7 @@ function prepareLaunch(params) {
|
|
|
9161
9759
|
}
|
|
9162
9760
|
|
|
9163
9761
|
// src/launchWiring.ts
|
|
9164
|
-
import
|
|
9762
|
+
import os6 from "node:os";
|
|
9165
9763
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
9166
9764
|
|
|
9167
9765
|
// src/frontdoor/realFrontDoorPort.ts
|
|
@@ -9310,7 +9908,7 @@ function realFrontDoorPort(paths, spawnDaemon) {
|
|
|
9310
9908
|
|
|
9311
9909
|
// src/launchWiring.ts
|
|
9312
9910
|
function buildFarmRuntime(paths, cwd) {
|
|
9313
|
-
const home =
|
|
9911
|
+
const home = os6.homedir();
|
|
9314
9912
|
const read = cosmiconfigReader();
|
|
9315
9913
|
const classification = loadClassification(paths);
|
|
9316
9914
|
const loaded = loadCascadeInput({ paths, home, cwd, read });
|
|
@@ -9344,6 +9942,9 @@ function buildFarmRuntime(paths, cwd) {
|
|
|
9344
9942
|
...loaded.globalConfig?.pools === void 0 ? {} : { pools: loaded.globalConfig.pools }
|
|
9345
9943
|
};
|
|
9346
9944
|
}
|
|
9945
|
+
function realClaudeBinaryResolver(paths) {
|
|
9946
|
+
return realResolveClaudeBinary(resolveOwnBinaryCheck(paths, realContentSourcePath()));
|
|
9947
|
+
}
|
|
9347
9948
|
function realPrepareLaunchParams(paths, options) {
|
|
9348
9949
|
const { farm } = options;
|
|
9349
9950
|
return {
|
|
@@ -9351,7 +9952,7 @@ function realPrepareLaunchParams(paths, options) {
|
|
|
9351
9952
|
fs: realFsPort,
|
|
9352
9953
|
proc: options.proc,
|
|
9353
9954
|
log: options.log,
|
|
9354
|
-
resolveClaudeBinary:
|
|
9955
|
+
resolveClaudeBinary: realClaudeBinaryResolver(paths),
|
|
9355
9956
|
farm: farm.runtime,
|
|
9356
9957
|
headroom: realHeadroomPort(paths, { spawnDaemon: options.spawnDaemon, cwd: farm.runtime.cwd }),
|
|
9357
9958
|
frontdoor: realFrontDoorPort(paths, options.spawnDaemon),
|
|
@@ -9441,11 +10042,16 @@ export {
|
|
|
9441
10042
|
ProviderNotFoundError,
|
|
9442
10043
|
ProviderSchema,
|
|
9443
10044
|
RC_IDLE_EXPIRY_MS,
|
|
10045
|
+
RC_ORPC_PATH_PREFIX,
|
|
9444
10046
|
RC_PENDING_DEADLINE_MS,
|
|
9445
10047
|
RC_PERMISSION_MODES,
|
|
9446
10048
|
RC_REQUEST_PARSE_CAP_BYTES,
|
|
9447
10049
|
RC_SESSIONS_PATH_PREFIX,
|
|
10050
|
+
RC_STREAM_BACKOFF_MS,
|
|
9448
10051
|
ROUTED_PATH_PREFIX,
|
|
10052
|
+
RcPermissionModeSchema,
|
|
10053
|
+
RcStreamEnvelopeSchema,
|
|
10054
|
+
RcStreamEventSchema,
|
|
9449
10055
|
UsageError,
|
|
9450
10056
|
UsageSnapshotError,
|
|
9451
10057
|
UsageSnapshotSchema,
|
|
@@ -9471,8 +10077,13 @@ export {
|
|
|
9471
10077
|
collectPoolPick,
|
|
9472
10078
|
createLeafCache,
|
|
9473
10079
|
createProfile,
|
|
10080
|
+
createRcApiNodeHandler,
|
|
10081
|
+
createRcApiRouter,
|
|
9474
10082
|
createRcControlHandler,
|
|
10083
|
+
createRcEventFanout,
|
|
9475
10084
|
createRcSessionTracker,
|
|
10085
|
+
createRcStreamHub,
|
|
10086
|
+
createSseParser,
|
|
9476
10087
|
describeProviderEndpoint,
|
|
9477
10088
|
detectAmbientCredential,
|
|
9478
10089
|
effectiveWindow,
|
|
@@ -9485,6 +10096,7 @@ export {
|
|
|
9485
10096
|
formatCheckReport,
|
|
9486
10097
|
formatDoctorReport,
|
|
9487
10098
|
forwardableHeaders,
|
|
10099
|
+
frontDoorRcApiClient,
|
|
9488
10100
|
frontDoorRcControl,
|
|
9489
10101
|
generateCa,
|
|
9490
10102
|
identityExists,
|
|
@@ -9502,6 +10114,7 @@ export {
|
|
|
9502
10114
|
mintLeaf,
|
|
9503
10115
|
observingRoutedRoute,
|
|
9504
10116
|
parseConnectTarget,
|
|
10117
|
+
parseRcStreamEnvelope,
|
|
9505
10118
|
planOf,
|
|
9506
10119
|
prepareClaudeLaunch,
|
|
9507
10120
|
prepareLaunch,
|
|
@@ -9523,6 +10136,7 @@ export {
|
|
|
9523
10136
|
realConnectEffects,
|
|
9524
10137
|
realRcControlTransport,
|
|
9525
10138
|
realRcEventDial,
|
|
10139
|
+
realRcStreamDial,
|
|
9526
10140
|
recoverFarm,
|
|
9527
10141
|
recoveryDiagnostics,
|
|
9528
10142
|
removeDirectoryRule,
|