@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/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) — no history, no acks.
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
- return await handler.__handler(request, user, match.params);
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, _message) {
31091
- if (!this.clientSockets.has(ws)) this.clientSockets.add(ws);
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