@standardagents/builder 0.41.7 → 0.41.9
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/built-in-routes.js +52 -40
- package/dist/built-in-routes.js.map +1 -1
- package/dist/{discovery-CIGNVJfG.d.ts → discovery-D5pmuazY.d.ts} +1 -1
- package/dist/{index-PSu6cMEz.d.ts → index-ChHAVogf.d.ts} +2 -2
- package/dist/index.d.ts +5 -5
- package/dist/index.js +367 -16
- package/dist/index.js.map +1 -1
- package/dist/packing.d.ts +3 -3
- package/dist/plugin.js +9 -1
- package/dist/plugin.js.map +1 -1
- package/dist/resolver-DWuGxI4w.d.ts +4557 -0
- package/dist/runtime.d.ts +10 -4519
- package/dist/runtime.js +359 -16
- package/dist/runtime.js.map +1 -1
- package/dist/test.d.ts +2 -2
- package/dist/{types-D1ABHvAH.d.ts → types-Dhd0M369.d.ts} +1 -1
- package/package.json +5 -5
package/dist/runtime.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getProviderModelPricing } from '@standardagents/provider-pricing';
|
|
2
2
|
import { jsonPathReader } from '@formkit/jsonreader';
|
|
3
|
-
import { ProviderError, mapReasoningLevel, validateAgentDefinition, THREAD_STREAM_REASONING_PARAM, isUserEndpoint } from '@standardagents/spec';
|
|
3
|
+
import { ProviderError, mapReasoningLevel, validateAgentDefinition, THREAD_STREAM_REASONING_PARAM, isUserRoom, isUserEndpoint } from '@standardagents/spec';
|
|
4
4
|
export { HANDLER_SYMBOL, ProviderError, belongsToPackage, defineAgent, defineHandler, defineHook, defineModel, definePrompt, defineProvider, defineTool, isHandler, isPacked, isVisibleInNamespace, mapReasoningLevel, providerConst, providerEnv, providerValue } from '@standardagents/spec';
|
|
5
5
|
import 'virtual:@standardagents-providers';
|
|
6
6
|
import { DurableObject, WorkerEntrypoint } from 'cloudflare:workers';
|
|
@@ -30198,6 +30198,41 @@ function canAccessThread(auth, thread) {
|
|
|
30198
30198
|
return thread.user_id === auth.user.id;
|
|
30199
30199
|
}
|
|
30200
30200
|
|
|
30201
|
+
// src/users/upstream-timing.ts
|
|
30202
|
+
var UPSTREAM_TIMING_HEADER = "x-standard-upstream-timing";
|
|
30203
|
+
var MAX_HEADER_CHARS = 512;
|
|
30204
|
+
function formatUpstreamTiming(name, durationMs) {
|
|
30205
|
+
const safe = Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 0;
|
|
30206
|
+
return `${name};dur=${Math.round(safe * 10) / 10}`;
|
|
30207
|
+
}
|
|
30208
|
+
function appendUpstreamTiming(current, name, durationMs) {
|
|
30209
|
+
const entry = formatUpstreamTiming(name, durationMs);
|
|
30210
|
+
if (!current) return entry;
|
|
30211
|
+
const combined = `${current}, ${entry}`;
|
|
30212
|
+
return combined.length > MAX_HEADER_CHARS ? current : combined;
|
|
30213
|
+
}
|
|
30214
|
+
function withUpstreamTiming(request, value) {
|
|
30215
|
+
const headers = new Headers(request.headers);
|
|
30216
|
+
headers.set(UPSTREAM_TIMING_HEADER, value);
|
|
30217
|
+
return new Request(request, { headers });
|
|
30218
|
+
}
|
|
30219
|
+
function responseWithUpstreamTiming(response, name, durationMs) {
|
|
30220
|
+
if (response.status === 101) return response;
|
|
30221
|
+
const value = appendUpstreamTiming(response.headers.get(UPSTREAM_TIMING_HEADER), name, durationMs);
|
|
30222
|
+
try {
|
|
30223
|
+
response.headers.set(UPSTREAM_TIMING_HEADER, value);
|
|
30224
|
+
return response;
|
|
30225
|
+
} catch {
|
|
30226
|
+
const headers = new Headers(response.headers);
|
|
30227
|
+
headers.set(UPSTREAM_TIMING_HEADER, value);
|
|
30228
|
+
return new Response(response.body, {
|
|
30229
|
+
status: response.status,
|
|
30230
|
+
statusText: response.statusText,
|
|
30231
|
+
headers
|
|
30232
|
+
});
|
|
30233
|
+
}
|
|
30234
|
+
}
|
|
30235
|
+
|
|
30201
30236
|
// src/runtime.ts
|
|
30202
30237
|
init_client_forwarding();
|
|
30203
30238
|
init_DurableThread();
|
|
@@ -30451,6 +30486,9 @@ var UserStateImpl = class {
|
|
|
30451
30486
|
host;
|
|
30452
30487
|
/** Caller descriptor for grant `allow` policy checks. */
|
|
30453
30488
|
caller;
|
|
30489
|
+
/** One identity lookup per execution: the singleton RPC runs at most once. */
|
|
30490
|
+
identityLookup = null;
|
|
30491
|
+
identityLookupMs = null;
|
|
30454
30492
|
constructor(userId, host, caller) {
|
|
30455
30493
|
this.userId = userId;
|
|
30456
30494
|
this.host = host;
|
|
@@ -30462,6 +30500,22 @@ var UserStateImpl = class {
|
|
|
30462
30500
|
return ns.get(ns.idFromName("singleton"));
|
|
30463
30501
|
}
|
|
30464
30502
|
async identity() {
|
|
30503
|
+
if (!this.identityLookup) {
|
|
30504
|
+
const started = Date.now();
|
|
30505
|
+
this.identityLookup = this.lookupIdentity().finally(() => {
|
|
30506
|
+
this.identityLookupMs = Date.now() - started;
|
|
30507
|
+
});
|
|
30508
|
+
this.identityLookup.catch(() => {
|
|
30509
|
+
this.identityLookup = null;
|
|
30510
|
+
});
|
|
30511
|
+
}
|
|
30512
|
+
return this.identityLookup;
|
|
30513
|
+
}
|
|
30514
|
+
/** Milliseconds the execution spent resolving identity, once it has. */
|
|
30515
|
+
timing() {
|
|
30516
|
+
return { identity_ms: this.identityLookupMs };
|
|
30517
|
+
}
|
|
30518
|
+
async lookupIdentity() {
|
|
30465
30519
|
const user = await this.singleton().getUserById(this.userId);
|
|
30466
30520
|
if (!user) throw new Error(`Unknown user: ${this.userId}`);
|
|
30467
30521
|
return {
|
|
@@ -30493,6 +30547,12 @@ var UserStateImpl = class {
|
|
|
30493
30547
|
emit(event, data) {
|
|
30494
30548
|
this.host.broadcastUserEvent(this.userId, event, data);
|
|
30495
30549
|
}
|
|
30550
|
+
async clients() {
|
|
30551
|
+
return this.host.listRoomClients(this.userId);
|
|
30552
|
+
}
|
|
30553
|
+
async send(clientId, frame) {
|
|
30554
|
+
return this.host.sendToRoomClient(this.userId, clientId, frame);
|
|
30555
|
+
}
|
|
30496
30556
|
async listThreads(options) {
|
|
30497
30557
|
const { threads, total } = await this.singleton().listThreads({
|
|
30498
30558
|
user_id: this.userId,
|
|
@@ -30560,6 +30620,72 @@ function createExecutionChain() {
|
|
|
30560
30620
|
};
|
|
30561
30621
|
}
|
|
30562
30622
|
|
|
30623
|
+
// src/durable-objects/DurableUserHub.ts
|
|
30624
|
+
init_bridge_owner();
|
|
30625
|
+
|
|
30626
|
+
// src/users/room.ts
|
|
30627
|
+
var ROOM_FRAME_MAX_BYTES = 1024 * 1024;
|
|
30628
|
+
var ROOM_FRAME_RATE_PER_SECOND = 200;
|
|
30629
|
+
var ROOM_FRAME_BURST = 400;
|
|
30630
|
+
var ROOM_SEND_BUDGET_BYTES = 8 * 1024 * 1024;
|
|
30631
|
+
var ROOM_SEND_BUDGET_WINDOW_MS = 1e4;
|
|
30632
|
+
var ROOM_PRESENCE_GRACE_MS = 3e4;
|
|
30633
|
+
var ROOM_CLOSE_SUPERSEDED = 4001;
|
|
30634
|
+
var ROOM_CLOSE_TOO_LARGE = 1009;
|
|
30635
|
+
var ROOM_CLOSE_TRY_AGAIN = 1013;
|
|
30636
|
+
function parseRoomFrame(message) {
|
|
30637
|
+
const size = typeof message === "string" ? message.length : message.byteLength;
|
|
30638
|
+
if (size > ROOM_FRAME_MAX_BYTES) return "too_large";
|
|
30639
|
+
if (typeof message !== "string") return null;
|
|
30640
|
+
let parsed;
|
|
30641
|
+
try {
|
|
30642
|
+
parsed = JSON.parse(message);
|
|
30643
|
+
} catch {
|
|
30644
|
+
return null;
|
|
30645
|
+
}
|
|
30646
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
30647
|
+
const frame = parsed;
|
|
30648
|
+
if (frame.type !== "room" || typeof frame.id !== "string" || frame.id.length === 0 || frame.id.length > 256) {
|
|
30649
|
+
return null;
|
|
30650
|
+
}
|
|
30651
|
+
return frame;
|
|
30652
|
+
}
|
|
30653
|
+
var RoomRateLimiter = class {
|
|
30654
|
+
constructor(ratePerSecond = ROOM_FRAME_RATE_PER_SECOND, burst = ROOM_FRAME_BURST, now = Date.now()) {
|
|
30655
|
+
this.ratePerSecond = ratePerSecond;
|
|
30656
|
+
this.burst = burst;
|
|
30657
|
+
this.tokens = burst;
|
|
30658
|
+
this.last = now;
|
|
30659
|
+
}
|
|
30660
|
+
tokens;
|
|
30661
|
+
last;
|
|
30662
|
+
take(now = Date.now()) {
|
|
30663
|
+
const elapsed = Math.max(0, now - this.last) / 1e3;
|
|
30664
|
+
this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerSecond);
|
|
30665
|
+
this.last = now;
|
|
30666
|
+
if (this.tokens < 1) return false;
|
|
30667
|
+
this.tokens -= 1;
|
|
30668
|
+
return true;
|
|
30669
|
+
}
|
|
30670
|
+
};
|
|
30671
|
+
var RoomSendBudget = class {
|
|
30672
|
+
constructor(budgetBytes = ROOM_SEND_BUDGET_BYTES, windowMs = ROOM_SEND_BUDGET_WINDOW_MS, now = Date.now()) {
|
|
30673
|
+
this.budgetBytes = budgetBytes;
|
|
30674
|
+
this.windowMs = windowMs;
|
|
30675
|
+
this.windowStart = now;
|
|
30676
|
+
}
|
|
30677
|
+
spent = 0;
|
|
30678
|
+
windowStart;
|
|
30679
|
+
charge(bytes, now = Date.now()) {
|
|
30680
|
+
if (now - this.windowStart >= this.windowMs) {
|
|
30681
|
+
this.windowStart = now;
|
|
30682
|
+
this.spent = 0;
|
|
30683
|
+
}
|
|
30684
|
+
this.spent += bytes;
|
|
30685
|
+
return this.spent <= this.budgetBytes;
|
|
30686
|
+
}
|
|
30687
|
+
};
|
|
30688
|
+
|
|
30563
30689
|
// src/users/grants.ts
|
|
30564
30690
|
var INSTANCE_GRANTS_MAX_BYTES = 64 * 1024;
|
|
30565
30691
|
var INSTANCE_GRANTS_MAX_COUNT = 64;
|
|
@@ -30715,6 +30841,12 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30715
30841
|
clientSockets = /* @__PURE__ */ new Set();
|
|
30716
30842
|
clientInfo = /* @__PURE__ */ new Map();
|
|
30717
30843
|
migrated = false;
|
|
30844
|
+
/** Inbound frame rate and outbound byte budget per room socket. */
|
|
30845
|
+
roomLimits = /* @__PURE__ */ new Map();
|
|
30846
|
+
/** Detach notices held for the presence grace window, by client id. */
|
|
30847
|
+
pendingDetach = /* @__PURE__ */ new Map();
|
|
30848
|
+
/** The project's room definition, loaded once per instance. */
|
|
30849
|
+
roomDefinition = null;
|
|
30718
30850
|
constructor(ctx, env) {
|
|
30719
30851
|
super(ctx, env);
|
|
30720
30852
|
ctx.blockConcurrencyWhile(async () => {
|
|
@@ -30727,7 +30859,8 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30727
30859
|
this.clientInfo.set(ws, {
|
|
30728
30860
|
clientId: typeof attachment.clientId === "string" ? attachment.clientId : null,
|
|
30729
30861
|
clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
|
|
30730
|
-
clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null
|
|
30862
|
+
clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null,
|
|
30863
|
+
since: typeof attachment.since === "number" ? attachment.since : void 0
|
|
30731
30864
|
});
|
|
30732
30865
|
}
|
|
30733
30866
|
}
|
|
@@ -30800,24 +30933,188 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30800
30933
|
if (request.headers.get("Upgrade") === "websocket") {
|
|
30801
30934
|
const pair = new WebSocketPair();
|
|
30802
30935
|
const [client, server] = [pair[0], pair[1]];
|
|
30803
|
-
const info = {
|
|
30804
|
-
clientId: url.searchParams.get("client_id"),
|
|
30805
|
-
clientName: url.searchParams.get("client_name"),
|
|
30806
|
-
clientKind: url.searchParams.get("client_kind")
|
|
30807
|
-
};
|
|
30936
|
+
const info = { ...parseBridgeClientParams(url).info, since: Date.now() };
|
|
30808
30937
|
this.ensureHeartbeatAutoResponse();
|
|
30809
30938
|
this.ctx.acceptWebSocket(server);
|
|
30810
30939
|
server.serializeAttachment({ type: "hub_client", ...info });
|
|
30940
|
+
const reconnecting = info.clientId !== null && this.supersedeClient(info.clientId, server);
|
|
30811
30941
|
this.clientSockets.add(server);
|
|
30812
30942
|
this.clientInfo.set(server, info);
|
|
30813
30943
|
try {
|
|
30814
30944
|
server.send(JSON.stringify({ type: "hub_ready" }));
|
|
30815
30945
|
} catch {
|
|
30816
30946
|
}
|
|
30947
|
+
if (info.clientId) {
|
|
30948
|
+
const connection = this.connectionOf(info);
|
|
30949
|
+
if (connection) this.observeAttach(connection, reconnecting);
|
|
30950
|
+
}
|
|
30817
30951
|
return new Response(null, { status: 101, webSocket: client });
|
|
30818
30952
|
}
|
|
30819
30953
|
return new Response("Not found", { status: 404 });
|
|
30820
30954
|
}
|
|
30955
|
+
// ─── Room connections (spec: Users rooms) ───────────────────────────────────
|
|
30956
|
+
connectionOf(info) {
|
|
30957
|
+
if (!info?.clientId) return null;
|
|
30958
|
+
return { clientId: info.clientId, clientKind: info.clientKind, since: info.since ?? 0 };
|
|
30959
|
+
}
|
|
30960
|
+
/** Close every other socket presenting `clientId`; true when one existed. */
|
|
30961
|
+
supersedeClient(clientId, keep) {
|
|
30962
|
+
let superseded = false;
|
|
30963
|
+
for (const ws of Array.from(this.clientSockets)) {
|
|
30964
|
+
if (ws === keep || this.clientInfo.get(ws)?.clientId !== clientId) continue;
|
|
30965
|
+
this.forgetSocket(ws);
|
|
30966
|
+
superseded = true;
|
|
30967
|
+
try {
|
|
30968
|
+
ws.send(JSON.stringify({ type: "superseded" }));
|
|
30969
|
+
} catch {
|
|
30970
|
+
}
|
|
30971
|
+
try {
|
|
30972
|
+
ws.close(ROOM_CLOSE_SUPERSEDED, "Superseded by a newer connection from this client");
|
|
30973
|
+
} catch {
|
|
30974
|
+
}
|
|
30975
|
+
}
|
|
30976
|
+
return superseded;
|
|
30977
|
+
}
|
|
30978
|
+
forgetSocket(ws) {
|
|
30979
|
+
this.clientSockets.delete(ws);
|
|
30980
|
+
this.clientInfo.delete(ws);
|
|
30981
|
+
this.roomLimits.delete(ws);
|
|
30982
|
+
}
|
|
30983
|
+
liveSocketFor(clientId) {
|
|
30984
|
+
for (const ws of this.clientSockets) {
|
|
30985
|
+
if (this.clientInfo.get(ws)?.clientId === clientId && ws.readyState === WebSocket.OPEN) return ws;
|
|
30986
|
+
}
|
|
30987
|
+
return null;
|
|
30988
|
+
}
|
|
30989
|
+
/** Live room connections, one per client id, oldest attachment first. */
|
|
30990
|
+
listRoomClients() {
|
|
30991
|
+
const byId = /* @__PURE__ */ new Map();
|
|
30992
|
+
for (const ws of this.clientSockets) {
|
|
30993
|
+
if (ws.readyState !== WebSocket.OPEN) continue;
|
|
30994
|
+
const connection = this.connectionOf(this.clientInfo.get(ws));
|
|
30995
|
+
if (connection) byId.set(connection.clientId, connection);
|
|
30996
|
+
}
|
|
30997
|
+
return Array.from(byId.values()).sort((a, b) => a.since - b.since);
|
|
30998
|
+
}
|
|
30999
|
+
/** Deliver one frame to the socket carrying `clientId`, within its send budget. */
|
|
31000
|
+
sendToRoomClient(clientId, frame) {
|
|
31001
|
+
const ws = this.liveSocketFor(clientId);
|
|
31002
|
+
if (!ws) return false;
|
|
31003
|
+
return this.sendRoomFrame(ws, JSON.stringify(frame));
|
|
31004
|
+
}
|
|
31005
|
+
sendRoomFrame(ws, payload) {
|
|
31006
|
+
const limits = this.limitsFor(ws);
|
|
31007
|
+
if (!limits.send.charge(payload.length)) {
|
|
31008
|
+
this.forgetSocket(ws);
|
|
31009
|
+
try {
|
|
31010
|
+
ws.close(ROOM_CLOSE_TRY_AGAIN, "Slow consumer: send budget exceeded");
|
|
31011
|
+
} catch {
|
|
31012
|
+
}
|
|
31013
|
+
return false;
|
|
31014
|
+
}
|
|
31015
|
+
try {
|
|
31016
|
+
ws.send(payload);
|
|
31017
|
+
return true;
|
|
31018
|
+
} catch {
|
|
31019
|
+
this.forgetSocket(ws);
|
|
31020
|
+
return false;
|
|
31021
|
+
}
|
|
31022
|
+
}
|
|
31023
|
+
limitsFor(ws) {
|
|
31024
|
+
let limits = this.roomLimits.get(ws);
|
|
31025
|
+
if (!limits) {
|
|
31026
|
+
limits = { rate: new RoomRateLimiter(), send: new RoomSendBudget() };
|
|
31027
|
+
this.roomLimits.set(ws, limits);
|
|
31028
|
+
}
|
|
31029
|
+
return limits;
|
|
31030
|
+
}
|
|
31031
|
+
/** Load the project's room definition from the users registry, once. */
|
|
31032
|
+
async loadRoom() {
|
|
31033
|
+
if (!this.roomDefinition) {
|
|
31034
|
+
this.roomDefinition = (async () => {
|
|
31035
|
+
const registry = this.userEndpoints();
|
|
31036
|
+
for (const key of Object.keys(registry)) {
|
|
31037
|
+
if (!/(^|\/)room$/.test(key)) continue;
|
|
31038
|
+
try {
|
|
31039
|
+
const loaded = await registry[key]();
|
|
31040
|
+
const candidate = loaded.default ?? loaded;
|
|
31041
|
+
if (isUserRoom(candidate)) return candidate;
|
|
31042
|
+
} catch {
|
|
31043
|
+
}
|
|
31044
|
+
}
|
|
31045
|
+
return null;
|
|
31046
|
+
})();
|
|
31047
|
+
}
|
|
31048
|
+
return this.roomDefinition;
|
|
31049
|
+
}
|
|
31050
|
+
/** Whether `key` is room-owned state, exempt from `user_value_changed`. */
|
|
31051
|
+
async roomOwnsKey(key) {
|
|
31052
|
+
const room = await this.loadRoom();
|
|
31053
|
+
return Boolean(room?.valuePrefix && key.startsWith(room.valuePrefix));
|
|
31054
|
+
}
|
|
31055
|
+
/**
|
|
31056
|
+
* Presence runs inside the serialization chain, after any execution the
|
|
31057
|
+
* event raced with, so a room's presence write never interleaves with a
|
|
31058
|
+
* mutation. A reconnect inside the grace window is not a change.
|
|
31059
|
+
*/
|
|
31060
|
+
observeAttach(connection, reconnecting) {
|
|
31061
|
+
const pending = this.pendingDetach.get(connection.clientId);
|
|
31062
|
+
if (pending) {
|
|
31063
|
+
clearTimeout(pending);
|
|
31064
|
+
this.pendingDetach.delete(connection.clientId);
|
|
31065
|
+
return;
|
|
31066
|
+
}
|
|
31067
|
+
if (reconnecting) return;
|
|
31068
|
+
this.dispatchRoomPresence({ kind: "attached", connection, at: Date.now() });
|
|
31069
|
+
}
|
|
31070
|
+
observeDetach(connection) {
|
|
31071
|
+
if (this.liveSocketFor(connection.clientId)) return;
|
|
31072
|
+
const existing = this.pendingDetach.get(connection.clientId);
|
|
31073
|
+
if (existing) clearTimeout(existing);
|
|
31074
|
+
this.pendingDetach.set(
|
|
31075
|
+
connection.clientId,
|
|
31076
|
+
setTimeout(() => {
|
|
31077
|
+
this.pendingDetach.delete(connection.clientId);
|
|
31078
|
+
if (this.liveSocketFor(connection.clientId)) return;
|
|
31079
|
+
this.dispatchRoomPresence({ kind: "detached", connection, at: Date.now() });
|
|
31080
|
+
}, ROOM_PRESENCE_GRACE_MS)
|
|
31081
|
+
);
|
|
31082
|
+
}
|
|
31083
|
+
dispatchRoomPresence(event) {
|
|
31084
|
+
const run = this.executionChain.run(async () => {
|
|
31085
|
+
const room = await this.loadRoom();
|
|
31086
|
+
if (!room?.onPresence) return;
|
|
31087
|
+
const user = new UserStateImpl(this.roomUserId(), this.scopeHost(), { kind: "userRoom", name: "presence" });
|
|
31088
|
+
try {
|
|
31089
|
+
await room.onPresence(user, event);
|
|
31090
|
+
} catch (error) {
|
|
31091
|
+
console.error("[DurableUserHub] room presence handler failed:", error);
|
|
31092
|
+
}
|
|
31093
|
+
});
|
|
31094
|
+
this.ctx.waitUntil(run);
|
|
31095
|
+
}
|
|
31096
|
+
/** The hub is one object per user; its name is the user id. */
|
|
31097
|
+
roomUserId() {
|
|
31098
|
+
return this.ctx.id.name ?? this.ctx.id.toString();
|
|
31099
|
+
}
|
|
31100
|
+
async dispatchRoomFrame(ws, frame) {
|
|
31101
|
+
const connection = this.connectionOf(this.clientInfo.get(ws));
|
|
31102
|
+
if (!connection) return;
|
|
31103
|
+
await this.executionChain.run(async () => {
|
|
31104
|
+
const room = await this.loadRoom();
|
|
31105
|
+
if (!room) {
|
|
31106
|
+
this.sendRoomFrame(ws, JSON.stringify({ type: "room", id: frame.id, error: "no_room" }));
|
|
31107
|
+
return;
|
|
31108
|
+
}
|
|
31109
|
+
const user = new UserStateImpl(this.roomUserId(), this.scopeHost(), { kind: "userRoom", name: "frame" });
|
|
31110
|
+
try {
|
|
31111
|
+
await room.onFrame(user, connection, frame);
|
|
31112
|
+
} catch (error) {
|
|
31113
|
+
console.error("[DurableUserHub] room frame handler failed:", error);
|
|
31114
|
+
this.sendRoomFrame(ws, JSON.stringify({ type: "room", id: frame.id, error: "handler_failed" }));
|
|
31115
|
+
}
|
|
31116
|
+
});
|
|
31117
|
+
}
|
|
30821
31118
|
// ─── User scope: event stream (spec: Users §4) ─────────────────────────────
|
|
30822
31119
|
/** Idle sockets answer heartbeats at the edge without waking the object.
|
|
30823
31120
|
* Registered at accept time rather than in the constructor so a hub that
|
|
@@ -30855,8 +31152,9 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30855
31152
|
return new Response(null, { status: 101, webSocket: client });
|
|
30856
31153
|
}
|
|
30857
31154
|
/**
|
|
30858
|
-
* Deliver an event frame to every socket attached to this user's stream
|
|
30859
|
-
* At-most-once, live-only (spec: Users §4) —
|
|
31155
|
+
* Deliver an event frame to every socket attached to this user's stream
|
|
31156
|
+
* and to every room socket. At-most-once, live-only (spec: Users §4) —
|
|
31157
|
+
* no history, no acks.
|
|
30860
31158
|
*/
|
|
30861
31159
|
broadcastUserEvent(userId, event, data) {
|
|
30862
31160
|
const frame = JSON.stringify({ event, data });
|
|
@@ -30868,6 +31166,10 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30868
31166
|
} catch {
|
|
30869
31167
|
}
|
|
30870
31168
|
}
|
|
31169
|
+
for (const ws of Array.from(this.clientSockets)) {
|
|
31170
|
+
if (ws.readyState !== WebSocket.OPEN || !this.clientInfo.get(ws)?.clientId) continue;
|
|
31171
|
+
this.sendRoomFrame(ws, frame);
|
|
31172
|
+
}
|
|
30871
31173
|
}
|
|
30872
31174
|
// ─── User scope: serialized endpoint execution (spec: Users §2.1, §3) ─────
|
|
30873
31175
|
/** Per-user execution chain (spec: Users §2.1) — see users/serialize.ts. */
|
|
@@ -30878,6 +31180,8 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30878
31180
|
setUserValue: (userId, key, value) => this.setUserValue(userId, key, value),
|
|
30879
31181
|
listUserValues: (userId, options) => this.listUserValues(userId, options ?? {}),
|
|
30880
31182
|
broadcastUserEvent: (userId, event, data) => this.broadcastUserEvent(userId, event, data),
|
|
31183
|
+
listRoomClients: () => this.listRoomClients(),
|
|
31184
|
+
sendToRoomClient: (_userId, clientId, frame) => this.sendToRoomClient(clientId, frame),
|
|
30881
31185
|
grantFetch: (userId, grant, req) => this.grantFetch(userId, grant, req),
|
|
30882
31186
|
env: this.env
|
|
30883
31187
|
};
|
|
@@ -30897,19 +31201,28 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30897
31201
|
}
|
|
30898
31202
|
const module = loaded;
|
|
30899
31203
|
const handler = module.default ?? loaded;
|
|
31204
|
+
if (isUserRoom(handler)) return Response.json({ error: "Not found" }, { status: 404 });
|
|
30900
31205
|
if (!isUserEndpoint(handler)) {
|
|
30901
31206
|
return Response.json(
|
|
30902
31207
|
{ error: `${match.key} is not a defineUserEndpoint export` },
|
|
30903
31208
|
{ status: 500 }
|
|
30904
31209
|
);
|
|
30905
31210
|
}
|
|
31211
|
+
const queuedAt = Date.now();
|
|
30906
31212
|
return this.executionChain.run(async () => {
|
|
31213
|
+
const queueMs = Date.now() - queuedAt;
|
|
31214
|
+
const timed = withUpstreamTiming(
|
|
31215
|
+
request,
|
|
31216
|
+
appendUpstreamTiming(request.headers.get(UPSTREAM_TIMING_HEADER), "queue", queueMs)
|
|
31217
|
+
);
|
|
30907
31218
|
const user = new UserStateImpl(userId, this.scopeHost(), {
|
|
30908
31219
|
kind: "userEndpoint",
|
|
30909
31220
|
name: match.key
|
|
30910
31221
|
});
|
|
30911
31222
|
try {
|
|
30912
|
-
|
|
31223
|
+
const response = await handler.__handler(timed, user, match.params);
|
|
31224
|
+
const identityMs = user.timing().identity_ms;
|
|
31225
|
+
return identityMs === null ? response : responseWithUpstreamTiming(response, "identity", identityMs);
|
|
30913
31226
|
} catch (error) {
|
|
30914
31227
|
return Response.json(
|
|
30915
31228
|
{ error: error instanceof Error ? error.message : "User endpoint failed" },
|
|
@@ -30932,6 +31245,7 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30932
31245
|
const names = Object.keys(registry);
|
|
30933
31246
|
if (names.length === 0) return;
|
|
30934
31247
|
const run = async () => {
|
|
31248
|
+
if (hookType === "user_value_changed" && await this.roomOwnsKey(String(args[0]))) return;
|
|
30935
31249
|
const user = new UserStateImpl(userId, this.scopeHost(), {
|
|
30936
31250
|
kind: "userHook",
|
|
30937
31251
|
name: hookType
|
|
@@ -30956,6 +31270,9 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30956
31270
|
this.ctx.waitUntil(run());
|
|
30957
31271
|
}
|
|
30958
31272
|
async webSocketClose(ws) {
|
|
31273
|
+
const connection = this.connectionOf(this.clientInfo.get(ws));
|
|
31274
|
+
this.forgetSocket(ws);
|
|
31275
|
+
if (connection) this.observeDetach(connection);
|
|
30959
31276
|
const attachment = ws.deserializeAttachment();
|
|
30960
31277
|
if (attachment?.type === "user_stream") {
|
|
30961
31278
|
this.dispatchUserHooks(String(attachment.userId), "user_client_detached", [
|
|
@@ -30964,10 +31281,7 @@ var DurableUserHub = class extends DurableObject {
|
|
|
30964
31281
|
kind: typeof attachment.clientKind === "string" ? attachment.clientKind : void 0
|
|
30965
31282
|
}
|
|
30966
31283
|
]);
|
|
30967
|
-
return;
|
|
30968
31284
|
}
|
|
30969
|
-
this.clientSockets.delete(ws);
|
|
30970
|
-
this.clientInfo.delete(ws);
|
|
30971
31285
|
}
|
|
30972
31286
|
// ─── User scope: credential grants (spec: Users §6) ───────────────────────
|
|
30973
31287
|
/**
|
|
@@ -31087,8 +31401,37 @@ var DurableUserHub = class extends DurableObject {
|
|
|
31087
31401
|
caller: body.caller ?? { kind: "unknown", name: "unknown" }
|
|
31088
31402
|
});
|
|
31089
31403
|
}
|
|
31090
|
-
async webSocketMessage(ws,
|
|
31091
|
-
|
|
31404
|
+
async webSocketMessage(ws, message) {
|
|
31405
|
+
const attachment = ws.deserializeAttachment();
|
|
31406
|
+
if (attachment?.type !== "hub_client") return;
|
|
31407
|
+
if (!this.clientSockets.has(ws)) {
|
|
31408
|
+
this.clientSockets.add(ws);
|
|
31409
|
+
this.clientInfo.set(ws, {
|
|
31410
|
+
clientId: typeof attachment.clientId === "string" ? attachment.clientId : null,
|
|
31411
|
+
clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
|
|
31412
|
+
clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null,
|
|
31413
|
+
since: typeof attachment.since === "number" ? attachment.since : void 0
|
|
31414
|
+
});
|
|
31415
|
+
}
|
|
31416
|
+
if (!this.limitsFor(ws).rate.take()) {
|
|
31417
|
+
this.forgetSocket(ws);
|
|
31418
|
+
try {
|
|
31419
|
+
ws.close(ROOM_CLOSE_TRY_AGAIN, "Frame rate exceeded");
|
|
31420
|
+
} catch {
|
|
31421
|
+
}
|
|
31422
|
+
return;
|
|
31423
|
+
}
|
|
31424
|
+
const frame = parseRoomFrame(message);
|
|
31425
|
+
if (frame === "too_large") {
|
|
31426
|
+
this.forgetSocket(ws);
|
|
31427
|
+
try {
|
|
31428
|
+
ws.close(ROOM_CLOSE_TOO_LARGE, "Frame exceeds 1 MiB");
|
|
31429
|
+
} catch {
|
|
31430
|
+
}
|
|
31431
|
+
return;
|
|
31432
|
+
}
|
|
31433
|
+
if (frame === null || !this.clientInfo.get(ws)?.clientId) return;
|
|
31434
|
+
await this.dispatchRoomFrame(ws, frame);
|
|
31092
31435
|
}
|
|
31093
31436
|
async webSocketError(ws) {
|
|
31094
31437
|
await this.webSocketClose(ws);
|
|
@@ -31518,6 +31861,6 @@ init_ProviderRegistry();
|
|
|
31518
31861
|
init_types2();
|
|
31519
31862
|
init_resolver();
|
|
31520
31863
|
|
|
31521
|
-
export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, applyRuntimeCors, authenticate, buildImageDescription, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isSameOriginBrowserRequest, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, runtimeCorsHeaders, runtimePreflightResponse, stat, supportsClientForwarding, tail, unlink, updateThread, writeFile, writeImage };
|
|
31864
|
+
export { CLIENT_STREAM_CHUNK_BYTES, CodeExecutionBridge, DurableAgentBuilder, DurableThread, DurableUserHub, FlowStateSdk, NamespaceResolutionError, ProviderRegistry, ThreadWatchdog, applyRuntimeCors, authenticate, buildImageDescription, callClient, callClientStream, canAccessThread, cat, createNamespaceContext, createThreadEndpointHandler, decodeStreamChunk, defineController2 as defineController, emitThreadEvent, encodeStreamChunk, endpointCallerFromAuth, enhanceFlowState, exists, find, forceTurn, formatUpstreamTiming, generateImageDescription, getFileStats, getMessages, getMessagesToSummarize, getShortName, getThumbnail, getUnsummarizedImageAttachments, getVisibleAgentNames, getVisibleModelNames, getVisiblePromptNames, getVisibleToolNames, grep, hasImageAttachments, head, injectMessage, isAdminOnlyApiRoute, isHostedInstance, isPublicApiRoute, isQualifiedName, isSameOriginBrowserRequest, isTrustedLocalDevelopmentRequest, linkFile, mkdir, move, optimizeImageContext, parseQualifiedName, qualifyName, queueTool, readFile, readdir, reloadHistory, rename, replaceImagesWithDescriptions, requireAdmin, requireAuth, resolveAgent, resolveHook, resolveModel, resolvePrompt, resolveTool, rmdir, runtimeCorsHeaders, runtimePreflightResponse, stat, supportsClientForwarding, tail, unlink, updateThread, withUpstreamTiming, writeFile, writeImage };
|
|
31522
31865
|
//# sourceMappingURL=runtime.js.map
|
|
31523
31866
|
//# sourceMappingURL=runtime.js.map
|