agent-shim 8.25.0 → 8.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +2411 -2078
- package/dist/index.cjs +423 -185
- package/dist/index.mjs +417 -185
- package/dist/types/index.d.ts +287 -13
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -114,11 +114,15 @@ __export(index_exports, {
|
|
|
114
114
|
RC_WEB_FETCH_MAX_BYTES: () => RC_WEB_FETCH_MAX_BYTES,
|
|
115
115
|
RC_WEB_FETCH_MAX_REDIRECTS: () => RC_WEB_FETCH_MAX_REDIRECTS,
|
|
116
116
|
ROUTED_PATH_PREFIX: () => ROUTED_PATH_PREFIX,
|
|
117
|
+
RcLiveRateLimitSchema: () => RcLiveRateLimitSchema,
|
|
117
118
|
RcPermissionModeSchema: () => RcPermissionModeSchema,
|
|
119
|
+
RcRateLimitInfoSchema: () => RcRateLimitInfoSchema,
|
|
120
|
+
RcRateLimitWindowSchema: () => RcRateLimitWindowSchema,
|
|
118
121
|
RcStreamEnvelopeSchema: () => RcStreamEnvelopeSchema,
|
|
119
122
|
RcStreamEventSchema: () => RcStreamEventSchema,
|
|
120
123
|
UsageError: () => UsageError,
|
|
121
124
|
UsageListOutputSchema: () => UsageListOutputSchema,
|
|
125
|
+
UsageLiveOutputSchema: () => UsageLiveOutputSchema,
|
|
122
126
|
UsageSnapshotError: () => UsageSnapshotError,
|
|
123
127
|
UsageSnapshotSchema: () => UsageSnapshotSchema,
|
|
124
128
|
UsageWindowsInputSchema: () => UsageWindowsInputSchema,
|
|
@@ -170,6 +174,7 @@ __export(index_exports, {
|
|
|
170
174
|
createRcControlHandler: () => createRcControlHandler,
|
|
171
175
|
createRcCredentialStore: () => createRcCredentialStore,
|
|
172
176
|
createRcEventFanout: () => createRcEventFanout,
|
|
177
|
+
createRcLiveUsage: () => createRcLiveUsage,
|
|
173
178
|
createRcSelfHostSurface: () => createRcSelfHostSurface,
|
|
174
179
|
createRcSessionTracker: () => createRcSessionTracker,
|
|
175
180
|
createRcStreamHub: () => createRcStreamHub,
|
|
@@ -231,6 +236,7 @@ __export(index_exports, {
|
|
|
231
236
|
rcEventWriteResultFromAnswer: () => rcEventWriteResultFromAnswer,
|
|
232
237
|
rcFanoutOnDoorHub: () => rcFanoutOnDoorHub,
|
|
233
238
|
rcObserverAsPassthrough: () => rcObserverAsPassthrough,
|
|
239
|
+
rcRateLimitInfoOf: () => rcRateLimitInfoOf,
|
|
234
240
|
rcSelfHostFromEnv: () => rcSelfHostFromEnv,
|
|
235
241
|
rcWebFetchAllowsPrivateFromEnv: () => rcWebFetchAllowsPrivateFromEnv,
|
|
236
242
|
readActiveIdentity: () => readActiveIdentity,
|
|
@@ -5373,6 +5379,35 @@ var RcStreamEventSchema = import_zod4.z.strictObject({
|
|
|
5373
5379
|
session: import_zod4.z.string(),
|
|
5374
5380
|
envelope: RcStreamEnvelopeSchema
|
|
5375
5381
|
});
|
|
5382
|
+
var RcRateLimitWindowSchema = import_zod4.z.looseObject({
|
|
5383
|
+
/** The fraction of the window used, usually between 0 and 1, above 1 when usage legitimately runs past the window's cap (the CLI's own field description states both). */
|
|
5384
|
+
utilization: import_zod4.z.number().optional(),
|
|
5385
|
+
/** When the window resets, in unix epoch seconds: the unit is the CLI's own field description, stated in the 2.1.289 bundle's schema. */
|
|
5386
|
+
resetsAt: import_zod4.z.number().optional()
|
|
5387
|
+
});
|
|
5388
|
+
var RcRateLimitInfoSchema = import_zod4.z.looseObject({
|
|
5389
|
+
/** The limiting window's own status (`allowed`, `allowed_warning`, `rejected`), asserted only as a string because the vocabulary grows with the CLI. */
|
|
5390
|
+
status: import_zod4.z.string(),
|
|
5391
|
+
/** When the limiting window resets, in unix epoch seconds. */
|
|
5392
|
+
resetsAt: import_zod4.z.number().optional(),
|
|
5393
|
+
/** Which window the top-level fields describe (`five_hour`, `seven_day`, and kin), the payload's own name for it. */
|
|
5394
|
+
rateLimitType: import_zod4.z.string().optional(),
|
|
5395
|
+
/** The fraction of the limiting window used, on the same scale the per-window utilizations carry. */
|
|
5396
|
+
utilization: import_zod4.z.number().optional(),
|
|
5397
|
+
/** Whether usage beyond the plan (extra usage) is currently covering the overflow, the payload's own flag for it. */
|
|
5398
|
+
isUsingOverage: import_zod4.z.boolean().optional(),
|
|
5399
|
+
/** The overage's own status, the same vocabulary the limiting window's `status` carries. */
|
|
5400
|
+
overageStatus: import_zod4.z.string().optional(),
|
|
5401
|
+
/** When the overage window resets, in unix epoch seconds. */
|
|
5402
|
+
overageResetsAt: import_zod4.z.number().optional(),
|
|
5403
|
+
/** Each subscription window the account's response headers carried: the session (five-hour), weekly (seven-day), and overage-included weekly windows, each present only when the account's responses carry it. */
|
|
5404
|
+
unifiedWindows: import_zod4.z.looseObject({ five_hour: RcRateLimitWindowSchema.optional(), seven_day: RcRateLimitWindowSchema.optional(), seven_day_overage_included: RcRateLimitWindowSchema.optional() }).optional()
|
|
5405
|
+
});
|
|
5406
|
+
var RcLiveRateLimitSchema = import_zod4.z.strictObject({
|
|
5407
|
+
session: import_zod4.z.string(),
|
|
5408
|
+
observedAt: import_zod4.z.number(),
|
|
5409
|
+
rateLimit: RcRateLimitInfoSchema
|
|
5410
|
+
});
|
|
5376
5411
|
var RcSessionQuerySchema = import_zod4.z.strictObject({
|
|
5377
5412
|
session: import_zod4.z.string().min(1).optional()
|
|
5378
5413
|
});
|
|
@@ -9983,6 +10018,12 @@ var UsageWindowsOutputSchema = import_zod19.z.strictObject({
|
|
|
9983
10018
|
fiveHour: EffectiveWindowSchema.optional(),
|
|
9984
10019
|
sevenDay: EffectiveWindowSchema.optional()
|
|
9985
10020
|
});
|
|
10021
|
+
var UsageLiveOutputSchema = import_zod19.z.strictObject({
|
|
10022
|
+
/** The freshest observation the door holds, never narrowed by the read's session filter because it is the door's single latest statement of the account's quota; absent while no rate_limit_event has been filed at all. */
|
|
10023
|
+
latest: RcLiveRateLimitSchema.optional(),
|
|
10024
|
+
/** One observation per session whose stream has filed a rate_limit_event this door generation. */
|
|
10025
|
+
sessions: import_zod19.z.readonly(import_zod19.z.array(RcLiveRateLimitSchema))
|
|
10026
|
+
});
|
|
9986
10027
|
var FrontDoorSessionStatusSchema = import_zod19.z.strictObject({
|
|
9987
10028
|
pid: import_zod19.z.int().positive(),
|
|
9988
10029
|
startedAt: import_zod19.z.number(),
|
|
@@ -10097,6 +10138,16 @@ function createControlApiRouter(deps) {
|
|
|
10097
10138
|
...unified?.fiveHour === void 0 ? {} : { fiveHour: effectiveWindow(unified.fiveHour, now) },
|
|
10098
10139
|
...unified?.sevenDay === void 0 ? {} : { sevenDay: effectiveWindow(unified.sevenDay, now) }
|
|
10099
10140
|
};
|
|
10141
|
+
}),
|
|
10142
|
+
live: authed.input(RcSessionQuerySchema).output(UsageLiveOutputSchema).handler(({ input }) => {
|
|
10143
|
+
const sessions = deps.liveRateLimits(input.session);
|
|
10144
|
+
if (input.session !== void 0 && sessions.length === 0) {
|
|
10145
|
+
if (deps.list().some((session) => session.id === input.session)) {
|
|
10146
|
+
throw new import_client3.ORPCError("NOT_FOUND", { message: `session ${input.session} has filed no rate_limit_event on its stream yet: the worker emits one only once a turn has completed, so retry after this session's next turn` });
|
|
10147
|
+
}
|
|
10148
|
+
throw new import_client3.ORPCError("NOT_FOUND", { message: rcSessionNotObservedMessage(input.session) });
|
|
10149
|
+
}
|
|
10150
|
+
return { latest: deps.latestRateLimit(), sessions };
|
|
10100
10151
|
})
|
|
10101
10152
|
},
|
|
10102
10153
|
frontdoor: {
|
|
@@ -10317,6 +10368,42 @@ function createLaunchEventPublisher(hub, now) {
|
|
|
10317
10368
|
};
|
|
10318
10369
|
}
|
|
10319
10370
|
|
|
10371
|
+
// src/frontdoor/rcLiveUsage.ts
|
|
10372
|
+
var RC_RATE_LIMIT_EVENT_TYPE = "rate_limit_event";
|
|
10373
|
+
function isRecord5(value) {
|
|
10374
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10375
|
+
}
|
|
10376
|
+
function rcRateLimitInfoOf(event) {
|
|
10377
|
+
const payload = event.envelope.payload;
|
|
10378
|
+
if (!isRecord5(payload) || payload.type !== RC_RATE_LIMIT_EVENT_TYPE) {
|
|
10379
|
+
return void 0;
|
|
10380
|
+
}
|
|
10381
|
+
if (!isRecord5(payload.rate_limit_info)) {
|
|
10382
|
+
return void 0;
|
|
10383
|
+
}
|
|
10384
|
+
const parsed = RcRateLimitInfoSchema.safeParse(payload.rate_limit_info);
|
|
10385
|
+
return parsed.success ? parsed.data : void 0;
|
|
10386
|
+
}
|
|
10387
|
+
function createRcLiveUsage(deps) {
|
|
10388
|
+
const observed = /* @__PURE__ */ new Map();
|
|
10389
|
+
deps.hub.subscribe([DOOR_EVENT_SOURCE_RC], (event) => {
|
|
10390
|
+
const stream = RcStreamEventSchema.safeParse(event.payload);
|
|
10391
|
+
if (!stream.success) {
|
|
10392
|
+
return;
|
|
10393
|
+
}
|
|
10394
|
+
const rateLimit = rcRateLimitInfoOf(stream.data);
|
|
10395
|
+
if (rateLimit === void 0) {
|
|
10396
|
+
return;
|
|
10397
|
+
}
|
|
10398
|
+
observed.set(stream.data.session, { session: stream.data.session, observedAt: deps.now(), rateLimit });
|
|
10399
|
+
});
|
|
10400
|
+
const sorted = () => [...observed.values()].sort((left, right) => left.observedAt - right.observedAt || (left.session < right.session ? -1 : 1));
|
|
10401
|
+
return {
|
|
10402
|
+
liveOf: (sessionId) => sorted().filter((entry) => sessionId === void 0 || entry.session === sessionId),
|
|
10403
|
+
latestOf: () => sorted().at(-1)
|
|
10404
|
+
};
|
|
10405
|
+
}
|
|
10406
|
+
|
|
10320
10407
|
// src/frontdoor/rcStream.ts
|
|
10321
10408
|
var RC_STREAM_EVENT_NAME = "client_event";
|
|
10322
10409
|
var SSE_BYTE_ORDER_MARK = 65279;
|
|
@@ -10379,7 +10466,7 @@ function createSseParser() {
|
|
|
10379
10466
|
}
|
|
10380
10467
|
};
|
|
10381
10468
|
}
|
|
10382
|
-
function
|
|
10469
|
+
function isRecord6(value) {
|
|
10383
10470
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10384
10471
|
}
|
|
10385
10472
|
function sequenceNumOf(value) {
|
|
@@ -10401,7 +10488,7 @@ function parseRcStreamEnvelope(parsed) {
|
|
|
10401
10488
|
} catch {
|
|
10402
10489
|
return void 0;
|
|
10403
10490
|
}
|
|
10404
|
-
if (!
|
|
10491
|
+
if (!isRecord6(data)) {
|
|
10405
10492
|
return void 0;
|
|
10406
10493
|
}
|
|
10407
10494
|
const eventType = data.event_type;
|
|
@@ -10632,7 +10719,7 @@ function createRcStreamHub(deps) {
|
|
|
10632
10719
|
// src/frontdoor/rcSelfHostEndpoints.ts
|
|
10633
10720
|
var RC_SOURCE_WORKER = "worker";
|
|
10634
10721
|
var RC_SOURCE_CLIENT = "client";
|
|
10635
|
-
function
|
|
10722
|
+
function isRecord7(value) {
|
|
10636
10723
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10637
10724
|
}
|
|
10638
10725
|
function parseJsonObject2(body) {
|
|
@@ -10642,7 +10729,7 @@ function parseJsonObject2(body) {
|
|
|
10642
10729
|
} catch {
|
|
10643
10730
|
return void 0;
|
|
10644
10731
|
}
|
|
10645
|
-
return
|
|
10732
|
+
return isRecord7(parsed) ? parsed : void 0;
|
|
10646
10733
|
}
|
|
10647
10734
|
function nonEmptyString2(record, field) {
|
|
10648
10735
|
const value = record === void 0 ? void 0 : record[field];
|
|
@@ -10675,7 +10762,7 @@ function pageOfRcChannel(url, rows, idOf, rowOf) {
|
|
|
10675
10762
|
return { data: page.map(rowOf), ...last === void 0 ? {} : { next_cursor: idOf(last) } };
|
|
10676
10763
|
}
|
|
10677
10764
|
async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
10678
|
-
const { request: request4, response,
|
|
10765
|
+
const { request: request4, response, host } = ctx;
|
|
10679
10766
|
if (tail === "client/presence" && method === "POST") {
|
|
10680
10767
|
const body = parseJsonObject2(await ctx.readBody(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
10681
10768
|
const clientId = nonEmptyString2(body, "client_id");
|
|
@@ -10685,11 +10772,11 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10685
10772
|
return true;
|
|
10686
10773
|
}
|
|
10687
10774
|
if (clear === true) {
|
|
10688
|
-
|
|
10689
|
-
ctx.log?.(`rc selfhost ${
|
|
10775
|
+
host.clients.delete(clientId);
|
|
10776
|
+
ctx.log?.(`rc selfhost ${host.id}: client ${clientId} cleared its presence`);
|
|
10690
10777
|
} else {
|
|
10691
|
-
|
|
10692
|
-
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)`}`);
|
|
10693
10780
|
}
|
|
10694
10781
|
await ctx.answerJson(response, HTTP_STATUS.ok, { refresh_after_seconds: ctx.presenceRefreshSeconds });
|
|
10695
10782
|
return true;
|
|
@@ -10702,8 +10789,8 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10702
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" } });
|
|
10703
10790
|
return true;
|
|
10704
10791
|
}
|
|
10705
|
-
const receipt = ctx.publish(
|
|
10706
|
-
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)}`);
|
|
10707
10794
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10708
10795
|
return true;
|
|
10709
10796
|
}
|
|
@@ -10716,20 +10803,20 @@ async function serveRcClientChannels(ctx, tail, method, maxBatchEvents) {
|
|
|
10716
10803
|
}
|
|
10717
10804
|
const results = [];
|
|
10718
10805
|
for (const event of events) {
|
|
10719
|
-
if (!
|
|
10806
|
+
if (!isRecord7(event) || !isRecord7(event.payload)) {
|
|
10720
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" } });
|
|
10721
10808
|
return true;
|
|
10722
10809
|
}
|
|
10723
|
-
const published = ctx.publish(
|
|
10810
|
+
const published = ctx.publish(host.conversation, host.writer, RC_SOURCE_CLIENT, event.payload);
|
|
10724
10811
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
10725
10812
|
}
|
|
10726
10813
|
ctx.sweep();
|
|
10727
|
-
ctx.log?.(`rc selfhost ${
|
|
10814
|
+
ctx.log?.(`rc selfhost ${host.id}: teleport write published ${String(results.length)} marker event(s)`);
|
|
10728
10815
|
await ctx.answerJson(response, HTTP_STATUS.ok, { results });
|
|
10729
10816
|
return true;
|
|
10730
10817
|
}
|
|
10731
10818
|
if (tail === "teleport-events" && method === "GET") {
|
|
10732
|
-
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 }));
|
|
10733
10820
|
if ("error" in page) {
|
|
10734
10821
|
await ctx.answerJson(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: page.error } });
|
|
10735
10822
|
return true;
|
|
@@ -10805,10 +10892,7 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10805
10892
|
await ctx.answerJson(response, HTTP_STATUS.ok, compatSessionRow(session, ctx.now));
|
|
10806
10893
|
return;
|
|
10807
10894
|
}
|
|
10808
|
-
|
|
10809
|
-
sink.retire();
|
|
10810
|
-
}
|
|
10811
|
-
ctx.sessions.delete(session.id);
|
|
10895
|
+
ctx.retireSession(session);
|
|
10812
10896
|
ctx.log?.(`rc selfhost ${session.id}: archived through the compatibility family`);
|
|
10813
10897
|
await ctx.answerJson(response, HTTP_STATUS.ok, {});
|
|
10814
10898
|
return;
|
|
@@ -10816,6 +10900,202 @@ async function serveRcCompatSessions(ctx, pathname, compatPath) {
|
|
|
10816
10900
|
await ctx.answerJson(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the compatibility session family serves no path "${compatTail}"` } });
|
|
10817
10901
|
}
|
|
10818
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
|
+
|
|
10819
11099
|
// src/frontdoor/rcWebFetch.ts
|
|
10820
11100
|
var dns2 = __toESM(require("node:dns"), 1);
|
|
10821
11101
|
var http2 = __toESM(require("node:http"), 1);
|
|
@@ -11067,17 +11347,14 @@ var RC_SELF_HOST_ENV = "AGENT_SHIM_FRONTDOOR_RC_SELF_HOST";
|
|
|
11067
11347
|
function rcSelfHostFromEnv(env) {
|
|
11068
11348
|
return env[RC_SELF_HOST_ENV] === "1";
|
|
11069
11349
|
}
|
|
11070
|
-
var RC_SELF_HOST_SCOPE_LIST = ["user:profile", "user:inference", "user:sessions:claude_code", "user:mcp_servers", "user:file_upload"];
|
|
11071
11350
|
var RC_SESSIONS_PATH_PREFIX2 = "/v1/code/sessions";
|
|
11072
11351
|
var RC_COMPAT_SESSIONS_PATH = "/v1/sessions";
|
|
11073
11352
|
var RC_SESSION_ID_PREFIX2 = "cse_";
|
|
11074
11353
|
var RC_SELF_HOST_API_BASE_URL = "https://api.anthropic.com";
|
|
11075
|
-
var RC_SELF_HOST_OAUTH_HOST = "platform.claude.com";
|
|
11076
11354
|
var HTTP_CONFLICT = 409;
|
|
11077
|
-
var
|
|
11078
|
-
var
|
|
11355
|
+
var MS_PER_SECOND6 = 1e3;
|
|
11356
|
+
var HTTP_SERVICE_UNAVAILABLE2 = 503;
|
|
11079
11357
|
var RC_SELF_HOST_WORKER_JWT_TTL_SECONDS = 46800;
|
|
11080
|
-
var RC_SELF_HOST_OAUTH_TOKEN_TTL_SECONDS = 3600;
|
|
11081
11358
|
var RC_SELF_HOST_KEEPALIVE_MS = 15e3;
|
|
11082
11359
|
var RC_SELF_HOST_RETENTION_MS = 6e5;
|
|
11083
11360
|
var RC_PRESENCE_REFRESH_SECONDS = 60;
|
|
@@ -11086,13 +11363,13 @@ var RC_SELF_HOST_WORKER_EPOCH = 1;
|
|
|
11086
11363
|
var RC_STREAM_EVENT_NAME2 = "client_event";
|
|
11087
11364
|
var CONFLICT_REASON_HEADER2 = "x-ccr-conflict-reason";
|
|
11088
11365
|
var CONFLICT_REASON_EPOCH_STALE = "epoch_stale";
|
|
11089
|
-
function
|
|
11366
|
+
function isRecord10(value) {
|
|
11090
11367
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11091
11368
|
}
|
|
11092
11369
|
function isWebSearchRefusal(value) {
|
|
11093
11370
|
return !Array.isArray(value);
|
|
11094
11371
|
}
|
|
11095
|
-
function
|
|
11372
|
+
function nonEmptyString4(record, field) {
|
|
11096
11373
|
const value = record === void 0 ? void 0 : record[field];
|
|
11097
11374
|
return typeof value === "string" && value !== "" ? value : void 0;
|
|
11098
11375
|
}
|
|
@@ -11163,56 +11440,69 @@ async function readBody2(request4, capBytes) {
|
|
|
11163
11440
|
});
|
|
11164
11441
|
});
|
|
11165
11442
|
}
|
|
11166
|
-
function
|
|
11443
|
+
function parseJsonObject5(body) {
|
|
11167
11444
|
let parsed;
|
|
11168
11445
|
try {
|
|
11169
11446
|
parsed = JSON.parse(body);
|
|
11170
11447
|
} catch {
|
|
11171
11448
|
return void 0;
|
|
11172
11449
|
}
|
|
11173
|
-
return
|
|
11450
|
+
return isRecord10(parsed) ? parsed : void 0;
|
|
11174
11451
|
}
|
|
11175
11452
|
function mintWorkerJwt(randomToken, issuedAt) {
|
|
11176
11453
|
const encode = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
11177
11454
|
const header = encode({ alg: "HS256", typ: "JWT" });
|
|
11178
|
-
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) });
|
|
11179
11456
|
return `${header}.${payload}.${Buffer.from(randomToken(), "utf8").toString("base64url")}`;
|
|
11180
11457
|
}
|
|
11181
|
-
function growthBookFeatures() {
|
|
11182
|
-
return {
|
|
11183
|
-
tengu_ccr_bridge: { defaultValue: true },
|
|
11184
|
-
tengu_bridge_repl_v2: { defaultValue: true },
|
|
11185
|
-
tengu_bridge_repl_v2_cse_shim_enabled: { defaultValue: true },
|
|
11186
|
-
tengu_bridge_min_version: { defaultValue: { minVersion: "0.0.0" } }
|
|
11187
|
-
};
|
|
11188
|
-
}
|
|
11189
11458
|
function createRcSelfHostSurface(deps) {
|
|
11190
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
|
+
};
|
|
11191
11470
|
const sweep = () => {
|
|
11192
11471
|
const now = deps.now();
|
|
11193
11472
|
for (const session of sessions.values()) {
|
|
11194
|
-
|
|
11195
|
-
|
|
11196
|
-
|
|
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");
|
|
11197
11487
|
if (uuid !== void 0) {
|
|
11198
|
-
|
|
11488
|
+
conversation.seenPayloadUuids.delete(uuid);
|
|
11199
11489
|
}
|
|
11200
11490
|
}
|
|
11201
|
-
for (const [clientId, lastSeenAt] of
|
|
11491
|
+
for (const [clientId, lastSeenAt] of conversation.clients) {
|
|
11202
11492
|
if (now - lastSeenAt > RC_SELF_HOST_RETENTION_MS) {
|
|
11203
|
-
|
|
11493
|
+
conversation.clients.delete(clientId);
|
|
11204
11494
|
}
|
|
11205
11495
|
}
|
|
11206
|
-
if (
|
|
11207
|
-
|
|
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);
|
|
11208
11498
|
}
|
|
11209
11499
|
}
|
|
11210
11500
|
};
|
|
11211
11501
|
const timer = setInterval(sweep, RC_SELF_HOST_RETENTION_MS);
|
|
11212
11502
|
timer.unref();
|
|
11213
11503
|
const keepalive = setInterval(() => {
|
|
11214
|
-
for (const
|
|
11215
|
-
for (const sink of [...
|
|
11504
|
+
for (const conversation of conversations.values()) {
|
|
11505
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
11216
11506
|
sink.write(": keep-alive\n\n");
|
|
11217
11507
|
}
|
|
11218
11508
|
}
|
|
@@ -11241,7 +11531,7 @@ function createRcSelfHostSurface(deps) {
|
|
|
11241
11531
|
const frameOf = (event) => {
|
|
11242
11532
|
const envelope = {
|
|
11243
11533
|
event_id: event.eventId,
|
|
11244
|
-
event_type:
|
|
11534
|
+
event_type: nonEmptyString4(event.payload, "type") ?? "event",
|
|
11245
11535
|
sequence_num: event.sequenceNum,
|
|
11246
11536
|
source: event.source,
|
|
11247
11537
|
payload: event.payload,
|
|
@@ -11253,14 +11543,16 @@ data: ${JSON.stringify(envelope)}
|
|
|
11253
11543
|
|
|
11254
11544
|
`;
|
|
11255
11545
|
};
|
|
11256
|
-
const
|
|
11546
|
+
const reachesWorkerStream = (event, owner) => !(event.writer.half === "worker" && event.writer.session === owner);
|
|
11547
|
+
const holdStream = async (conversation, owner, which, wantsReplay, request4, response) => {
|
|
11257
11548
|
const url = new URL(request4.url, "http://127.0.0.1");
|
|
11258
11549
|
const cursor = numberOf2({ value: url.searchParams.get("from_sequence_num") }, "value") ?? numberOf2({ value: singleHeader2(request4.headers, "last-event-id") }, "value");
|
|
11259
|
-
const replay = cursor === void 0 ? [] :
|
|
11550
|
+
const replay = cursor === void 0 ? [] : conversation.events.filter((event) => wantsReplay(event) && event.sequenceNum > cursor);
|
|
11260
11551
|
response.start(HTTP_STATUS.ok, { "content-type": "text/event-stream", "cache-control": "no-cache" });
|
|
11261
11552
|
response.flush();
|
|
11262
11553
|
let pending = Promise.resolve();
|
|
11263
11554
|
const sink = {
|
|
11555
|
+
owner,
|
|
11264
11556
|
write: (frame) => {
|
|
11265
11557
|
pending = pending.then(async () => {
|
|
11266
11558
|
await response.write(frame);
|
|
@@ -11268,11 +11560,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11268
11560
|
});
|
|
11269
11561
|
},
|
|
11270
11562
|
retire: () => {
|
|
11271
|
-
|
|
11563
|
+
conversation[which].delete(sink);
|
|
11272
11564
|
}
|
|
11273
11565
|
};
|
|
11274
|
-
|
|
11275
|
-
|
|
11566
|
+
conversation[which].add(sink);
|
|
11567
|
+
conversation.lastTrafficAt = deps.now();
|
|
11276
11568
|
for (const event of replay) {
|
|
11277
11569
|
sink.write(frameOf(event));
|
|
11278
11570
|
}
|
|
@@ -11284,36 +11576,37 @@ data: ${JSON.stringify(envelope)}
|
|
|
11284
11576
|
sink.retire();
|
|
11285
11577
|
response.end();
|
|
11286
11578
|
};
|
|
11287
|
-
const publish = (
|
|
11288
|
-
const uuid =
|
|
11289
|
-
const duplicate = uuid !== void 0 &&
|
|
11290
|
-
|
|
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;
|
|
11291
11583
|
const event = {
|
|
11292
|
-
sequenceNum:
|
|
11584
|
+
sequenceNum: conversation.sequenceNum,
|
|
11293
11585
|
eventId: uuid ?? deps.newUuid(),
|
|
11294
11586
|
source,
|
|
11295
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.
|
|
11296
11588
|
payload: source === RC_SOURCE_WORKER ? payload : { ...payload, server_received_wall_ms: deps.now() },
|
|
11297
11589
|
createdAt: new Date(deps.now()).toISOString(),
|
|
11298
|
-
|
|
11590
|
+
writer,
|
|
11299
11591
|
storedAt: deps.now()
|
|
11300
11592
|
};
|
|
11301
|
-
|
|
11593
|
+
conversation.events.push(event);
|
|
11302
11594
|
if (uuid !== void 0) {
|
|
11303
|
-
|
|
11595
|
+
conversation.seenPayloadUuids.add(uuid);
|
|
11304
11596
|
}
|
|
11305
|
-
|
|
11597
|
+
conversation.lastTrafficAt = event.storedAt;
|
|
11306
11598
|
const frame = frameOf(event);
|
|
11307
|
-
|
|
11308
|
-
|
|
11599
|
+
for (const sink of [...conversation.workerStreams]) {
|
|
11600
|
+
if (reachesWorkerStream(event, sink.owner)) {
|
|
11309
11601
|
sink.write(frame);
|
|
11310
11602
|
}
|
|
11311
11603
|
}
|
|
11312
|
-
for (const sink of [...
|
|
11604
|
+
for (const sink of [...conversation.clientStreams]) {
|
|
11313
11605
|
sink.write(frame);
|
|
11314
11606
|
}
|
|
11315
11607
|
return { sequenceNum: event.sequenceNum, eventId: event.eventId, duplicate };
|
|
11316
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 });
|
|
11317
11610
|
const answerJson2 = async (response, status, body, extraHeaders = {}) => {
|
|
11318
11611
|
const text = JSON.stringify(body);
|
|
11319
11612
|
response.start(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")), ...extraHeaders });
|
|
@@ -11321,7 +11614,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11321
11614
|
response.end();
|
|
11322
11615
|
};
|
|
11323
11616
|
const noCredential = async (response) => {
|
|
11324
|
-
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" } });
|
|
11325
11618
|
};
|
|
11326
11619
|
const epochStale = async (response) => {
|
|
11327
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 });
|
|
@@ -11349,8 +11642,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11349
11642
|
await unauthorized(response, refusal);
|
|
11350
11643
|
return;
|
|
11351
11644
|
}
|
|
11352
|
-
const body =
|
|
11353
|
-
const title =
|
|
11645
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11646
|
+
const title = nonEmptyString4(body, "title");
|
|
11354
11647
|
if (title === void 0) {
|
|
11355
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" } });
|
|
11356
11649
|
return;
|
|
@@ -11365,7 +11658,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11365
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" } });
|
|
11366
11659
|
return;
|
|
11367
11660
|
}
|
|
11368
|
-
await answerJson2(response, HTTP_STATUS.ok, { session:
|
|
11661
|
+
await answerJson2(response, HTTP_STATUS.ok, { session: sessionRow(session) });
|
|
11369
11662
|
return;
|
|
11370
11663
|
}
|
|
11371
11664
|
if (tail === "bridge" && method === "POST") {
|
|
@@ -11386,10 +11679,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11386
11679
|
await unauthorized(response, refusal);
|
|
11387
11680
|
return;
|
|
11388
11681
|
}
|
|
11389
|
-
|
|
11390
|
-
sink.retire();
|
|
11391
|
-
}
|
|
11392
|
-
sessions.delete(session.id);
|
|
11682
|
+
retireSession(session);
|
|
11393
11683
|
deps.log?.(`rc selfhost ${session.id}: archived at its client's request`);
|
|
11394
11684
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
11395
11685
|
return;
|
|
@@ -11407,17 +11697,17 @@ data: ${JSON.stringify(envelope)}
|
|
|
11407
11697
|
return;
|
|
11408
11698
|
}
|
|
11409
11699
|
if (workerTail === "" && method === "PUT") {
|
|
11410
|
-
const body =
|
|
11700
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11411
11701
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11412
11702
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11413
11703
|
await epochStale(response);
|
|
11414
11704
|
return;
|
|
11415
11705
|
}
|
|
11416
|
-
const status =
|
|
11706
|
+
const status = nonEmptyString4(body, "worker_status");
|
|
11417
11707
|
if (status !== void 0) {
|
|
11418
11708
|
session.workerStatus = status;
|
|
11419
11709
|
}
|
|
11420
|
-
if (
|
|
11710
|
+
if (isRecord10(body?.external_metadata)) {
|
|
11421
11711
|
session.externalMetadata = body.external_metadata;
|
|
11422
11712
|
}
|
|
11423
11713
|
await answerJson2(response, HTTP_STATUS.ok, {});
|
|
@@ -11434,7 +11724,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11434
11724
|
return;
|
|
11435
11725
|
}
|
|
11436
11726
|
if (workerTail === "events" && method === "POST") {
|
|
11437
|
-
const body =
|
|
11727
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11438
11728
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11439
11729
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11440
11730
|
await epochStale(response);
|
|
@@ -11447,11 +11737,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11447
11737
|
}
|
|
11448
11738
|
const results = [];
|
|
11449
11739
|
for (const event of events) {
|
|
11450
|
-
if (!
|
|
11740
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11451
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" } });
|
|
11452
11742
|
return;
|
|
11453
11743
|
}
|
|
11454
|
-
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);
|
|
11455
11745
|
results.push({ event_id: published.eventId, sequence_num: String(published.sequenceNum) });
|
|
11456
11746
|
}
|
|
11457
11747
|
sweep();
|
|
@@ -11459,7 +11749,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11459
11749
|
return;
|
|
11460
11750
|
}
|
|
11461
11751
|
if (workerTail === "events/stream" && method === "GET") {
|
|
11462
|
-
await holdStream(session, "workerStreams", (event) => event.
|
|
11752
|
+
await holdStream(session.conversation, session.id, "workerStreams", (event) => reachesWorkerStream(event, session.id), request4, response);
|
|
11463
11753
|
return;
|
|
11464
11754
|
}
|
|
11465
11755
|
if (workerTail === "events/delivery" && method === "POST") {
|
|
@@ -11468,7 +11758,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11468
11758
|
return;
|
|
11469
11759
|
}
|
|
11470
11760
|
if (workerTail === "internal-events" && method === "POST") {
|
|
11471
|
-
const body =
|
|
11761
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11472
11762
|
const claimedEpoch = numberOf2(body, "worker_epoch");
|
|
11473
11763
|
if (claimedEpoch !== void 0 && claimedEpoch !== RC_SELF_HOST_WORKER_EPOCH) {
|
|
11474
11764
|
await epochStale(response);
|
|
@@ -11480,7 +11770,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11480
11770
|
return;
|
|
11481
11771
|
}
|
|
11482
11772
|
for (const event of events) {
|
|
11483
|
-
if (
|
|
11773
|
+
if (isRecord10(event)) {
|
|
11484
11774
|
session.internalEvents.push({ eventId: deps.newUuid(), body: event });
|
|
11485
11775
|
}
|
|
11486
11776
|
}
|
|
@@ -11508,8 +11798,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11508
11798
|
return;
|
|
11509
11799
|
}
|
|
11510
11800
|
if (workerTail === "web-fetch" && method === "POST") {
|
|
11511
|
-
const body =
|
|
11512
|
-
const target =
|
|
11801
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11802
|
+
const target = nonEmptyString4(body, "url");
|
|
11513
11803
|
if (target === void 0) {
|
|
11514
11804
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-fetch carries one url" } });
|
|
11515
11805
|
return;
|
|
@@ -11531,8 +11821,8 @@ data: ${JSON.stringify(envelope)}
|
|
|
11531
11821
|
return;
|
|
11532
11822
|
}
|
|
11533
11823
|
if (workerTail === "web-search" && method === "POST") {
|
|
11534
|
-
const body =
|
|
11535
|
-
const query =
|
|
11824
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11825
|
+
const query = nonEmptyString4(body, "query");
|
|
11536
11826
|
if (query === void 0) {
|
|
11537
11827
|
await answerJson2(response, HTTP_STATUS.badRequest, { type: "error", error: { type: "invalid_request_error", message: "a worker web-search carries one query" } });
|
|
11538
11828
|
return;
|
|
@@ -11543,7 +11833,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11543
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" } });
|
|
11544
11834
|
return;
|
|
11545
11835
|
}
|
|
11546
|
-
const searchProfile =
|
|
11836
|
+
const searchProfile = nonEmptyString4(body, "search_profile");
|
|
11547
11837
|
const backend = deps.webSearch;
|
|
11548
11838
|
if (backend === void 0) {
|
|
11549
11839
|
deps.log?.(`rc selfhost ${session.id}: worker web-search of "${query}" refused: no search backend serves this door`);
|
|
@@ -11568,7 +11858,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11568
11858
|
return;
|
|
11569
11859
|
}
|
|
11570
11860
|
if (tail === "events" && method === "POST") {
|
|
11571
|
-
const body =
|
|
11861
|
+
const body = parseJsonObject5(await readBody2(request4.body, CONTROL_BODY_CAP_BYTES));
|
|
11572
11862
|
const events = body?.events;
|
|
11573
11863
|
if (!Array.isArray(events) || events.length === 0 || events.length > RC_SELF_HOST_MAX_BATCH_EVENTS) {
|
|
11574
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` } });
|
|
@@ -11576,11 +11866,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11576
11866
|
}
|
|
11577
11867
|
const results = [];
|
|
11578
11868
|
for (const event of events) {
|
|
11579
|
-
if (!
|
|
11869
|
+
if (!isRecord10(event) || !isRecord10(event.payload)) {
|
|
11580
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" } });
|
|
11581
11871
|
return;
|
|
11582
11872
|
}
|
|
11583
|
-
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);
|
|
11584
11874
|
results.push({ sequence_num: String(published.sequenceNum), duplicate: published.duplicate });
|
|
11585
11875
|
}
|
|
11586
11876
|
sweep();
|
|
@@ -11588,14 +11878,14 @@ data: ${JSON.stringify(envelope)}
|
|
|
11588
11878
|
return;
|
|
11589
11879
|
}
|
|
11590
11880
|
if (tail === "events" && method === "GET") {
|
|
11591
|
-
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 })) });
|
|
11592
11882
|
return;
|
|
11593
11883
|
}
|
|
11594
11884
|
if (tail === "events/stream" && method === "GET") {
|
|
11595
|
-
await holdStream(session, "clientStreams", () => true, request4, response);
|
|
11885
|
+
await holdStream(session.conversation, session.id, "clientStreams", () => true, request4, response);
|
|
11596
11886
|
return;
|
|
11597
11887
|
}
|
|
11598
|
-
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)) {
|
|
11599
11889
|
return;
|
|
11600
11890
|
}
|
|
11601
11891
|
await answerJson2(response, HTTP_STATUS.notFound, { type: "error", error: { type: "api_error", message: `the self-hosted Remote Control surface serves no path "${tail}"` } });
|
|
@@ -11613,7 +11903,11 @@ data: ${JSON.stringify(envelope)}
|
|
|
11613
11903
|
return;
|
|
11614
11904
|
}
|
|
11615
11905
|
if (pathname === RC_COMPAT_SESSIONS_PATH || pathname.startsWith(`${RC_COMPAT_SESSIONS_PATH}/`)) {
|
|
11616
|
-
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);
|
|
11617
11911
|
return;
|
|
11618
11912
|
}
|
|
11619
11913
|
if (pathname !== RC_SESSIONS_PATH_PREFIX2 && !pathname.startsWith(`${RC_SESSIONS_PATH_PREFIX2}/`)) {
|
|
@@ -11627,26 +11921,36 @@ data: ${JSON.stringify(envelope)}
|
|
|
11627
11921
|
await unauthorized(response, createRefusal);
|
|
11628
11922
|
return;
|
|
11629
11923
|
}
|
|
11630
|
-
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
|
+
}
|
|
11631
11938
|
const session = {
|
|
11632
11939
|
id: `${RC_SESSION_ID_PREFIX2}${deps.newUuid()}`,
|
|
11633
11940
|
createdAt: deps.now(),
|
|
11634
|
-
title:
|
|
11941
|
+
title: nonEmptyString4(body, "title"),
|
|
11942
|
+
conversation,
|
|
11635
11943
|
workerJwts: /* @__PURE__ */ new Set(),
|
|
11636
|
-
sequenceNum: 0,
|
|
11637
|
-
events: [],
|
|
11638
|
-
seenPayloadUuids: /* @__PURE__ */ new Set(),
|
|
11639
|
-
workerStreams: /* @__PURE__ */ new Set(),
|
|
11640
|
-
clientStreams: /* @__PURE__ */ new Set(),
|
|
11641
11944
|
internalEvents: [],
|
|
11642
11945
|
clients: /* @__PURE__ */ new Map(),
|
|
11643
11946
|
workerStatus: void 0,
|
|
11644
11947
|
externalMetadata: void 0,
|
|
11645
11948
|
lastTrafficAt: deps.now()
|
|
11646
11949
|
};
|
|
11950
|
+
conversation.sessionIds.add(session.id);
|
|
11647
11951
|
sessions.set(session.id, session);
|
|
11648
|
-
deps.log?.(`rc selfhost ${session.id}: session created on the door's own surface`);
|
|
11649
|
-
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) });
|
|
11650
11954
|
return;
|
|
11651
11955
|
}
|
|
11652
11956
|
if (method === "GET") {
|
|
@@ -11655,7 +11959,7 @@ data: ${JSON.stringify(envelope)}
|
|
|
11655
11959
|
await unauthorized(response, readRefusal);
|
|
11656
11960
|
return;
|
|
11657
11961
|
}
|
|
11658
|
-
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 })) });
|
|
11659
11963
|
return;
|
|
11660
11964
|
}
|
|
11661
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)" } });
|
|
@@ -11672,92 +11976,20 @@ data: ${JSON.stringify(envelope)}
|
|
|
11672
11976
|
await serveSessionPath(sessions.get(id), tail, request4, response);
|
|
11673
11977
|
}
|
|
11674
11978
|
};
|
|
11675
|
-
const
|
|
11676
|
-
const text = JSON.stringify(body);
|
|
11677
|
-
response.writeHead(status, { "content-type": "application/json", "content-length": String(Buffer.byteLength(text, "utf8")) });
|
|
11678
|
-
response.end(text);
|
|
11679
|
-
};
|
|
11680
|
-
const local = {
|
|
11681
|
-
parsesHost: (host) => host === RC_SELF_HOST_OAUTH_HOST,
|
|
11682
|
-
serve: async (host, request4, response) => {
|
|
11683
|
-
const method = (request4.method ?? "").toUpperCase();
|
|
11684
|
-
const pathname = new URL(request4.url ?? "/", "http://127.0.0.1").pathname;
|
|
11685
|
-
if (host === RC_SELF_HOST_OAUTH_HOST) {
|
|
11686
|
-
if (method === "POST" && pathname === "/v1/oauth/token") {
|
|
11687
|
-
const body = parseJsonObject3(await readBody2(request4, CONTROL_BODY_CAP_BYTES));
|
|
11688
|
-
const record = deps.credentialRecord();
|
|
11689
|
-
if (record === void 0) {
|
|
11690
|
-
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" });
|
|
11691
|
-
return true;
|
|
11692
|
-
}
|
|
11693
|
-
if (body?.grant_type !== void 0 && body.grant_type !== "refresh_token") {
|
|
11694
|
-
answerServerJson(response, HTTP_STATUS.badRequest, { error: "unsupported_grant_type" });
|
|
11695
|
-
return true;
|
|
11696
|
-
}
|
|
11697
|
-
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(" ") });
|
|
11698
|
-
return true;
|
|
11699
|
-
}
|
|
11700
|
-
if (method === "GET" && pathname === "/v1/oauth/hello") {
|
|
11701
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11702
|
-
return true;
|
|
11703
|
-
}
|
|
11704
|
-
return false;
|
|
11705
|
-
}
|
|
11706
|
-
if (method === "POST" && pathname.startsWith("/api/eval/")) {
|
|
11707
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11708
|
-
answerServerJson(response, HTTP_STATUS.ok, { features: growthBookFeatures(), dateUpdated: Math.floor(deps.now() / MS_PER_SECOND5) });
|
|
11709
|
-
return true;
|
|
11710
|
-
}
|
|
11711
|
-
if (method === "GET" && pathname === "/api/oauth/profile") {
|
|
11712
|
-
const record = deps.credentialRecord();
|
|
11713
|
-
if (record === void 0) {
|
|
11714
|
-
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" });
|
|
11715
|
-
return true;
|
|
11716
|
-
}
|
|
11717
|
-
answerServerJson(response, HTTP_STATUS.ok, {
|
|
11718
|
-
account: { uuid: record.accountUuid, display_name: "agent-shim self-hosted", created_at: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
11719
|
-
organization: { uuid: record.organizationUuid, organization_type: "claude_max" }
|
|
11720
|
-
});
|
|
11721
|
-
return true;
|
|
11722
|
-
}
|
|
11723
|
-
if (method === "GET" && pathname === "/api/claude_code/policy_limits") {
|
|
11724
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11725
|
-
return true;
|
|
11726
|
-
}
|
|
11727
|
-
if (method === "POST" && pathname === "/api/oauth/validate") {
|
|
11728
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11729
|
-
const record = deps.credentialRecord();
|
|
11730
|
-
if (record === void 0) {
|
|
11731
|
-
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" });
|
|
11732
|
-
return true;
|
|
11733
|
-
}
|
|
11734
|
-
answerServerJson(response, HTTP_STATUS.ok, { scopes: [...RC_SELF_HOST_SCOPE_LIST], expiresAt: null, subscriptionType: "max", account_uuid: record.accountUuid, organization_uuid: record.organizationUuid });
|
|
11735
|
-
return true;
|
|
11736
|
-
}
|
|
11737
|
-
if (method === "POST" && (pathname === "/api/event_logging/v2/batch" || pathname === "/api/claude_code/metrics" || pathname === "/api/claude_cli_feedback")) {
|
|
11738
|
-
await readBody2(request4, CONTROL_BODY_CAP_BYTES);
|
|
11739
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11740
|
-
return true;
|
|
11741
|
-
}
|
|
11742
|
-
if (method === "GET" && pathname === "/api/hello") {
|
|
11743
|
-
answerServerJson(response, HTTP_STATUS.ok, {});
|
|
11744
|
-
return true;
|
|
11745
|
-
}
|
|
11746
|
-
return false;
|
|
11747
|
-
}
|
|
11748
|
-
};
|
|
11979
|
+
const local = createRcSelfHostLocal({ now: deps.now, credentialRecord: deps.credentialRecord, readBody: readBody2 });
|
|
11749
11980
|
return {
|
|
11750
11981
|
route,
|
|
11751
11982
|
local,
|
|
11752
11983
|
close: () => {
|
|
11753
11984
|
clearInterval(timer);
|
|
11754
11985
|
clearInterval(keepalive);
|
|
11755
|
-
for (const
|
|
11756
|
-
for (const sink of [...
|
|
11986
|
+
for (const conversation of conversations.values()) {
|
|
11987
|
+
for (const sink of [...conversation.workerStreams, ...conversation.clientStreams]) {
|
|
11757
11988
|
sink.retire();
|
|
11758
11989
|
}
|
|
11759
11990
|
}
|
|
11760
11991
|
sessions.clear();
|
|
11992
|
+
conversations.clear();
|
|
11761
11993
|
}
|
|
11762
11994
|
};
|
|
11763
11995
|
}
|
|
@@ -11770,7 +12002,7 @@ var RC_SELF_HOST_RECORD_DIR = "rc-selfhost";
|
|
|
11770
12002
|
var RC_SELF_HOST_RECORD_FILE = "credential.json";
|
|
11771
12003
|
var SELF_HOST_EMAIL = "selfhost@agent-shim.invalid";
|
|
11772
12004
|
var SELF_HOST_SUBSCRIPTION_TYPE = "max";
|
|
11773
|
-
function
|
|
12005
|
+
function isRecord11(value) {
|
|
11774
12006
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11775
12007
|
}
|
|
11776
12008
|
function readJsonObject(fs12, file) {
|
|
@@ -11784,7 +12016,7 @@ function readJsonObject(fs12, file) {
|
|
|
11784
12016
|
} catch {
|
|
11785
12017
|
return void 0;
|
|
11786
12018
|
}
|
|
11787
|
-
return
|
|
12019
|
+
return isRecord11(parsed) ? parsed : void 0;
|
|
11788
12020
|
}
|
|
11789
12021
|
function readRcSelfHostRecord(fs12, frontdoorDir) {
|
|
11790
12022
|
const parsed = readJsonObject(fs12, import_node_path28.default.join(frontdoorDir, RC_SELF_HOST_RECORD_DIR, RC_SELF_HOST_RECORD_FILE));
|
|
@@ -11808,7 +12040,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11808
12040
|
const recordFile = import_node_path28.default.join(recordDir, RC_SELF_HOST_RECORD_FILE);
|
|
11809
12041
|
const previousRecord = readRcSelfHostRecord(deps.fs, deps.frontdoorDir);
|
|
11810
12042
|
const existingCredentials = readJsonObject(deps.fs, credentialsFile);
|
|
11811
|
-
const existingOauth =
|
|
12043
|
+
const existingOauth = isRecord11(existingCredentials?.claudeAiOauth) ? existingCredentials.claudeAiOauth : void 0;
|
|
11812
12044
|
const existingToken = typeof existingOauth?.accessToken === "string" && existingOauth.accessToken !== "" ? existingOauth.accessToken : void 0;
|
|
11813
12045
|
const existingIsOwn = existingToken !== void 0 && previousRecord?.accessToken === existingToken;
|
|
11814
12046
|
if (existingToken !== void 0 && !existingIsOwn && !deps.force) {
|
|
@@ -11825,7 +12057,7 @@ function mintRcSelfHostCredential(deps) {
|
|
|
11825
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)}
|
|
11826
12058
|
`);
|
|
11827
12059
|
const claudeJson = readJsonObject(deps.fs, claudeJsonFile) ?? {};
|
|
11828
|
-
const existingBlock = (value) =>
|
|
12060
|
+
const existingBlock = (value) => isRecord11(value) ? value : {};
|
|
11829
12061
|
deps.fs.writeFilePrivate(claudeJsonFile, `${JSON.stringify({
|
|
11830
12062
|
...claudeJson,
|
|
11831
12063
|
oauthAccount: {
|
|
@@ -12024,7 +12256,7 @@ var import_node_path30 = __toESM(require("node:path"), 1);
|
|
|
12024
12256
|
// package.json
|
|
12025
12257
|
var package_default = {
|
|
12026
12258
|
name: "agent-shim",
|
|
12027
|
-
version: "8.
|
|
12259
|
+
version: "8.27.0",
|
|
12028
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.",
|
|
12029
12261
|
license: "MIT",
|
|
12030
12262
|
author: "Joseph Mearman <joseph@mearman.co.uk>",
|
|
@@ -13288,11 +13520,15 @@ function prepareClaudeLaunch(options) {
|
|
|
13288
13520
|
RC_WEB_FETCH_MAX_BYTES,
|
|
13289
13521
|
RC_WEB_FETCH_MAX_REDIRECTS,
|
|
13290
13522
|
ROUTED_PATH_PREFIX,
|
|
13523
|
+
RcLiveRateLimitSchema,
|
|
13291
13524
|
RcPermissionModeSchema,
|
|
13525
|
+
RcRateLimitInfoSchema,
|
|
13526
|
+
RcRateLimitWindowSchema,
|
|
13292
13527
|
RcStreamEnvelopeSchema,
|
|
13293
13528
|
RcStreamEventSchema,
|
|
13294
13529
|
UsageError,
|
|
13295
13530
|
UsageListOutputSchema,
|
|
13531
|
+
UsageLiveOutputSchema,
|
|
13296
13532
|
UsageSnapshotError,
|
|
13297
13533
|
UsageSnapshotSchema,
|
|
13298
13534
|
UsageWindowsInputSchema,
|
|
@@ -13344,6 +13580,7 @@ function prepareClaudeLaunch(options) {
|
|
|
13344
13580
|
createRcControlHandler,
|
|
13345
13581
|
createRcCredentialStore,
|
|
13346
13582
|
createRcEventFanout,
|
|
13583
|
+
createRcLiveUsage,
|
|
13347
13584
|
createRcSelfHostSurface,
|
|
13348
13585
|
createRcSessionTracker,
|
|
13349
13586
|
createRcStreamHub,
|
|
@@ -13405,6 +13642,7 @@ function prepareClaudeLaunch(options) {
|
|
|
13405
13642
|
rcEventWriteResultFromAnswer,
|
|
13406
13643
|
rcFanoutOnDoorHub,
|
|
13407
13644
|
rcObserverAsPassthrough,
|
|
13645
|
+
rcRateLimitInfoOf,
|
|
13408
13646
|
rcSelfHostFromEnv,
|
|
13409
13647
|
rcWebFetchAllowsPrivateFromEnv,
|
|
13410
13648
|
readActiveIdentity,
|