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.mjs
CHANGED
|
@@ -10457,7 +10457,7 @@ function pageOfRcChannel(url, rows, idOf, rowOf) {
|
|
|
10457
10457
|
return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
|
|
10458
10458
|
}
|
|
10459
10459
|
async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
10460
|
-
const { request: request4, response,
|
|
10460
|
+
const { request: request4, response, host } = ctx;
|
|
10461
10461
|
if (tail === "client/presence" && method === "POST") {
|
|
10462
10462
|
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10463
10463
|
const clientId = nonEmptyString2(body, "client_id");
|
|
@@ -10467,11 +10467,11 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10467
10467
|
return true;
|
|
10468
10468
|
}
|
|
10469
10469
|
if (clear === true) {
|
|
10470
|
-
|
|
10471
|
-
ctx.log?.(`rc selfhost ${
|
|
10470
|
+
host.clients.delete(clientId);
|
|
10471
|
+
ctx.log?.(`rc selfhost ${host.id}: client ${clientId} cleared its presence`);
|
|
10472
10472
|
} else {
|
|
10473
|
-
|
|
10474
|
-
ctx.log?.(`rc selfhost ${
|
|
10473
|
+
host.clients.set(clientId, ctx.now());
|
|
10474
|
+
ctx.log?.(`rc selfhost ${host.id}: client ${clientId} announced its presence${host.clients.size === 1 ? "" : ` (${String(host.clients.size)} clients now announced)`}`);
|
|
10475
10475
|
}
|
|
10476
10476
|
await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
|
|
10477
10477
|
return true;
|
|
@@ -10484,8 +10484,8 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10484
10484
|
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" } });
|
|
10485
10485
|
return true;
|
|
10486
10486
|
}
|
|
10487
|
-
const receipt = ctx.publish(
|
|
10488
|
-
ctx.log?.(`rc selfhost ${
|
|
10487
|
+
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 } });
|
|
10488
|
+
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)}`);
|
|
10489
10489
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10490
10490
|
return true;
|
|
10491
10491
|
}
|
|
@@ -10502,16 +10502,16 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10502
10502
|
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" } });
|
|
10503
10503
|
return true;
|
|
10504
10504
|
}
|
|
10505
|
-
const published = ctx.publish(
|
|
10505
|
+
const published = ctx.publish(host.conversation, host.writer, RC_SOURCE_CLIENT, event.payload);
|
|
10506
10506
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10507
10507
|
}
|
|
10508
10508
|
ctx.sweep();
|
|
10509
|
-
ctx.log?.(`rc selfhost ${
|
|
10509
|
+
ctx.log?.(`rc selfhost ${host.id}: teleport write published ${String(results.length)} marker event(s)`);
|
|
10510
10510
|
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10511
10511
|
return true;
|
|
10512
10512
|
}
|
|
10513
10513
|
if (tail === "teleport-events" && method === "GET") {
|
|
10514
|
-
const page = pageOfRcChannel(request4.url,
|
|
10514
|
+
const page = pageOfRcChannel(request4.url, host.conversation.events, (event) => event.eventId, (event) => ({ event_id: event.eventId, payload: event.payload }));
|
|
10515
10515
|
if ("error" in page) {
|
|
10516
10516
|
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
|
|
10517
10517
|
return true;
|
|
@@ -10587,10 +10587,7 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10587
10587
|
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10588
10588
|
return;
|
|
10589
10589
|
}
|
|
10590
|
-
|
|
10591
|
-
sink.retire();
|
|
10592
|
-
}
|
|
10593
|
-
ctx.sessions.delete(session.id);
|
|
10590
|
+
ctx.retireSession(session);
|
|
10594
10591
|
ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
|
|
10595
10592
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10596
10593
|
return;
|
|
@@ -10598,6 +10595,202 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10598
10595
|
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
|
|
10599
10596
|
}
|
|
10600
10597
|
|
|
10598
|
+
// src/frontdoor/rcSelfHostConversations.ts
|
|
10599
|
+
var RC_CONVERSATIONS_PATH_PREFIX = "/v1/code/conversations";
|
|
10600
|
+
var RC_CONVERSATION_ID_PREFIX = "conv_";
|
|
10601
|
+
function isRecord8(value) {
|
|
10602
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10603
|
+
}
|
|
10604
|
+
function parseJsonObject3(body) {
|
|
10605
|
+
let parsed;
|
|
10606
|
+
try {
|
|
10607
|
+
parsed = JSON.parse(body);
|
|
10608
|
+
} catch {
|
|
10609
|
+
return void 0;
|
|
10610
|
+
}
|
|
10611
|
+
return isRecord8(parsed) ? parsed : void 0;
|
|
10612
|
+
}
|
|
10613
|
+
function nonEmptyString3(record, field) {
|
|
10614
|
+
const value = record === void 0 ? void 0 : record[field];
|
|
10615
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10616
|
+
}
|
|
10617
|
+
function newRcSelfHostConversation(newUuid, now) {
|
|
10618
|
+
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() };
|
|
10619
|
+
}
|
|
10620
|
+
function conversationRow(conversation) {
|
|
10621
|
+
return { id: conversation.id, created_at: new Date(conversation.createdAt).toISOString(), updated_at: new Date(conversation.lastTrafficAt).toISOString(), sessions: [...conversation.sessionIds] };
|
|
10622
|
+
}
|
|
10623
|
+
async function serveRcConversations(ctx, pathname, method) {
|
|
10624
|
+
const { request: request4, response, conversations } = ctx;
|
|
10625
|
+
const refusal = ctx.credentialRefusal(request4.headers);
|
|
10626
|
+
if (refusal !== void 0) {
|
|
10627
|
+
await ctx.unauthorized(response, refusal);
|
|
10628
|
+
return;
|
|
10629
|
+
}
|
|
10630
|
+
if (pathname === RC_CONVERSATIONS_PATH_PREFIX) {
|
|
10631
|
+
if (method !== "GET") {
|
|
10632
|
+
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" } });
|
|
10633
|
+
return;
|
|
10634
|
+
}
|
|
10635
|
+
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) });
|
|
10636
|
+
return;
|
|
10637
|
+
}
|
|
10638
|
+
const rest = pathname.slice(`${RC_CONVERSATIONS_PATH_PREFIX}/`.length);
|
|
10639
|
+
const slash = rest.indexOf("/");
|
|
10640
|
+
const conversationId = slash === -1 ? rest : rest.slice(0, slash);
|
|
10641
|
+
const tail = slash === -1 ? "" : rest.slice(slash + 1);
|
|
10642
|
+
if (!conversationId.startsWith(RC_CONVERSATION_ID_PREFIX)) {
|
|
10643
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "the conversation family names one conv_-prefixed conversation id" } });
|
|
10644
|
+
return;
|
|
10645
|
+
}
|
|
10646
|
+
const conversation = conversations.get(conversationId);
|
|
10647
|
+
if (conversation === void 0) {
|
|
10648
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: "no such Remote Control conversation on this door" } });
|
|
10649
|
+
return;
|
|
10650
|
+
}
|
|
10651
|
+
conversation.lastTrafficAt = ctx.now();
|
|
10652
|
+
if (tail === "" && method === "GET") {
|
|
10653
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { conversation: conversationRow(conversation) });
|
|
10654
|
+
return;
|
|
10655
|
+
}
|
|
10656
|
+
if (tail === "events" && method === "POST") {
|
|
10657
|
+
const body = parseJsonObject3(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10658
|
+
const events = body?.events;
|
|
10659
|
+
if (!Array.isArray(events) || events.length === 0 || events.length > ctx.maxBatchEvents) {
|
|
10660
|
+
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` } });
|
|
10661
|
+
return;
|
|
10662
|
+
}
|
|
10663
|
+
const results = [];
|
|
10664
|
+
for (const event of events) {
|
|
10665
|
+
if (!isRecord8(event) || !isRecord8(event.payload)) {
|
|
10666
|
+
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" } });
|
|
10667
|
+
return;
|
|
10668
|
+
}
|
|
10669
|
+
const published = ctx.publish(conversation, { session: void 0, half: "client" }, RC_SOURCE_CLIENT, event.payload);
|
|
10670
|
+
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10671
|
+
}
|
|
10672
|
+
ctx.sweep();
|
|
10673
|
+
ctx.log?.(`rc selfhost ${conversation.id}: a conversation client wrote ${String(results.length)} event(s) into the shared space`);
|
|
10674
|
+
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10675
|
+
return;
|
|
10676
|
+
}
|
|
10677
|
+
if (tail === "events" && method === "GET") {
|
|
10678
|
+
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 })) });
|
|
10679
|
+
return;
|
|
10680
|
+
}
|
|
10681
|
+
if (tail === "events/stream" && method === "GET") {
|
|
10682
|
+
await ctx.holdStream(conversation, conversation.id, "clientStreams", () => true, request4, response);
|
|
10683
|
+
return;
|
|
10684
|
+
}
|
|
10685
|
+
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)) {
|
|
10686
|
+
return;
|
|
10687
|
+
}
|
|
10688
|
+
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the conversation family serves no path "${tail}"` } });
|
|
10689
|
+
}
|
|
10690
|
+
|
|
10691
|
+
// src/frontdoor/rcSelfHostLocal.ts
|
|
10692
|
+
var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
|
|
10693
|
+
var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
|
|
10694
|
+
var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
|
|
10695
|
+
var MS_PER_SECOND5 = 1e3;
|
|
10696
|
+
var HTTP_SERVICE_UNAVAILABLE = 503;
|
|
10697
|
+
function isRecord9(value) {
|
|
10698
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10699
|
+
}
|
|
10700
|
+
function parseJsonObject4(body) {
|
|
10701
|
+
let parsed;
|
|
10702
|
+
try {
|
|
10703
|
+
parsed = JSON.parse(body);
|
|
10704
|
+
} catch {
|
|
10705
|
+
return void 0;
|
|
10706
|
+
}
|
|
10707
|
+
return isRecord9(parsed) ? parsed : void 0;
|
|
10708
|
+
}
|
|
10709
|
+
function growthBookFeatures() {
|
|
10710
|
+
return {
|
|
10711
|
+
tengu_ccr_bridge: { defaultValue: true },
|
|
10712
|
+
tengu_bridge_repl_v2: { defaultValue: true },
|
|
10713
|
+
tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
|
|
10714
|
+
tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
|
|
10715
|
+
};
|
|
10716
|
+
}
|
|
10717
|
+
var answerServerJson = (response, status, body) => {
|
|
10718
|
+
const text = JSON.stringify(body);
|
|
10719
|
+
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
10720
|
+
response.end(text);
|
|
10721
|
+
};
|
|
10722
|
+
function createRcSelfHostLocal(deps) {
|
|
10723
|
+
return {
|
|
10724
|
+
parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
|
|
10725
|
+
serve: async (host, request4, response) => {
|
|
10726
|
+
const method = (request4.method ?? "").toUpperCase();
|
|
10727
|
+
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
10728
|
+
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
10729
|
+
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
10730
|
+
const body = parseJsonObject4(await deps.readBody(request4, CONTROL_BODY_CAP_BYTES));
|
|
10731
|
+
const record = deps.credentialRecord();
|
|
10732
|
+
if (record === void 0) {
|
|
10733
|
+
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" });
|
|
10734
|
+
return true;
|
|
10735
|
+
}
|
|
10736
|
+
if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
|
|
10737
|
+
answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
|
|
10738
|
+
return true;
|
|
10739
|
+
}
|
|
10740
|
+
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(" ") });
|
|
10741
|
+
return true;
|
|
10742
|
+
}
|
|
10743
|
+
if (method === "GET" && pathname === "/v1/oauth/hello") {
|
|
10744
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
10745
|
+
return true;
|
|
10746
|
+
}
|
|
10747
|
+
return false;
|
|
10748
|
+
}
|
|
10749
|
+
if (method === "POST" && pathname.startsWith("/api/eval/")) {
|
|
10750
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
10751
|
+
answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
|
|
10752
|
+
return true;
|
|
10753
|
+
}
|
|
10754
|
+
if (method === "GET" && pathname === "/api/oauth/profile") {
|
|
10755
|
+
const record = deps.credentialRecord();
|
|
10756
|
+
if (record === void 0) {
|
|
10757
|
+
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" });
|
|
10758
|
+
return true;
|
|
10759
|
+
}
|
|
10760
|
+
answerServerJson(response, HTTP_STATUS.ok, {
|
|
10761
|
+
account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
10762
|
+
organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
|
|
10763
|
+
});
|
|
10764
|
+
return true;
|
|
10765
|
+
}
|
|
10766
|
+
if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
|
|
10767
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
10768
|
+
return true;
|
|
10769
|
+
}
|
|
10770
|
+
if (method === "POST" && pathname === "/api/oauth/validate") {
|
|
10771
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
10772
|
+
const record = deps.credentialRecord();
|
|
10773
|
+
if (record === void 0) {
|
|
10774
|
+
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" });
|
|
10775
|
+
return true;
|
|
10776
|
+
}
|
|
10777
|
+
answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
|
|
10778
|
+
return true;
|
|
10779
|
+
}
|
|
10780
|
+
if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
|
|
10781
|
+
await deps.readBody(request4, CONTROL_BODY_CAP_BYTES);
|
|
10782
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
10783
|
+
return true;
|
|
10784
|
+
}
|
|
10785
|
+
if (method === "GET" && pathname === "/api/hello") {
|
|
10786
|
+
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
10787
|
+
return true;
|
|
10788
|
+
}
|
|
10789
|
+
return false;
|
|
10790
|
+
}
|
|
10791
|
+
};
|
|
10792
|
+
}
|
|
10793
|
+
|
|
10601
10794
|
// src/frontdoor/rcWebFetch.ts
|
|
10602
10795
|
import * as dns2 from "node:dns";
|
|
10603
10796
|
import * as http2 from "node:http";
|
|
@@ -10849,17 +11042,14 @@ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
|
|
|
10849
11042
|
function rcSelfHostFromEnv(env) {
|
|
10850
11043
|
return env[RC_SELF_HOST_ENV] === "1";
|
|
10851
11044
|
}
|
|
10852
|
-
var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
|
|
10853
11045
|
var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
|
|
10854
11046
|
var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
|
|
10855
11047
|
var RC_SESSION_ID_PREFIX2 = "cse_";
|
|
10856
11048
|
var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
|
|
10857
|
-
var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
|
|
10858
11049
|
var HTTP_CONFLICT = 409;
|
|
10859
|
-
var
|
|
10860
|
-
var
|
|
11050
|
+
var MS_PER_SECOND6 = 1e3;
|
|
11051
|
+
var HTTP_SERVICE_UNAVAILABLE2 = 503;
|
|
10861
11052
|
var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
|
|
10862
|
-
var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
|
|
10863
11053
|
var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
|
|
10864
11054
|
var RC_SELF_HOST_RETENTION_MS = 6e5;
|
|
10865
11055
|
var RC_PRESENCE_REFRESH_SECONDS = 60;
|
|
@@ -10868,13 +11058,13 @@ var RC_SELF_HOST_WORKER_EPOCH = 1;
|
|
|
10868
11058
|
var RC_STREAM_EVENT_NAME2 = "client_event";
|
|
10869
11059
|
var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
|
|
10870
11060
|
var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
|
|
10871
|
-
function
|
|
11061
|
+
function isRecord10(value) {
|
|
10872
11062
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10873
11063
|
}
|
|
10874
11064
|
function isWebSearchRefusal(value) {
|
|
10875
11065
|
return !Array.isArray(value);
|
|
10876
11066
|
}
|
|
10877
|
-
function
|
|
11067
|
+
function nonEmptyString4(record, field) {
|
|
10878
11068
|
const value = record === void 0 ? void 0 : record[field];
|
|
10879
11069
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
10880
11070
|
}
|
|
@@ -10945,56 +11135,69 @@ async function readBody2(request4, capBytes) {
|
|
|
10945
11135
|
});
|
|
10946
11136
|
});
|
|
10947
11137
|
}
|
|
10948
|
-
function
|
|
11138
|
+
function parseJsonObject5(body) {
|
|
10949
11139
|
let parsed;
|
|
10950
11140
|
try {
|
|
10951
11141
|
parsed = JSON.parse(body);
|
|
10952
11142
|
} catch {
|
|
10953
11143
|
return void 0;
|
|
10954
11144
|
}
|
|
10955
|
-
return
|
|
11145
|
+
return isRecord10(parsed) ? parsed : void 0;
|
|
10956
11146
|
}
|
|
10957
11147
|
function mintWorkerJwt(randomToken, issuedAt) {
|
|
10958
11148
|
const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
10959
11149
|
const header = encode({ alg: "HS256", typ: "JWT" });
|
|
10960
|
-
const payload = encode({ exp: Math.floor((issuedAt + RC_SELF_HOST_WORKER_JWT_TTL_SECONDS *
|
|
11150
|
+
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) });
|
|
10961
11151
|
return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
|
|
10962
11152
|
}
|
|
10963
|
-
function growthBookFeatures() {
|
|
10964
|
-
return {
|
|
10965
|
-
tengu_ccr_bridge: { defaultValue: true },
|
|
10966
|
-
tengu_bridge_repl_v2: { defaultValue: true },
|
|
10967
|
-
tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
|
|
10968
|
-
tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
|
|
10969
|
-
};
|
|
10970
|
-
}
|
|
10971
11153
|
function createRcSelfHostSurface(deps) {
|
|
10972
11154
|
const sessions = /* @__PURE__ */ new Map();
|
|
11155
|
+
const conversations = /* @__PURE__ */ new Map();
|
|
11156
|
+
const retireSession = (session) => {
|
|
11157
|
+
for (const sink of [...session.conversation.workerStreams, ...session.conversation.clientStreams]) {
|
|
11158
|
+
if (sink.owner === session.id) {
|
|
11159
|
+
sink.retire();
|
|
11160
|
+
}
|
|
11161
|
+
}
|
|
11162
|
+
session.conversation.sessionIds.delete(session.id);
|
|
11163
|
+
sessions.delete(session.id);
|
|
11164
|
+
};
|
|
10973
11165
|
const sweep = () => {
|
|
10974
11166
|
const now = deps.now();
|
|
10975
11167
|
for (const session of sessions.values()) {
|
|
10976
|
-
|
|
10977
|
-
|
|
10978
|
-
|
|
11168
|
+
for (const [clientId, lastSeenAt] of session.clients) {
|
|
11169
|
+
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11170
|
+
session.clients.delete(clientId);
|
|
11171
|
+
}
|
|
11172
|
+
}
|
|
11173
|
+
const holdsStream = [...session.conversation.workerStreams, ...session.conversation.clientStreams].some((sink) => sink.owner === session.id);
|
|
11174
|
+
if (!holdsStream && now - session.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11175
|
+
retireSession(session);
|
|
11176
|
+
}
|
|
11177
|
+
}
|
|
11178
|
+
for (const conversation of conversations.values()) {
|
|
11179
|
+
while (conversation.events[0] !== void 0 && now - conversation.events[0].storedAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11180
|
+
const dropped = conversation.events.shift();
|
|
11181
|
+
const uuid = dropped === void 0 ? void 0 : nonEmptyString4(dropped.payload, "uuid");
|
|
10979
11182
|
if (uuid !== void 0) {
|
|
10980
|
-
|
|
11183
|
+
conversation.seenPayloadUuids.delete(uuid);
|
|
10981
11184
|
}
|
|
10982
11185
|
}
|
|
10983
|
-
for (const [clientId, lastSeenAt] of
|
|
11186
|
+
for (const [clientId, lastSeenAt] of conversation.clients) {
|
|
10984
11187
|
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
10985
|
-
|
|
11188
|
+
conversation.clients.delete(clientId);
|
|
10986
11189
|
}
|
|
10987
11190
|
}
|
|
10988
|
-
if (
|
|
10989
|
-
|
|
11191
|
+
if (conversation.sessionIds.size === 0 && conversation.workerStreams.size === 0 && conversation.clientStreams.size === 0 && now - conversation.lastTrafficAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11192
|
+
conversations.delete(conversation.id);
|
|
10990
11193
|
}
|
|
10991
11194
|
}
|
|
10992
11195
|
};
|
|
10993
11196
|
const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
|
|
10994
11197
|
timer.unref();
|
|
10995
11198
|
const keepalive = setInterval(() => {
|
|
10996
|
-
for (const
|
|
10997
|
-
for (const sink of [...
|
|
11199
|
+
for (const conversation of conversations.values()) {
|
|
11200
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
10998
11201
|
sink.write(": keep-alive\n\n");
|
|
10999
11202
|
}
|
|
11000
11203
|
}
|
|
@@ -11023,7 +11226,7 @@ function createRcSelfHostSurface(deps) {
|
|
|
11023
11226
|
const frameOf = (event) => {
|
|
11024
11227
|
const envelope = {
|
|
11025
11228
|
event_id: event.eventId,
|
|
11026
|
-
event_type:
|
|
11229
|
+
event_type: nonEmptyString4(event.payload, "type") ?? "event",
|
|
11027
11230
|
sequence_num: event.sequenceNum,
|
|
11028
11231
|
source: event.source,
|
|
11029
11232
|
payload: event.payload,
|
|
@@ -11035,14 +11238,16 @@ data: ${JSON.stringify(envelope)}
|
|
|
11035
11238
|
|
|
11036
11239
|
`;
|
|
11037
11240
|
};
|
|
11038
|
-
const
|
|
11241
|
+
const reachesWorkerStream = (event, owner) => !(event.writer.half === "worker" && event.writer.session === owner);
|
|
11242
|
+
const holdStream = async (conversation, owner, which, wantsReplay, request4, response) => {
|
|
11039
11243
|
const url = new URL(request4.url, "http://127.0.0.1");
|
|
11040
11244
|
const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
|
|
11041
|
-
const replay = cursor === void 0 ? [] :
|
|
11245
|
+
const replay = cursor === void 0 ? [] : conversation.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
|
|
11042
11246
|
response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
|
|
11043
11247
|
response.flush();
|
|
11044
11248
|
let pending = Promise.resolve();
|
|
11045
11249
|
const sink = {
|
|
11250
|
+
owner,
|
|
11046
11251
|
write: (frame) => {
|
|
11047
11252
|
pending = pending.then(async () => {
|
|
11048
11253
|
await response.write(frame);
|
|
@@ -11050,11 +11255,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11050
11255
|
});
|
|
11051
11256
|
},
|
|
11052
11257
|
retire: () => {
|
|
11053
|
-
|
|
11258
|
+
conversation[which].delete(sink);
|
|
11054
11259
|
}
|
|
11055
11260
|
};
|
|
11056
|
-
|
|
11057
|
-
|
|
11261
|
+
conversation[which].add(sink);
|
|
11262
|
+
conversation.lastTrafficAt = deps.now();
|
|
11058
11263
|
for (const event of replay) {
|
|
11059
11264
|
sink.write(frameOf(event));
|
|
11060
11265
|
}
|
|
@@ -11066,36 +11271,37 @@ data: ${JSON.stringify(envelope)}
|
|
|
11066
11271
|
sink.retire();
|
|
11067
11272
|
response.end();
|
|
11068
11273
|
};
|
|
11069
|
-
const publish = (
|
|
11070
|
-
const uuid =
|
|
11071
|
-
const duplicate = uuid !== void 0 &&
|
|
11072
|
-
|
|
11274
|
+
const publish = (conversation, writer, source, payload) => {
|
|
11275
|
+
const uuid = nonEmptyString4(payload, "uuid");
|
|
11276
|
+
const duplicate = uuid !== void 0 && conversation.seenPayloadUuids.has(uuid);
|
|
11277
|
+
conversation.sequenceNum += 1;
|
|
11073
11278
|
const event = {
|
|
11074
|
-
sequenceNum:
|
|
11279
|
+
sequenceNum: conversation.sequenceNum,
|
|
11075
11280
|
eventId: uuid ?? deps.newUuid(),
|
|
11076
11281
|
source,
|
|
11077
11282
|
// 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.
|
|
11078
11283
|
payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
|
|
11079
11284
|
createdAt: new Date(deps.now()).toISOString(),
|
|
11080
|
-
|
|
11285
|
+
writer,
|
|
11081
11286
|
storedAt: deps.now()
|
|
11082
11287
|
};
|
|
11083
|
-
|
|
11288
|
+
conversation.events.push(event);
|
|
11084
11289
|
if (uuid !== void 0) {
|
|
11085
|
-
|
|
11290
|
+
conversation.seenPayloadUuids.add(uuid);
|
|
11086
11291
|
}
|
|
11087
|
-
|
|
11292
|
+
conversation.lastTrafficAt = event.storedAt;
|
|
11088
11293
|
const frame = frameOf(event);
|
|
11089
|
-
|
|
11090
|
-
|
|
11294
|
+
for (const sink of [...conversation.workerStreams]) {
|
|
11295
|
+
if (reachesWorkerStream(event, sink.owner)) {
|
|
11091
11296
|
sink.write(frame);
|
|
11092
11297
|
}
|
|
11093
11298
|
}
|
|
11094
|
-
for (const sink of [...
|
|
11299
|
+
for (const sink of [...conversation.clientStreams]) {
|
|
11095
11300
|
sink.write(frame);
|
|
11096
11301
|
}
|
|
11097
11302
|
return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
|
|
11098
11303
|
};
|
|
11304
|
+
const sessionRow = (session) => ({ id: session.id, title: session.title, created_at: new Date(session.createdAt).toISOString(), status: "active", conversation_id: session.conversation.id });
|
|
11099
11305
|
const answerJson2 = async (response, status, body, extraHeaders = {}) => {
|
|
11100
11306
|
const text = JSON.stringify(body);
|
|
11101
11307
|
response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")), ...extraHeaders });
|
|
@@ -11103,7 +11309,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11103
11309
|
response.end();
|
|
11104
11310
|
};
|
|
11105
11311
|
const noCredential = async (response) => {
|
|
11106
|
-
await answerJson2(response,
|
|
11312
|
+
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" } });
|
|
11107
11313
|
};
|
|
11108
11314
|
const epochStale = async (response) => {
|
|
11109
11315
|
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 });
|
|
@@ -11131,8 +11337,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11131
11337
|
await unauthorized(response, refusal);
|
|
11132
11338
|
return;
|
|
11133
11339
|
}
|
|
11134
|
-
const body =
|
|
11135
|
-
const title =
|
|
11340
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11341
|
+
const title = nonEmptyString4(body, "title");
|
|
11136
11342
|
if (title === void 0) {
|
|
11137
11343
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a session update is JSON naming a non-empty title" } });
|
|
11138
11344
|
return;
|
|
@@ -11147,7 +11353,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11147
11353
|
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" } });
|
|
11148
11354
|
return;
|
|
11149
11355
|
}
|
|
11150
|
-
await answerJson2(response, HTTP_STATUS.ok, { session:
|
|
11356
|
+
await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
|
|
11151
11357
|
return;
|
|
11152
11358
|
}
|
|
11153
11359
|
if (tail === "bridge" && method === "POST") {
|
|
@@ -11168,10 +11374,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11168
11374
|
await unauthorized(response, refusal);
|
|
11169
11375
|
return;
|
|
11170
11376
|
}
|
|
11171
|
-
|
|
11172
|
-
sink.retire();
|
|
11173
|
-
}
|
|
11174
|
-
sessions.delete(session.id);
|
|
11377
|
+
retireSession(session);
|
|
11175
11378
|
deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
|
|
11176
11379
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
11177
11380
|
return;
|
|
@@ -11189,17 +11392,17 @@ data: ${JSON.stringify(envelope)}
|
|
|
11189
11392
|
return;
|
|
11190
11393
|
}
|
|
11191
11394
|
if (workerTail === "" && method === "PUT") {
|
|
11192
|
-
const body =
|
|
11395
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11193
11396
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11194
11397
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11195
11398
|
await epochStale(response);
|
|
11196
11399
|
return;
|
|
11197
11400
|
}
|
|
11198
|
-
const status =
|
|
11401
|
+
const status = nonEmptyString4(body, "worker_status");
|
|
11199
11402
|
if (status !== void 0) {
|
|
11200
11403
|
session.workerStatus = status;
|
|
11201
11404
|
}
|
|
11202
|
-
if (
|
|
11405
|
+
if (isRecord10(body?.external_metadata)) {
|
|
11203
11406
|
session.externalMetadata = body.external_metadata;
|
|
11204
11407
|
}
|
|
11205
11408
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
@@ -11216,7 +11419,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11216
11419
|
return;
|
|
11217
11420
|
}
|
|
11218
11421
|
if (workerTail === "events" && method === "POST") {
|
|
11219
|
-
const body =
|
|
11422
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11220
11423
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11221
11424
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11222
11425
|
await epochStale(response);
|
|
@@ -11229,11 +11432,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11229
11432
|
}
|
|
11230
11433
|
const results = [];
|
|
11231
11434
|
for (const event of events) {
|
|
11232
|
-
if (!
|
|
11435
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11233
11436
|
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" } });
|
|
11234
11437
|
return;
|
|
11235
11438
|
}
|
|
11236
|
-
const published = publish(session, RC_SOURCE_WORKER, event.payload);
|
|
11439
|
+
const published = publish(session.conversation, { session: session.id, half: "worker" }, RC_SOURCE_WORKER, event.payload);
|
|
11237
11440
|
results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
|
|
11238
11441
|
}
|
|
11239
11442
|
sweep();
|
|
@@ -11241,7 +11444,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11241
11444
|
return;
|
|
11242
11445
|
}
|
|
11243
11446
|
if (workerTail === "events/stream" && method === "GET") {
|
|
11244
|
-
await holdStream(session, "workerStreams", (event) => event.
|
|
11447
|
+
await holdStream(session.conversation, session.id, "workerStreams", (event) => reachesWorkerStream(event, session.id), request4, response);
|
|
11245
11448
|
return;
|
|
11246
11449
|
}
|
|
11247
11450
|
if (workerTail === "events/delivery" && method === "POST") {
|
|
@@ -11250,7 +11453,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11250
11453
|
return;
|
|
11251
11454
|
}
|
|
11252
11455
|
if (workerTail === "internal-events" && method === "POST") {
|
|
11253
|
-
const body =
|
|
11456
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11254
11457
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11255
11458
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11256
11459
|
await epochStale(response);
|
|
@@ -11262,7 +11465,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11262
11465
|
return;
|
|
11263
11466
|
}
|
|
11264
11467
|
for (const event of events) {
|
|
11265
|
-
if (
|
|
11468
|
+
if (isRecord10(event)) {
|
|
11266
11469
|
session.internalEvents.push({ eventId: deps.newUuid(), body: event });
|
|
11267
11470
|
}
|
|
11268
11471
|
}
|
|
@@ -11290,8 +11493,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11290
11493
|
return;
|
|
11291
11494
|
}
|
|
11292
11495
|
if (workerTail === "web-fetch" && method === "POST") {
|
|
11293
|
-
const body =
|
|
11294
|
-
const target =
|
|
11496
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11497
|
+
const target = nonEmptyString4(body, "url");
|
|
11295
11498
|
if (target === void 0) {
|
|
11296
11499
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
|
|
11297
11500
|
return;
|
|
@@ -11313,8 +11516,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11313
11516
|
return;
|
|
11314
11517
|
}
|
|
11315
11518
|
if (workerTail === "web-search" && method === "POST") {
|
|
11316
|
-
const body =
|
|
11317
|
-
const query =
|
|
11519
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11520
|
+
const query = nonEmptyString4(body, "query");
|
|
11318
11521
|
if (query === void 0) {
|
|
11319
11522
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
|
|
11320
11523
|
return;
|
|
@@ -11325,7 +11528,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11325
11528
|
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" } });
|
|
11326
11529
|
return;
|
|
11327
11530
|
}
|
|
11328
|
-
const searchProfile =
|
|
11531
|
+
const searchProfile = nonEmptyString4(body, "search_profile");
|
|
11329
11532
|
const backend = deps.webSearch;
|
|
11330
11533
|
if (backend === void 0) {
|
|
11331
11534
|
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
|
|
@@ -11350,7 +11553,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11350
11553
|
return;
|
|
11351
11554
|
}
|
|
11352
11555
|
if (tail === "events" && method === "POST") {
|
|
11353
|
-
const body =
|
|
11556
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11354
11557
|
const events = body?.events;
|
|
11355
11558
|
if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
|
|
11356
11559
|
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` } });
|
|
@@ -11358,11 +11561,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11358
11561
|
}
|
|
11359
11562
|
const results = [];
|
|
11360
11563
|
for (const event of events) {
|
|
11361
|
-
if (!
|
|
11564
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11362
11565
|
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" } });
|
|
11363
11566
|
return;
|
|
11364
11567
|
}
|
|
11365
|
-
const published = publish(session, RC_SOURCE_CLIENT, event.payload);
|
|
11568
|
+
const published = publish(session.conversation, { session: session.id, half: "client" }, RC_SOURCE_CLIENT, event.payload);
|
|
11366
11569
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
11367
11570
|
}
|
|
11368
11571
|
sweep();
|
|
@@ -11370,14 +11573,14 @@ data: ${JSON.stringify(envelope)}
|
|
|
11370
11573
|
return;
|
|
11371
11574
|
}
|
|
11372
11575
|
if (tail === "events" && method === "GET") {
|
|
11373
|
-
await answerJson2(response, HTTP_STATUS.ok, { data: session.events.map((event) => ({ event_id: event.eventId, event_type:
|
|
11576
|
+
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 })) });
|
|
11374
11577
|
return;
|
|
11375
11578
|
}
|
|
11376
11579
|
if (tail === "events/stream" && method === "GET") {
|
|
11377
|
-
await holdStream(session, "clientStreams", () => true, request4, response);
|
|
11580
|
+
await holdStream(session.conversation, session.id, "clientStreams", () => true, request4, response);
|
|
11378
11581
|
return;
|
|
11379
11582
|
}
|
|
11380
|
-
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)) {
|
|
11583
|
+
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)) {
|
|
11381
11584
|
return;
|
|
11382
11585
|
}
|
|
11383
11586
|
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
|
|
@@ -11395,7 +11598,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11395
11598
|
return;
|
|
11396
11599
|
}
|
|
11397
11600
|
if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
|
|
11398
|
-
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);
|
|
11601
|
+
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);
|
|
11602
|
+
return;
|
|
11603
|
+
}
|
|
11604
|
+
if (pathname === RC_CONVERSATIONS_PATH_PREFIX || pathname.startsWith(`${RC_CONVERSATIONS_PATH_PREFIX}/`)) {
|
|
11605
|
+
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);
|
|
11399
11606
|
return;
|
|
11400
11607
|
}
|
|
11401
11608
|
if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
|
|
@@ -11409,26 +11616,36 @@ data: ${JSON.stringify(envelope)}
|
|
|
11409
11616
|
await unauthorized(response, createRefusal);
|
|
11410
11617
|
return;
|
|
11411
11618
|
}
|
|
11412
|
-
const body =
|
|
11619
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11620
|
+
const namedConversation = nonEmptyString4(body, "conversation");
|
|
11621
|
+
let conversation;
|
|
11622
|
+
if (namedConversation === void 0) {
|
|
11623
|
+
conversation = newRcSelfHostConversation(deps.newUuid, deps.now);
|
|
11624
|
+
conversations.set(conversation.id, conversation);
|
|
11625
|
+
} else {
|
|
11626
|
+
conversation = conversations.get(namedConversation);
|
|
11627
|
+
if (conversation === void 0) {
|
|
11628
|
+
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `no such Remote Control conversation on this door: ${namedConversation}` } });
|
|
11629
|
+
return;
|
|
11630
|
+
}
|
|
11631
|
+
conversation.lastTrafficAt = deps.now();
|
|
11632
|
+
}
|
|
11413
11633
|
const session = {
|
|
11414
11634
|
id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
|
|
11415
11635
|
createdAt: deps.now(),
|
|
11416
|
-
title:
|
|
11636
|
+
title: nonEmptyString4(body, "title"),
|
|
11637
|
+
conversation,
|
|
11417
11638
|
workerJwts: /* @__PURE__ */ new Set(),
|
|
11418
|
-
sequenceNum: 0,
|
|
11419
|
-
events: [],
|
|
11420
|
-
seenPayloadUuids: /* @__PURE__ */ new Set(),
|
|
11421
|
-
workerStreams: /* @__PURE__ */ new Set(),
|
|
11422
|
-
clientStreams: /* @__PURE__ */ new Set(),
|
|
11423
11639
|
internalEvents: [],
|
|
11424
11640
|
clients: /* @__PURE__ */ new Map(),
|
|
11425
11641
|
workerStatus: void 0,
|
|
11426
11642
|
externalMetadata: void 0,
|
|
11427
11643
|
lastTrafficAt: deps.now()
|
|
11428
11644
|
};
|
|
11645
|
+
conversation.sessionIds.add(session.id);
|
|
11429
11646
|
sessions.set(session.id, session);
|
|
11430
|
-
deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
|
|
11431
|
-
await answerJson2(response, HTTP_STATUS.ok, { session:
|
|
11647
|
+
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"}`);
|
|
11648
|
+
await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
|
|
11432
11649
|
return;
|
|
11433
11650
|
}
|
|
11434
11651
|
if (method === "GET") {
|
|
@@ -11437,7 +11654,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11437
11654
|
await unauthorized(response, readRefusal);
|
|
11438
11655
|
return;
|
|
11439
11656
|
}
|
|
11440
|
-
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" })) });
|
|
11657
|
+
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 })) });
|
|
11441
11658
|
return;
|
|
11442
11659
|
}
|
|
11443
11660
|
await answerJson2(response, HTTP_STATUS.methodNotAllowed, { type: "error", error: { type: "api_error", message: "the session collection is a POST (create) and a GET (list)" } });
|
|
@@ -11454,92 +11671,20 @@ data: ${JSON.stringify(envelope)}
|
|
|
11454
11671
|
await serveSessionPath(sessions.get(id), tail, request4, response);
|
|
11455
11672
|
}
|
|
11456
11673
|
};
|
|
11457
|
-
const
|
|
11458
|
-
const text = JSON.stringify(body);
|
|
11459
|
-
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
11460
|
-
response.end(text);
|
|
11461
|
-
};
|
|
11462
|
-
const local = {
|
|
11463
|
-
parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
|
|
11464
|
-
serve: async (host, request4, response) => {
|
|
11465
|
-
const method = (request4.method ?? "").toUpperCase();
|
|
11466
|
-
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
11467
|
-
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
11468
|
-
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
11469
|
-
const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
|
|
11470
|
-
const record = deps.credentialRecord();
|
|
11471
|
-
if (record === void 0) {
|
|
11472
|
-
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" });
|
|
11473
|
-
return true;
|
|
11474
|
-
}
|
|
11475
|
-
if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
|
|
11476
|
-
answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
|
|
11477
|
-
return true;
|
|
11478
|
-
}
|
|
11479
|
-
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(" ") });
|
|
11480
|
-
return true;
|
|
11481
|
-
}
|
|
11482
|
-
if (method === "GET" && pathname === "/v1/oauth/hello") {
|
|
11483
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11484
|
-
return true;
|
|
11485
|
-
}
|
|
11486
|
-
return false;
|
|
11487
|
-
}
|
|
11488
|
-
if (method === "POST" && pathname.startsWith("/api/eval/")) {
|
|
11489
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11490
|
-
answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
|
|
11491
|
-
return true;
|
|
11492
|
-
}
|
|
11493
|
-
if (method === "GET" && pathname === "/api/oauth/profile") {
|
|
11494
|
-
const record = deps.credentialRecord();
|
|
11495
|
-
if (record === void 0) {
|
|
11496
|
-
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" });
|
|
11497
|
-
return true;
|
|
11498
|
-
}
|
|
11499
|
-
answerServerJson(response, HTTP_STATUS.ok, {
|
|
11500
|
-
account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
11501
|
-
organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
|
|
11502
|
-
});
|
|
11503
|
-
return true;
|
|
11504
|
-
}
|
|
11505
|
-
if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
|
|
11506
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11507
|
-
return true;
|
|
11508
|
-
}
|
|
11509
|
-
if (method === "POST" && pathname === "/api/oauth/validate") {
|
|
11510
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11511
|
-
const record = deps.credentialRecord();
|
|
11512
|
-
if (record === void 0) {
|
|
11513
|
-
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" });
|
|
11514
|
-
return true;
|
|
11515
|
-
}
|
|
11516
|
-
answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
|
|
11517
|
-
return true;
|
|
11518
|
-
}
|
|
11519
|
-
if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
|
|
11520
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11521
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11522
|
-
return true;
|
|
11523
|
-
}
|
|
11524
|
-
if (method === "GET" && pathname === "/api/hello") {
|
|
11525
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11526
|
-
return true;
|
|
11527
|
-
}
|
|
11528
|
-
return false;
|
|
11529
|
-
}
|
|
11530
|
-
};
|
|
11674
|
+
const local = createRcSelfHostLocal({ now: deps.now, credentialRecord: deps.credentialRecord, readBody: readBody2 });
|
|
11531
11675
|
return {
|
|
11532
11676
|
route,
|
|
11533
11677
|
local,
|
|
11534
11678
|
close: () => {
|
|
11535
11679
|
clearInterval(timer);
|
|
11536
11680
|
clearInterval(keepalive);
|
|
11537
|
-
for (const
|
|
11538
|
-
for (const sink of [...
|
|
11681
|
+
for (const conversation of conversations.values()) {
|
|
11682
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
11539
11683
|
sink.retire();
|
|
11540
11684
|
}
|
|
11541
11685
|
}
|
|
11542
11686
|
sessions.clear();
|
|
11687
|
+
conversations.clear();
|
|
11543
11688
|
}
|
|
11544
11689
|
};
|
|
11545
11690
|
}
|
|
@@ -11552,7 +11697,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
|
|
|
11552
11697
|
var RC_SELF_HOST_RECORD_FILE = "credential.json";
|
|
11553
11698
|
var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
|
|
11554
11699
|
var SELF_HOST_SUBSCRIPTION_TYPE = "max";
|
|
11555
|
-
function
|
|
11700
|
+
function isRecord11(value) {
|
|
11556
11701
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11557
11702
|
}
|
|
11558
11703
|
function readJsonObject(fs12, file) {
|
|
@@ -11566,7 +11711,7 @@ function readJsonObject(fs12, file) {
|
|
|
11566
11711
|
} catch {
|
|
11567
11712
|
return void 0;
|
|
11568
11713
|
}
|
|
11569
|
-
return
|
|
11714
|
+
return isRecord11(parsed) ? parsed : void 0;
|
|
11570
11715
|
}
|
|
11571
11716
|
function readRcSelfHostRecord(fs12, frontdoorDir) {
|
|
11572
11717
|
const parsed = readJsonObject(fs12, path28.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
|
|
@@ -11590,7 +11735,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11590
11735
|
const recordFile = path28.join(recordDir, RC_SELF_HOST_RECORD_FILE);
|
|
11591
11736
|
const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
|
|
11592
11737
|
const existingCredentials = readJsonObject(deps.fs, credentialsFile);
|
|
11593
|
-
const existingOauth =
|
|
11738
|
+
const existingOauth = isRecord11(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
|
|
11594
11739
|
const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
|
|
11595
11740
|
const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
|
|
11596
11741
|
if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
|
|
@@ -11607,7 +11752,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11607
11752
|
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)}
|
|
11608
11753
|
`);
|
|
11609
11754
|
const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
|
|
11610
|
-
const existingBlock = (value) =>
|
|
11755
|
+
const existingBlock = (value) => isRecord11(value) ? value : {};
|
|
11611
11756
|
deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
|
|
11612
11757
|
...claudeJson,
|
|
11613
11758
|
oauthAccount: {
|
|
@@ -11806,7 +11951,7 @@ import path30 from "node:path";
|
|
|
11806
11951
|
// package.json
|
|
11807
11952
|
var package_default = {
|
|
11808
11953
|
name: "agent-shim",
|
|
11809
|
-
version: "8.
|
|
11954
|
+
version: "8.27.1",
|
|
11810
11955
|
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.",
|
|
11811
11956
|
license: "MIT",
|
|
11812
11957
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -12222,6 +12367,11 @@ function buildFlagArgs(flags) {
|
|
|
12222
12367
|
function buildArgv(params) {
|
|
12223
12368
|
return [...params.toolFlags, ...params.extraFlags, ...params.passthrough];
|
|
12224
12369
|
}
|
|
12370
|
+
var LOOPBACK_HOSTS = ["127.0.0.1", "localhost", "::1"];
|
|
12371
|
+
function withLoopbackHosts(existing) {
|
|
12372
|
+
const listed = (existing ?? "").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
12373
|
+
return [...listed, ...LOOPBACK_HOSTS.filter((host) => !listed.includes(host))].join(",");
|
|
12374
|
+
}
|
|
12225
12375
|
function buildEnv(params) {
|
|
12226
12376
|
const env = { ...params.baseEnv };
|
|
12227
12377
|
const parentCustomHeaders = params.baseEnv.ANTHROPIC_CUSTOM_HEADERS;
|
|
@@ -12266,6 +12416,8 @@ function buildEnv(params) {
|
|
|
12266
12416
|
);
|
|
12267
12417
|
env.NODE_EXTRA_CA_CERTS = params.frontdoor.trustBundlePath;
|
|
12268
12418
|
env.HTTPS_PROXY = connectProxyUrl(params.frontdoor.connectPort, params.frontdoor.sessionToken);
|
|
12419
|
+
env.NO_PROXY = withLoopbackHosts(env.NO_PROXY);
|
|
12420
|
+
env.no_proxy = withLoopbackHosts(env.no_proxy);
|
|
12269
12421
|
}
|
|
12270
12422
|
return env;
|
|
12271
12423
|
}
|