agent-shim 8.26.0 → 8.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2173 -2021
- package/dist/index.cjs +332 -180
- package/dist/index.mjs +332 -180
- package/dist/types/index.d.ts +16 -9
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -10762,7 +10762,7 @@ function pageOfRcChannel(url, rows, idOf, rowOf) {
|
|
|
10762
10762
|
return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
|
|
10763
10763
|
}
|
|
10764
10764
|
async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
10765
|
-
const { request: request4, response,
|
|
10765
|
+
const { request: request4, response, host } = ctx;
|
|
10766
10766
|
if (tail === "client/presence" && method === "POST") {
|
|
10767
10767
|
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10768
10768
|
const clientId = nonEmptyString2(body, "client_id");
|
|
@@ -10772,11 +10772,11 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10772
10772
|
return true;
|
|
10773
10773
|
}
|
|
10774
10774
|
if (clear === true) {
|
|
10775
|
-
|
|
10776
|
-
ctx.log?.(`rc selfhost ${
|
|
10775
|
+
host.clients.delete(clientId);
|
|
10776
|
+
ctx.log?.(`rc selfhost ${host.id}: client ${clientId} cleared its presence`);
|
|
10777
10777
|
} else {
|
|
10778
|
-
|
|
10779
|
-
ctx.log?.(`rc selfhost ${
|
|
10778
|
+
host.clients.set(clientId, ctx.now());
|
|
10779
|
+
ctx.log?.(`rc selfhost ${host.id}: client ${clientId} announced its presence${host.clients.size === 1 ? "" : ` (${String(host.clients.size)} clients now announced)`}`);
|
|
10780
10780
|
}
|
|
10781
10781
|
await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
|
|
10782
10782
|
return true;
|
|
@@ -10789,8 +10789,8 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10789
10789
|
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a read receipt is JSON naming the event_id it read or none for the whole session, and client_id only as a non-empty string or absent" } });
|
|
10790
10790
|
return true;
|
|
10791
10791
|
}
|
|
10792
|
-
const receipt = ctx.publish(
|
|
10793
|
-
ctx.log?.(`rc selfhost ${
|
|
10792
|
+
const receipt = ctx.publish(host.conversation, host.writer, clientId ?? RC_SOURCE_CLIENT, { type: "mark_read", ...eventId === void 0 ? {} : { event_id: eventId }, ...clientId === void 0 ? {} : { client_id: clientId } });
|
|
10793
|
+
ctx.log?.(`rc selfhost ${host.id}: ${clientId === void 0 ? "an unnamed client" : `client ${clientId}`} marked ${eventId === void 0 ? "the session read" : `event ${eventId} read`} as sequence ${String(receipt.sequenceNum)}`);
|
|
10794
10794
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10795
10795
|
return true;
|
|
10796
10796
|
}
|
|
@@ -10807,16 +10807,16 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10807
10807
|
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a teleport write is an object carrying one payload object" } });
|
|
10808
10808
|
return true;
|
|
10809
10809
|
}
|
|
10810
|
-
const published = ctx.publish(
|
|
10810
|
+
const published = ctx.publish(host.conversation, host.writer, RC_SOURCE_CLIENT, event.payload);
|
|
10811
10811
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10812
10812
|
}
|
|
10813
10813
|
ctx.sweep();
|
|
10814
|
-
ctx.log?.(`rc selfhost ${
|
|
10814
|
+
ctx.log?.(`rc selfhost ${host.id}: teleport write published ${String(results.length)} marker event(s)`);
|
|
10815
10815
|
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10816
10816
|
return true;
|
|
10817
10817
|
}
|
|
10818
10818
|
if (tail === "teleport-events" && method === "GET") {
|
|
10819
|
-
const page = pageOfRcChannel(request4.url,
|
|
10819
|
+
const page = pageOfRcChannel(request4.url, host.conversation.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
|
|
10820
10820
|
if ("error" in page) {
|
|
10821
10821
|
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
|
|
10822
10822
|
return true;
|
|
@@ -10892,10 +10892,7 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10892
10892
|
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10893
10893
|
return;
|
|
10894
10894
|
}
|
|
10895
|
-
|
|
10896
|
-
sink.retire();
|
|
10897
|
-
}
|
|
10898
|
-
ctx.sessions.delete(session.id);
|
|
10895
|
+
ctx.retireSession(session);
|
|
10899
10896
|
ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
|
|
10900
10897
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10901
10898
|
return;
|
|
@@ -10903,6 +10900,202 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10903
10900
|
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
|
|
10904
10901
|
}
|
|
10905
10902
|
|
|
10903
|
+
// src/frontdoor/rcSelfHostConversations.ts
|
|
10904
|
+
var RC_CONVERSATIONS_PATH_PREFIX = "/v1/code/conversations";
|
|
10905
|
+
var RC_CONVERSATION_ID_PREFIX = "conv_";
|
|
10906
|
+
function isRecord8(value) {
|
|
10907
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10908
|
+
}
|
|
10909
|
+
function parseJsonObject3(body) {
|
|
10910
|
+
let parsed;
|
|
10911
|
+
try {
|
|
10912
|
+
parsed = JSON.parse(body);
|
|
10913
|
+
} catch {
|
|
10914
|
+
return void 0;
|
|
10915
|
+
}
|
|
10916
|
+
return isRecord8(parsed) ? parsed : void 0;
|
|
10917
|
+
}
|
|
10918
|
+
function nonEmptyString3(record, field) {
|
|
10919
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
10920
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10921
|
+
}
|
|
10922
|
+
function newRcSelfHostConversation(newUuid, now) {
|
|
10923
|
+
return { id: `${RC_CONVERSATION_ID_PREFIX}${newUuid()}`, createdAt: now(), sequenceNum: 0, events: [], seenPayloadUuids: /* @__PURE__ */ new Set(), workerStreams: /* @__PURE__ */ new Set(), clientStreams: /* @__PURE__ */ new Set(), clients: /* @__PURE__ */ new Map(), sessionIds: /* @__PURE__ */ new Set(), lastTrafficAt: now() };
|
|
10924
|
+
}
|
|
10925
|
+
function conversationRow(conversation) {
|
|
10926
|
+
return { id: conversation.id, created_at: new Date(conversation.createdAt).toISOString(), updated_at: new Date(conversation.lastTrafficAt).toISOString(), sessions: [...conversation.sessionIds] };
|
|
10927
|
+
}
|
|
10928
|
+
async function serveRcConversations(ctx, pathname, method) {
|
|
10929
|
+
const { request: request4, response, conversations } = ctx;
|
|
10930
|
+
const refusal = ctx.credentialRefusal(request4.headers);
|
|
10931
|
+
if (refusal !== void 0) {
|
|
10932
|
+
await ctx.unauthorized(response, refusal);
|
|
10933
|
+
return;
|
|
10934
|
+
}
|
|
10935
|
+
if (pathname === RC_CONVERSATIONS_PATH_PREFIX) {
|
|
10936
|
+
if (method !== "GET") {
|
|
10937
|
+
await ctx.answerJson(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the conversation collection is a GET; a conversation is born from a session create, never here" } });
|
|
10938
|
+
return;
|
|
10939
|
+
}
|
|
10940
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { data: [...conversations.values()].sort((left, right) => left.createdAt - right.createdAt || (left.id < right.id ? -1 : 1)).map(conversationRow) });
|
|
10941
|
+
return;
|
|
10942
|
+
}
|
|
10943
|
+
const rest = pathname.slice(`${RC_CONVERSATIONS_PATH_PREFIX}/`.length);
|
|
10944
|
+
const slash = rest.indexOf("/");
|
|
10945
|
+
const conversationId = slash === -1 ? rest : rest.slice(0, slash);
|
|
10946
|
+
const tail = slash === -1 ? "" : rest.slice(slash + 1);
|
|
10947
|
+
if (!conversationId.startsWith(RC_CONVERSATION_ID_PREFIX)) {
|
|
10948
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the conversation family names one conv_-prefixed conversation id" } });
|
|
10949
|
+
return;
|
|
10950
|
+
}
|
|
10951
|
+
const conversation = conversations.get(conversationId);
|
|
10952
|
+
if (conversation === void 0) {
|
|
10953
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control conversation on this door" } });
|
|
10954
|
+
return;
|
|
10955
|
+
}
|
|
10956
|
+
conversation.lastTrafficAt = ctx.now();
|
|
10957
|
+
if (tail === "" && method === "GET") {
|
|
10958
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { conversation: conversationRow(conversation) });
|
|
10959
|
+
return;
|
|
10960
|
+
}
|
|
10961
|
+
if (tail === "events" && method === "POST") {
|
|
10962
|
+
const body = parseJsonObject3(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10963
|
+
const events = body?.events;
|
|
10964
|
+
if (!Array.isArray(events) || events.length === 0 || events.length > ctx.maxBatchEvents) {
|
|
10965
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a client event write is a non-empty array of at most ${String(ctx.maxBatchEvents)} events` } });
|
|
10966
|
+
return;
|
|
10967
|
+
}
|
|
10968
|
+
const results = [];
|
|
10969
|
+
for (const event of events) {
|
|
10970
|
+
if (!isRecord8(event) || !isRecord8(event.payload)) {
|
|
10971
|
+
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
|
|
10972
|
+
return;
|
|
10973
|
+
}
|
|
10974
|
+
const published = ctx.publish(conversation, { session: void 0, half: "client" }, RC_SOURCE_CLIENT, event.payload);
|
|
10975
|
+
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10976
|
+
}
|
|
10977
|
+
ctx.sweep();
|
|
10978
|
+
ctx.log?.(`rc selfhost ${conversation.id}: a conversation client wrote ${String(results.length)} event(s) into the shared space`);
|
|
10979
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10980
|
+
return;
|
|
10981
|
+
}
|
|
10982
|
+
if (tail === "events" && method === "GET") {
|
|
10983
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { data: conversation.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString3(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
|
|
10984
|
+
return;
|
|
10985
|
+
}
|
|
10986
|
+
if (tail === "events/stream" && method === "GET") {
|
|
10987
|
+
await ctx.holdStream(conversation, conversation.id, "clientStreams", () => true, request4, response);
|
|
10988
|
+
return;
|
|
10989
|
+
}
|
|
10990
|
+
if (await serveRcClientChannels({ request: request4, response, host: { id: conversation.id, clients: conversation.clients, conversation, writer: { session: void 0, half: "client" } }, answerJson: ctx.answerJson, readBody: ctx.readBody, publish: ctx.publish, sweep: ctx.sweep, now: ctx.now, presenceRefreshSeconds: ctx.presenceRefreshSeconds, ...ctx.log === void 0 ? {} : { log: ctx.log } }, tail, method, ctx.maxBatchEvents)) {
|
|
10991
|
+
return;
|
|
10992
|
+
}
|
|
10993
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the conversation family serves no path "${tail}"` } });
|
|
10994
|
+
}
|
|
10995
|
+
|
|
10996
|
+
// src/frontdoor/rcSelfHostLocal.ts
|
|
10997
|
+
var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
|
|
10998
|
+
var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
|
|
10999
|
+
var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
|
|
11000
|
+
var MS_PER_SECOND5 = 1e3;
|
|
11001
|
+
var HTTP_SERVICE_UNAVAILABLE = 503;
|
|
11002
|
+
function isRecord9(value) {
|
|
11003
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11004
|
+
}
|
|
11005
|
+
function parseJsonObject4(body) {
|
|
11006
|
+
let parsed;
|
|
11007
|
+
try {
|
|
11008
|
+
parsed = JSON.parse(body);
|
|
11009
|
+
} catch {
|
|
11010
|
+
return void 0;
|
|
11011
|
+
}
|
|
11012
|
+
return isRecord9(parsed) ? parsed : void 0;
|
|
11013
|
+
}
|
|
11014
|
+
function growthBookFeatures() {
|
|
11015
|
+
return {
|
|
11016
|
+
tengu_ccr_bridge: { defaultValue: true },
|
|
11017
|
+
tengu_bridge_repl_v2: { defaultValue: true },
|
|
11018
|
+
tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
|
|
11019
|
+
tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
|
|
11020
|
+
};
|
|
11021
|
+
}
|
|
11022
|
+
var answerServerJson = (response, status, body) => {
|
|
11023
|
+
const text = JSON.stringify(body);
|
|
11024
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
11025
|
+
response.end(text);
|
|
11026
|
+
};
|
|
11027
|
+
function createRcSelfHostLocal(deps) {
|
|
11028
|
+
return {
|
|
11029
|
+
parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
|
|
11030
|
+
serve: async (host, request4, response) => {
|
|
11031
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
11032
|
+
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
11033
|
+
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
11034
|
+
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
11035
|
+
const body = parseJsonObject4(await deps.readBody(request4, CONTROL_BODY_CAP_BYTES));
|
|
11036
|
+
const record = deps.credentialRecord();
|
|
11037
|
+
if (record === void 0) {
|
|
11038
|
+
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11039
|
+
return true;
|
|
11040
|
+
}
|
|
11041
|
+
if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
|
|
11042
|
+
answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
|
|
11043
|
+
return true;
|
|
11044
|
+
}
|
|
11045
|
+
answerServerJson(response, HTTP_STATUS.ok, { access_token: record.accessToken, refresh_token: record.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
|
|
11046
|
+
return true;
|
|
11047
|
+
}
|
|
11048
|
+
if (method === "GET" && pathname === "/v1/oauth/hello") {
|
|
11049
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11050
|
+
return true;
|
|
11051
|
+
}
|
|
11052
|
+
return false;
|
|
11053
|
+
}
|
|
11054
|
+
if (method === "POST" && pathname.startsWith("/api/eval/")) {
|
|
11055
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
11056
|
+
answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
|
|
11057
|
+
return true;
|
|
11058
|
+
}
|
|
11059
|
+
if (method === "GET" && pathname === "/api/oauth/profile") {
|
|
11060
|
+
const record = deps.credentialRecord();
|
|
11061
|
+
if (record === void 0) {
|
|
11062
|
+
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11063
|
+
return true;
|
|
11064
|
+
}
|
|
11065
|
+
answerServerJson(response, HTTP_STATUS.ok, {
|
|
11066
|
+
account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
11067
|
+
organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
|
|
11068
|
+
});
|
|
11069
|
+
return true;
|
|
11070
|
+
}
|
|
11071
|
+
if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
|
|
11072
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11073
|
+
return true;
|
|
11074
|
+
}
|
|
11075
|
+
if (method === "POST" && pathname === "/api/oauth/validate") {
|
|
11076
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
11077
|
+
const record = deps.credentialRecord();
|
|
11078
|
+
if (record === void 0) {
|
|
11079
|
+
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11080
|
+
return true;
|
|
11081
|
+
}
|
|
11082
|
+
answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
|
|
11083
|
+
return true;
|
|
11084
|
+
}
|
|
11085
|
+
if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
|
|
11086
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
11087
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11088
|
+
return true;
|
|
11089
|
+
}
|
|
11090
|
+
if (method === "GET" && pathname === "/api/hello") {
|
|
11091
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11092
|
+
return true;
|
|
11093
|
+
}
|
|
11094
|
+
return false;
|
|
11095
|
+
}
|
|
11096
|
+
};
|
|
11097
|
+
}
|
|
11098
|
+
|
|
10906
11099
|
// src/frontdoor/rcWebFetch.ts
|
|
10907
11100
|
var dns2 = __toESM(require("node:dns"), 1);
|
|
10908
11101
|
var http2 = __toESM(require("node:http"), 1);
|
|
@@ -11154,17 +11347,14 @@ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
|
|
|
11154
11347
|
function rcSelfHostFromEnv(env) {
|
|
11155
11348
|
return env[RC_SELF_HOST_ENV] === "1";
|
|
11156
11349
|
}
|
|
11157
|
-
var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
|
|
11158
11350
|
var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
|
|
11159
11351
|
var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
|
|
11160
11352
|
var RC_SESSION_ID_PREFIX2 = "cse_";
|
|
11161
11353
|
var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
|
|
11162
|
-
var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
|
|
11163
11354
|
var HTTP_CONFLICT = 409;
|
|
11164
|
-
var
|
|
11165
|
-
var
|
|
11355
|
+
var MS_PER_SECOND6 = 1e3;
|
|
11356
|
+
var HTTP_SERVICE_UNAVAILABLE2 = 503;
|
|
11166
11357
|
var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
|
|
11167
|
-
var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
|
|
11168
11358
|
var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
|
|
11169
11359
|
var RC_SELF_HOST_RETENTION_MS = 6e5;
|
|
11170
11360
|
var RC_PRESENCE_REFRESH_SECONDS = 60;
|
|
@@ -11173,13 +11363,13 @@ var RC_SELF_HOST_WORKER_EPOCH = 1;
|
|
|
11173
11363
|
var RC_STREAM_EVENT_NAME2 = "client_event";
|
|
11174
11364
|
var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
|
|
11175
11365
|
var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
|
|
11176
|
-
function
|
|
11366
|
+
function isRecord10(value) {
|
|
11177
11367
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11178
11368
|
}
|
|
11179
11369
|
function isWebSearchRefusal(value) {
|
|
11180
11370
|
return !Array.isArray(value);
|
|
11181
11371
|
}
|
|
11182
|
-
function
|
|
11372
|
+
function nonEmptyString4(record, field) {
|
|
11183
11373
|
const value = record === void 0 ? void 0 : record[field];
|
|
11184
11374
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
11185
11375
|
}
|
|
@@ -11250,56 +11440,69 @@ async function readBody2(request4, capBytes) {
|
|
|
11250
11440
|
});
|
|
11251
11441
|
});
|
|
11252
11442
|
}
|
|
11253
|
-
function
|
|
11443
|
+
function parseJsonObject5(body) {
|
|
11254
11444
|
let parsed;
|
|
11255
11445
|
try {
|
|
11256
11446
|
parsed = JSON.parse(body);
|
|
11257
11447
|
} catch {
|
|
11258
11448
|
return void 0;
|
|
11259
11449
|
}
|
|
11260
|
-
return
|
|
11450
|
+
return isRecord10(parsed) ? parsed : void 0;
|
|
11261
11451
|
}
|
|
11262
11452
|
function mintWorkerJwt(randomToken, issuedAt) {
|
|
11263
11453
|
const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
11264
11454
|
const header = encode({ alg: "HS256", typ: "JWT" });
|
|
11265
|
-
const payload = encode({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS *
|
|
11455
|
+
const payload = encode({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS * MS_PER_SECOND6) / MS_PER_SECOND6), iat: Math.floor(issuedAt / MS_PER_SECOND6) });
|
|
11266
11456
|
return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
|
|
11267
11457
|
}
|
|
11268
|
-
function growthBookFeatures() {
|
|
11269
|
-
return {
|
|
11270
|
-
tengu_ccr_bridge: { defaultValue: true },
|
|
11271
|
-
tengu_bridge_repl_v2: { defaultValue: true },
|
|
11272
|
-
tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
|
|
11273
|
-
tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
|
|
11274
|
-
};
|
|
11275
|
-
}
|
|
11276
11458
|
function createRcSelfHostSurface(deps) {
|
|
11277
11459
|
const sessions = /* @__PURE__ */ new Map();
|
|
11460
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
11461
|
+
const retireSession = (session) => {
|
|
11462
|
+
for (const sink of [...session.conversation.workerStreams, ...session.conversation.clientStreams]) {
|
|
11463
|
+
if (sink.owner === session.id) {
|
|
11464
|
+
sink.retire();
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11467
|
+
session.conversation.sessionIds.delete(session.id);
|
|
11468
|
+
sessions.delete(session.id);
|
|
11469
|
+
};
|
|
11278
11470
|
const sweep = () => {
|
|
11279
11471
|
const now = deps.now();
|
|
11280
11472
|
for (const session of sessions.values()) {
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11473
|
+
for (const [clientId, lastSeenAt] of session.clients) {
|
|
11474
|
+
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11475
|
+
session.clients.delete(clientId);
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
const holdsStream = [...session.conversation.workerStreams, ...session.conversation.clientStreams].some((sink) => sink.owner === session.id);
|
|
11479
|
+
if (!holdsStream && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11480
|
+
retireSession(session);
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11483
|
+
for (const conversation of conversations.values()) {
|
|
11484
|
+
while (conversation.events[0] !== void 0 && now - conversation.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11485
|
+
const dropped = conversation.events.shift();
|
|
11486
|
+
const uuid = dropped === void 0 ? void 0 : nonEmptyString4(dropped.payload, "uuid");
|
|
11284
11487
|
if (uuid !== void 0) {
|
|
11285
|
-
|
|
11488
|
+
conversation.seenPayloadUuids.delete(uuid);
|
|
11286
11489
|
}
|
|
11287
11490
|
}
|
|
11288
|
-
for (const [clientId, lastSeenAt] of
|
|
11491
|
+
for (const [clientId, lastSeenAt] of conversation.clients) {
|
|
11289
11492
|
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11290
|
-
|
|
11493
|
+
conversation.clients.delete(clientId);
|
|
11291
11494
|
}
|
|
11292
11495
|
}
|
|
11293
|
-
if (
|
|
11294
|
-
|
|
11496
|
+
if (conversation.sessionIds.size === 0 && conversation.workerStreams.size === 0 && conversation.clientStreams.size === 0 && now - conversation.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11497
|
+
conversations.delete(conversation.id);
|
|
11295
11498
|
}
|
|
11296
11499
|
}
|
|
11297
11500
|
};
|
|
11298
11501
|
const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
|
|
11299
11502
|
timer.unref();
|
|
11300
11503
|
const keepalive = setInterval(() => {
|
|
11301
|
-
for (const
|
|
11302
|
-
for (const sink of [...
|
|
11504
|
+
for (const conversation of conversations.values()) {
|
|
11505
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
11303
11506
|
sink.write(": keep-alive\n\n");
|
|
11304
11507
|
}
|
|
11305
11508
|
}
|
|
@@ -11328,7 +11531,7 @@ function createRcSelfHostSurface(deps) {
|
|
|
11328
11531
|
const frameOf = (event) => {
|
|
11329
11532
|
const envelope = {
|
|
11330
11533
|
event_id: event.eventId,
|
|
11331
|
-
event_type:
|
|
11534
|
+
event_type: nonEmptyString4(event.payload, "type") ?? "event",
|
|
11332
11535
|
sequence_num: event.sequenceNum,
|
|
11333
11536
|
source: event.source,
|
|
11334
11537
|
payload: event.payload,
|
|
@@ -11340,14 +11543,16 @@ data: ${JSON.stringify(envelope)}
|
|
|
11340
11543
|
|
|
11341
11544
|
`;
|
|
11342
11545
|
};
|
|
11343
|
-
const
|
|
11546
|
+
const reachesWorkerStream = (event, owner) => !(event.writer.half === "worker" && event.writer.session === owner);
|
|
11547
|
+
const holdStream = async (conversation, owner, which, wantsReplay, request4, response) => {
|
|
11344
11548
|
const url = new URL(request4.url, "http://127.0.0.1");
|
|
11345
11549
|
const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
|
|
11346
|
-
const replay = cursor === void 0 ? [] :
|
|
11550
|
+
const replay = cursor === void 0 ? [] : conversation.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
|
|
11347
11551
|
response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
|
|
11348
11552
|
response.flush();
|
|
11349
11553
|
let pending = Promise.resolve();
|
|
11350
11554
|
const sink = {
|
|
11555
|
+
owner,
|
|
11351
11556
|
write: (frame) => {
|
|
11352
11557
|
pending = pending.then(async () => {
|
|
11353
11558
|
await response.write(frame);
|
|
@@ -11355,11 +11560,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11355
11560
|
});
|
|
11356
11561
|
},
|
|
11357
11562
|
retire: () => {
|
|
11358
|
-
|
|
11563
|
+
conversation[which].delete(sink);
|
|
11359
11564
|
}
|
|
11360
11565
|
};
|
|
11361
|
-
|
|
11362
|
-
|
|
11566
|
+
conversation[which].add(sink);
|
|
11567
|
+
conversation.lastTrafficAt = deps.now();
|
|
11363
11568
|
for (const event of replay) {
|
|
11364
11569
|
sink.write(frameOf(event));
|
|
11365
11570
|
}
|
|
@@ -11371,36 +11576,37 @@ data: ${JSON.stringify(envelope)}
|
|
|
11371
11576
|
sink.retire();
|
|
11372
11577
|
response.end();
|
|
11373
11578
|
};
|
|
11374
|
-
const publish = (
|
|
11375
|
-
const uuid =
|
|
11376
|
-
const duplicate = uuid !== void 0 &&
|
|
11377
|
-
|
|
11579
|
+
const publish = (conversation, writer, source, payload) => {
|
|
11580
|
+
const uuid = nonEmptyString4(payload, "uuid");
|
|
11581
|
+
const duplicate = uuid !== void 0 && conversation.seenPayloadUuids.has(uuid);
|
|
11582
|
+
conversation.sequenceNum += 1;
|
|
11378
11583
|
const event = {
|
|
11379
|
-
sequenceNum:
|
|
11584
|
+
sequenceNum: conversation.sequenceNum,
|
|
11380
11585
|
eventId: uuid ?? deps.newUuid(),
|
|
11381
11586
|
source,
|
|
11382
11587
|
// The one enrichment the real service was observed making on the live rig's stream: the wall-clock instant it took the write. The worker's handler ignores it; the door's own parser forwards it verbatim.
|
|
11383
11588
|
payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
|
|
11384
11589
|
createdAt: new Date(deps.now()).toISOString(),
|
|
11385
|
-
|
|
11590
|
+
writer,
|
|
11386
11591
|
storedAt: deps.now()
|
|
11387
11592
|
};
|
|
11388
|
-
|
|
11593
|
+
conversation.events.push(event);
|
|
11389
11594
|
if (uuid !== void 0) {
|
|
11390
|
-
|
|
11595
|
+
conversation.seenPayloadUuids.add(uuid);
|
|
11391
11596
|
}
|
|
11392
|
-
|
|
11597
|
+
conversation.lastTrafficAt = event.storedAt;
|
|
11393
11598
|
const frame = frameOf(event);
|
|
11394
|
-
|
|
11395
|
-
|
|
11599
|
+
for (const sink of [...conversation.workerStreams]) {
|
|
11600
|
+
if (reachesWorkerStream(event, sink.owner)) {
|
|
11396
11601
|
sink.write(frame);
|
|
11397
11602
|
}
|
|
11398
11603
|
}
|
|
11399
|
-
for (const sink of [...
|
|
11604
|
+
for (const sink of [...conversation.clientStreams]) {
|
|
11400
11605
|
sink.write(frame);
|
|
11401
11606
|
}
|
|
11402
11607
|
return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
|
|
11403
11608
|
};
|
|
11609
|
+
const sessionRow = (session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active", conversation_id: session.conversation.id });
|
|
11404
11610
|
const answerJson2 = async (response, status, body, extraHeaders = {}) => {
|
|
11405
11611
|
const text = JSON.stringify(body);
|
|
11406
11612
|
response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")), ...extraHeaders });
|
|
@@ -11408,7 +11614,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11408
11614
|
response.end();
|
|
11409
11615
|
};
|
|
11410
11616
|
const noCredential = async (response) => {
|
|
11411
|
-
await answerJson2(response,
|
|
11617
|
+
await answerJson2(response, HTTP_SERVICE_UNAVAILABLE2, { type: "error", error: { type: "api_error", message: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it; run agent-shim frontdoor rc selfhost mint first" } });
|
|
11412
11618
|
};
|
|
11413
11619
|
const epochStale = async (response) => {
|
|
11414
11620
|
await answerJson2(response, HTTP_CONFLICT, { type: "error", error: { type: "api_error", message: "worker_epoch does not match the session's live epoch" } }, { [CONFLICT_REASON_HEADER2]: CONFLICT_REASON_EPOCH_STALE });
|
|
@@ -11436,8 +11642,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11436
11642
|
await unauthorized(response, refusal);
|
|
11437
11643
|
return;
|
|
11438
11644
|
}
|
|
11439
|
-
const body =
|
|
11440
|
-
const title =
|
|
11645
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11646
|
+
const title = nonEmptyString4(body, "title");
|
|
11441
11647
|
if (title === void 0) {
|
|
11442
11648
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
|
|
11443
11649
|
return;
|
|
@@ -11452,7 +11658,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11452
11658
|
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session read is a GET, and its update a PATCH or PUT" } });
|
|
11453
11659
|
return;
|
|
11454
11660
|
}
|
|
11455
|
-
await answerJson2(response, HTTP_STATUS.ok, { session:
|
|
11661
|
+
await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
|
|
11456
11662
|
return;
|
|
11457
11663
|
}
|
|
11458
11664
|
if (tail === "bridge" && method === "POST") {
|
|
@@ -11473,10 +11679,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11473
11679
|
await unauthorized(response, refusal);
|
|
11474
11680
|
return;
|
|
11475
11681
|
}
|
|
11476
|
-
|
|
11477
|
-
sink.retire();
|
|
11478
|
-
}
|
|
11479
|
-
sessions.delete(session.id);
|
|
11682
|
+
retireSession(session);
|
|
11480
11683
|
deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
|
|
11481
11684
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
11482
11685
|
return;
|
|
@@ -11494,17 +11697,17 @@ data: ${JSON.stringify(envelope)}
|
|
|
11494
11697
|
return;
|
|
11495
11698
|
}
|
|
11496
11699
|
if (workerTail === "" && method === "PUT") {
|
|
11497
|
-
const body =
|
|
11700
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11498
11701
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11499
11702
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11500
11703
|
await epochStale(response);
|
|
11501
11704
|
return;
|
|
11502
11705
|
}
|
|
11503
|
-
const status =
|
|
11706
|
+
const status = nonEmptyString4(body, "worker_status");
|
|
11504
11707
|
if (status !== void 0) {
|
|
11505
11708
|
session.workerStatus = status;
|
|
11506
11709
|
}
|
|
11507
|
-
if (
|
|
11710
|
+
if (isRecord10(body?.external_metadata)) {
|
|
11508
11711
|
session.externalMetadata = body.external_metadata;
|
|
11509
11712
|
}
|
|
11510
11713
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
@@ -11521,7 +11724,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11521
11724
|
return;
|
|
11522
11725
|
}
|
|
11523
11726
|
if (workerTail === "events" && method === "POST") {
|
|
11524
|
-
const body =
|
|
11727
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11525
11728
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11526
11729
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11527
11730
|
await epochStale(response);
|
|
@@ -11534,11 +11737,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11534
11737
|
}
|
|
11535
11738
|
const results = [];
|
|
11536
11739
|
for (const event of events) {
|
|
11537
|
-
if (!
|
|
11740
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11538
11741
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a batch is an object carrying one payload object" } });
|
|
11539
11742
|
return;
|
|
11540
11743
|
}
|
|
11541
|
-
const published = publish(session, RC_SOURCE_WORKER, event.payload);
|
|
11744
|
+
const published = publish(session.conversation, { session: session.id, half: "worker" }, RC_SOURCE_WORKER, event.payload);
|
|
11542
11745
|
results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
|
|
11543
11746
|
}
|
|
11544
11747
|
sweep();
|
|
@@ -11546,7 +11749,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11546
11749
|
return;
|
|
11547
11750
|
}
|
|
11548
11751
|
if (workerTail === "events/stream" && method === "GET") {
|
|
11549
|
-
await holdStream(session, "workerStreams", (event) => event.
|
|
11752
|
+
await holdStream(session.conversation, session.id, "workerStreams", (event) => reachesWorkerStream(event, session.id), request4, response);
|
|
11550
11753
|
return;
|
|
11551
11754
|
}
|
|
11552
11755
|
if (workerTail === "events/delivery" && method === "POST") {
|
|
@@ -11555,7 +11758,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11555
11758
|
return;
|
|
11556
11759
|
}
|
|
11557
11760
|
if (workerTail === "internal-events" && method === "POST") {
|
|
11558
|
-
const body =
|
|
11761
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11559
11762
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11560
11763
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11561
11764
|
await epochStale(response);
|
|
@@ -11567,7 +11770,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11567
11770
|
return;
|
|
11568
11771
|
}
|
|
11569
11772
|
for (const event of events) {
|
|
11570
|
-
if (
|
|
11773
|
+
if (isRecord10(event)) {
|
|
11571
11774
|
session.internalEvents.push({ eventId: deps.newUuid(), body: event });
|
|
11572
11775
|
}
|
|
11573
11776
|
}
|
|
@@ -11595,8 +11798,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11595
11798
|
return;
|
|
11596
11799
|
}
|
|
11597
11800
|
if (workerTail === "web-fetch" && method === "POST") {
|
|
11598
|
-
const body =
|
|
11599
|
-
const target =
|
|
11801
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11802
|
+
const target = nonEmptyString4(body, "url");
|
|
11600
11803
|
if (target === void 0) {
|
|
11601
11804
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
|
|
11602
11805
|
return;
|
|
@@ -11618,8 +11821,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11618
11821
|
return;
|
|
11619
11822
|
}
|
|
11620
11823
|
if (workerTail === "web-search" && method === "POST") {
|
|
11621
|
-
const body =
|
|
11622
|
-
const query =
|
|
11824
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11825
|
+
const query = nonEmptyString4(body, "query");
|
|
11623
11826
|
if (query === void 0) {
|
|
11624
11827
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
|
|
11625
11828
|
return;
|
|
@@ -11630,7 +11833,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11630
11833
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "the domain filters of a worker web-search are string arrays" } });
|
|
11631
11834
|
return;
|
|
11632
11835
|
}
|
|
11633
|
-
const searchProfile =
|
|
11836
|
+
const searchProfile = nonEmptyString4(body, "search_profile");
|
|
11634
11837
|
const backend = deps.webSearch;
|
|
11635
11838
|
if (backend === void 0) {
|
|
11636
11839
|
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
|
|
@@ -11655,7 +11858,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11655
11858
|
return;
|
|
11656
11859
|
}
|
|
11657
11860
|
if (tail === "events" && method === "POST") {
|
|
11658
|
-
const body =
|
|
11861
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11659
11862
|
const events = body?.events;
|
|
11660
11863
|
if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
|
|
11661
11864
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: `a client event write is a non-empty array of at most ${String(RC_SELF_HOST_MAX_BATCH_EVENTS)} events` } });
|
|
@@ -11663,11 +11866,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11663
11866
|
}
|
|
11664
11867
|
const results = [];
|
|
11665
11868
|
for (const event of events) {
|
|
11666
|
-
if (!
|
|
11869
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11667
11870
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "every event in a write is an object carrying one payload object" } });
|
|
11668
11871
|
return;
|
|
11669
11872
|
}
|
|
11670
|
-
const published = publish(session, RC_SOURCE_CLIENT, event.payload);
|
|
11873
|
+
const published = publish(session.conversation, { session: session.id, half: "client" }, RC_SOURCE_CLIENT, event.payload);
|
|
11671
11874
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
11672
11875
|
}
|
|
11673
11876
|
sweep();
|
|
@@ -11675,14 +11878,14 @@ data: ${JSON.stringify(envelope)}
|
|
|
11675
11878
|
return;
|
|
11676
11879
|
}
|
|
11677
11880
|
if (tail === "events" && method === "GET") {
|
|
11678
|
-
await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type:
|
|
11881
|
+
await answerJson2(response, HTTP_STATUS.ok, { data: session.conversation.events.map((event) => ({ event_id: event.eventId, event_type: nonEmptyString4(event.payload, "type") ?? "event", sequence_num: event.sequenceNum, source: event.source, payload: event.payload, created_at: event.createdAt })) });
|
|
11679
11882
|
return;
|
|
11680
11883
|
}
|
|
11681
11884
|
if (tail === "events/stream" && method === "GET") {
|
|
11682
|
-
await holdStream(session, "clientStreams", () => true, request4, response);
|
|
11885
|
+
await holdStream(session.conversation, session.id, "clientStreams", () => true, request4, response);
|
|
11683
11886
|
return;
|
|
11684
11887
|
}
|
|
11685
|
-
if (await serveRcClientChannels({ request: request4, response, session, answerJson: answerJson2, readBody: readBody2, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
|
|
11888
|
+
if (await serveRcClientChannels({ request: request4, response, host: { id: session.id, clients: session.clients, conversation: session.conversation, writer: { session: session.id, half: "client" } }, answerJson: answerJson2, readBody: readBody2, publish, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, ...deps.log === void 0 ? {} : { log: deps.log } }, tail, method, RC_SELF_HOST_MAX_BATCH_EVENTS)) {
|
|
11686
11889
|
return;
|
|
11687
11890
|
}
|
|
11688
11891
|
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
|
|
@@ -11700,7 +11903,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11700
11903
|
return;
|
|
11701
11904
|
}
|
|
11702
11905
|
if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
|
|
11703
|
-
await serveRcCompatSessions({ request: request4, response, sessions, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
|
|
11906
|
+
await serveRcCompatSessions({ request: request4, response, sessions, retireSession, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, now: deps.now, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, RC_COMPAT_SESSIONS_PATH);
|
|
11907
|
+
return;
|
|
11908
|
+
}
|
|
11909
|
+
if (pathname === RC_CONVERSATIONS_PATH_PREFIX || pathname.startsWith(`${RC_CONVERSATIONS_PATH_PREFIX}/`)) {
|
|
11910
|
+
await serveRcConversations({ request: request4, response, conversations, answerJson: answerJson2, readBody: readBody2, credentialRefusal, unauthorized, publish, holdStream, sweep, now: deps.now, presenceRefreshSeconds: RC_PRESENCE_REFRESH_SECONDS, maxBatchEvents: RC_SELF_HOST_MAX_BATCH_EVENTS, ...deps.log === void 0 ? {} : { log: deps.log } }, pathname, method);
|
|
11704
11911
|
return;
|
|
11705
11912
|
}
|
|
11706
11913
|
if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
|
|
@@ -11714,26 +11921,36 @@ data: ${JSON.stringify(envelope)}
|
|
|
11714
11921
|
await unauthorized(response, createRefusal);
|
|
11715
11922
|
return;
|
|
11716
11923
|
}
|
|
11717
|
-
const body =
|
|
11924
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11925
|
+
const namedConversation = nonEmptyString4(body, "conversation");
|
|
11926
|
+
let conversation;
|
|
11927
|
+
if (namedConversation === void 0) {
|
|
11928
|
+
conversation = newRcSelfHostConversation(deps.newUuid, deps.now);
|
|
11929
|
+
conversations.set(conversation.id, conversation);
|
|
11930
|
+
} else {
|
|
11931
|
+
conversation = conversations.get(namedConversation);
|
|
11932
|
+
if (conversation === void 0) {
|
|
11933
|
+
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `no such Remote Control conversation on this door: ${namedConversation}` } });
|
|
11934
|
+
return;
|
|
11935
|
+
}
|
|
11936
|
+
conversation.lastTrafficAt = deps.now();
|
|
11937
|
+
}
|
|
11718
11938
|
const session = {
|
|
11719
11939
|
id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
|
|
11720
11940
|
createdAt: deps.now(),
|
|
11721
|
-
title:
|
|
11941
|
+
title: nonEmptyString4(body, "title"),
|
|
11942
|
+
conversation,
|
|
11722
11943
|
workerJwts: /* @__PURE__ */ new Set(),
|
|
11723
|
-
sequenceNum: 0,
|
|
11724
|
-
events: [],
|
|
11725
|
-
seenPayloadUuids: /* @__PURE__ */ new Set(),
|
|
11726
|
-
workerStreams: /* @__PURE__ */ new Set(),
|
|
11727
|
-
clientStreams: /* @__PURE__ */ new Set(),
|
|
11728
11944
|
internalEvents: [],
|
|
11729
11945
|
clients: /* @__PURE__ */ new Map(),
|
|
11730
11946
|
workerStatus: void 0,
|
|
11731
11947
|
externalMetadata: void 0,
|
|
11732
11948
|
lastTrafficAt: deps.now()
|
|
11733
11949
|
};
|
|
11950
|
+
conversation.sessionIds.add(session.id);
|
|
11734
11951
|
sessions.set(session.id, session);
|
|
11735
|
-
deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
|
|
11736
|
-
await answerJson2(response, HTTP_STATUS.ok, { session:
|
|
11952
|
+
deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface, attached to conversation ${conversation.id}${namedConversation === void 0 ? "" : " at its client's request"}`);
|
|
11953
|
+
await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
|
|
11737
11954
|
return;
|
|
11738
11955
|
}
|
|
11739
11956
|
if (method === "GET") {
|
|
@@ -11742,7 +11959,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11742
11959
|
await unauthorized(response, readRefusal);
|
|
11743
11960
|
return;
|
|
11744
11961
|
}
|
|
11745
|
-
await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active" })) });
|
|
11962
|
+
await answerJson2(response, HTTP_STATUS.ok, { data: [...sessions.values()].map((session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), updated_at: new Date(session.lastTrafficAt).toISOString(), session_status: "active", conversation_id: session.conversation.id })) });
|
|
11746
11963
|
return;
|
|
11747
11964
|
}
|
|
11748
11965
|
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
|
|
@@ -11759,92 +11976,20 @@ data: ${JSON.stringify(envelope)}
|
|
|
11759
11976
|
await serveSessionPath(sessions.get(id), tail, request4, response);
|
|
11760
11977
|
}
|
|
11761
11978
|
};
|
|
11762
|
-
const
|
|
11763
|
-
const text = JSON.stringify(body);
|
|
11764
|
-
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
11765
|
-
response.end(text);
|
|
11766
|
-
};
|
|
11767
|
-
const local = {
|
|
11768
|
-
parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
|
|
11769
|
-
serve: async (host, request4, response) => {
|
|
11770
|
-
const method = (request4.method ?? "").toUpperCase();
|
|
11771
|
-
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
11772
|
-
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
11773
|
-
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
11774
|
-
const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
|
|
11775
|
-
const record = deps.credentialRecord();
|
|
11776
|
-
if (record === void 0) {
|
|
11777
|
-
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11778
|
-
return true;
|
|
11779
|
-
}
|
|
11780
|
-
if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
|
|
11781
|
-
answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
|
|
11782
|
-
return true;
|
|
11783
|
-
}
|
|
11784
|
-
answerServerJson(response, HTTP_STATUS.ok, { access_token: record.accessToken, refresh_token: record.refreshToken, expires_in: RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS, scope: RC_SELF_HOST_SCOPE_LIST.join(" ") });
|
|
11785
|
-
return true;
|
|
11786
|
-
}
|
|
11787
|
-
if (method === "GET" && pathname === "/v1/oauth/hello") {
|
|
11788
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11789
|
-
return true;
|
|
11790
|
-
}
|
|
11791
|
-
return false;
|
|
11792
|
-
}
|
|
11793
|
-
if (method === "POST" && pathname.startsWith("/api/eval/")) {
|
|
11794
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11795
|
-
answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
|
|
11796
|
-
return true;
|
|
11797
|
-
}
|
|
11798
|
-
if (method === "GET" && pathname === "/api/oauth/profile") {
|
|
11799
|
-
const record = deps.credentialRecord();
|
|
11800
|
-
if (record === void 0) {
|
|
11801
|
-
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11802
|
-
return true;
|
|
11803
|
-
}
|
|
11804
|
-
answerServerJson(response, HTTP_STATUS.ok, {
|
|
11805
|
-
account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
11806
|
-
organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
|
|
11807
|
-
});
|
|
11808
|
-
return true;
|
|
11809
|
-
}
|
|
11810
|
-
if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
|
|
11811
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11812
|
-
return true;
|
|
11813
|
-
}
|
|
11814
|
-
if (method === "POST" && pathname === "/api/oauth/validate") {
|
|
11815
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11816
|
-
const record = deps.credentialRecord();
|
|
11817
|
-
if (record === void 0) {
|
|
11818
|
-
answerServerJson(response, HTTP_SERVICE_UNAVAILABLE, { error: "the front door's self-hosted Remote Control surface is on but no credential has been minted for it" });
|
|
11819
|
-
return true;
|
|
11820
|
-
}
|
|
11821
|
-
answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
|
|
11822
|
-
return true;
|
|
11823
|
-
}
|
|
11824
|
-
if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
|
|
11825
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11826
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11827
|
-
return true;
|
|
11828
|
-
}
|
|
11829
|
-
if (method === "GET" && pathname === "/api/hello") {
|
|
11830
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11831
|
-
return true;
|
|
11832
|
-
}
|
|
11833
|
-
return false;
|
|
11834
|
-
}
|
|
11835
|
-
};
|
|
11979
|
+
const local = createRcSelfHostLocal({ now: deps.now, credentialRecord: deps.credentialRecord, readBody: readBody2 });
|
|
11836
11980
|
return {
|
|
11837
11981
|
route,
|
|
11838
11982
|
local,
|
|
11839
11983
|
close: () => {
|
|
11840
11984
|
clearInterval(timer);
|
|
11841
11985
|
clearInterval(keepalive);
|
|
11842
|
-
for (const
|
|
11843
|
-
for (const sink of [...
|
|
11986
|
+
for (const conversation of conversations.values()) {
|
|
11987
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
11844
11988
|
sink.retire();
|
|
11845
11989
|
}
|
|
11846
11990
|
}
|
|
11847
11991
|
sessions.clear();
|
|
11992
|
+
conversations.clear();
|
|
11848
11993
|
}
|
|
11849
11994
|
};
|
|
11850
11995
|
}
|
|
@@ -11857,7 +12002,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
|
|
|
11857
12002
|
var RC_SELF_HOST_RECORD_FILE = "credential.json";
|
|
11858
12003
|
var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
|
|
11859
12004
|
var SELF_HOST_SUBSCRIPTION_TYPE = "max";
|
|
11860
|
-
function
|
|
12005
|
+
function isRecord11(value) {
|
|
11861
12006
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11862
12007
|
}
|
|
11863
12008
|
function readJsonObject(fs12, file) {
|
|
@@ -11871,7 +12016,7 @@ function readJsonObject(fs12, file) {
|
|
|
11871
12016
|
} catch {
|
|
11872
12017
|
return void 0;
|
|
11873
12018
|
}
|
|
11874
|
-
return
|
|
12019
|
+
return isRecord11(parsed) ? parsed : void 0;
|
|
11875
12020
|
}
|
|
11876
12021
|
function readRcSelfHostRecord(fs12, frontdoorDir) {
|
|
11877
12022
|
const parsed = readJsonObject(fs12, import_node_path28.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
|
|
@@ -11895,7 +12040,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11895
12040
|
const recordFile = import_node_path28.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
|
|
11896
12041
|
const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
|
|
11897
12042
|
const existingCredentials = readJsonObject(deps.fs, credentialsFile);
|
|
11898
|
-
const existingOauth =
|
|
12043
|
+
const existingOauth = isRecord11(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
|
|
11899
12044
|
const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
|
|
11900
12045
|
const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
|
|
11901
12046
|
if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
|
|
@@ -11912,7 +12057,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11912
12057
|
deps.fs.writeFilePrivate(credentialsFile, `${JSON.stringify({ ...existingCredentials, claudeAiOauth: { accessToken, refreshToken, expiresAt: null, scopes: [...RC_SELF_HOST_SCOPE_LIST], subscriptionType: SELF_HOST_SUBSCRIPTION_TYPE, rateLimitTier: null } }, null, 2)}
|
|
11913
12058
|
`);
|
|
11914
12059
|
const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
|
|
11915
|
-
const existingBlock = (value) =>
|
|
12060
|
+
const existingBlock = (value) => isRecord11(value) ? value : {};
|
|
11916
12061
|
deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
|
|
11917
12062
|
...claudeJson,
|
|
11918
12063
|
oauthAccount: {
|
|
@@ -12111,7 +12256,7 @@ var import_node_path30 = __toESM(require("node:path"), 1);
|
|
|
12111
12256
|
// package.json
|
|
12112
12257
|
var package_default = {
|
|
12113
12258
|
name: "agent-shim",
|
|
12114
|
-
version: "8.
|
|
12259
|
+
version: "8.27.1",
|
|
12115
12260
|
description: "A profile manager and launcher for Claude Code that lets one person run multiple logins from one machine while controlling what gets shared between them.",
|
|
12116
12261
|
license: "MIT",
|
|
12117
12262
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -12527,6 +12672,11 @@ function buildFlagArgs(flags) {
|
|
|
12527
12672
|
function buildArgv(params) {
|
|
12528
12673
|
return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
|
|
12529
12674
|
}
|
|
12675
|
+
var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
|
|
12676
|
+
function withLoopbackHosts(existing) {
|
|
12677
|
+
const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
12678
|
+
return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
|
|
12679
|
+
}
|
|
12530
12680
|
function buildEnv(params) {
|
|
12531
12681
|
const env = { ...params.baseEnv };
|
|
12532
12682
|
const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
|
|
@@ -12571,6 +12721,8 @@ function buildEnv(params) {
|
|
|
12571
12721
|
);
|
|
12572
12722
|
env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
|
|
12573
12723
|
env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
|
|
12724
|
+
env.NO_PROXY = withLoopbackHosts(env.NO_PROXY);
|
|
12725
|
+
env.no_proxy = withLoopbackHosts(env.no_proxy);
|
|
12574
12726
|
}
|
|
12575
12727
|
return env;
|
|
12576
12728
|
}
|