@standardagents/builder 0.41.6 → 0.41.8

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
@@ -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 {
@@ -30560,7 +30614,109 @@ function createExecutionChain() {
30560
30614
  };
30561
30615
  }
30562
30616
 
30617
+ // src/durable-objects/DurableUserHub.ts
30618
+ init_bridge_owner();
30619
+
30563
30620
  // src/users/grants.ts
30621
+ var INSTANCE_GRANTS_MAX_BYTES = 64 * 1024;
30622
+ var INSTANCE_GRANTS_MAX_COUNT = 64;
30623
+ var GRANT_NAME = /^[a-z0-9][a-z0-9._-]{0,127}$/;
30624
+ var HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
30625
+ var QUERY_NAME = /^[A-Za-z0-9._~-]{1,128}$/;
30626
+ var SUPPORTED_KINDS = /* @__PURE__ */ new Set(["bearer", "header", "query", "basic"]);
30627
+ var RECORD_KEYS = /* @__PURE__ */ new Set(["name", "kind", "origins", "inject", "allow", "metadata", "secret"]);
30628
+ var ALLOW_KEYS = /* @__PURE__ */ new Set(["tools", "endpoints", "userEndpoints", "agents"]);
30629
+ var INJECT_KEYS = /* @__PURE__ */ new Set(["header", "format", "query"]);
30630
+ function parseInstanceCredentialGrants(value) {
30631
+ const records = /* @__PURE__ */ new Map();
30632
+ if (typeof value !== "string" || value.length === 0 || value.length > INSTANCE_GRANTS_MAX_BYTES) {
30633
+ return records;
30634
+ }
30635
+ let parsed;
30636
+ try {
30637
+ parsed = JSON.parse(value);
30638
+ } catch {
30639
+ return records;
30640
+ }
30641
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return records;
30642
+ const envelope = parsed;
30643
+ if (envelope.version !== 1 || !Array.isArray(envelope.grants) || envelope.grants.length > INSTANCE_GRANTS_MAX_COUNT) {
30644
+ return records;
30645
+ }
30646
+ for (const candidate of envelope.grants) {
30647
+ const record = validateCredentialGrantRecord(candidate);
30648
+ if (!record || records.has(record.grant.name)) return /* @__PURE__ */ new Map();
30649
+ records.set(record.grant.name, record);
30650
+ }
30651
+ return records;
30652
+ }
30653
+ function validateCredentialGrantRecord(value, routeName) {
30654
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
30655
+ const input = value;
30656
+ if (Object.keys(input).some((key) => !RECORD_KEYS.has(key))) return null;
30657
+ const name = input.name;
30658
+ if (typeof name !== "string" || !GRANT_NAME.test(name)) return null;
30659
+ if (input.name !== void 0 && input.name !== name) return null;
30660
+ if (typeof input.secret !== "string" || input.secret.length === 0 || input.secret.length > 16 * 1024) return null;
30661
+ if (typeof input.kind !== "string" || !SUPPORTED_KINDS.has(input.kind)) return null;
30662
+ if ((input.kind === "bearer" || input.kind === "header") && /[\r\n]/.test(input.secret)) return null;
30663
+ if (!Array.isArray(input.origins) || input.origins.length === 0 || input.origins.length > 64) return null;
30664
+ const origins = [];
30665
+ for (const origin of input.origins) {
30666
+ if (typeof origin !== "string" || origin.length > 512) return null;
30667
+ if (origin.startsWith("*.")) {
30668
+ if (!/^\*\.[a-z0-9.-]+$/i.test(origin)) return null;
30669
+ } else {
30670
+ try {
30671
+ const url = new URL(origin);
30672
+ if (url.origin !== origin || url.protocol !== "https:" || url.username || url.password) return null;
30673
+ } catch {
30674
+ return null;
30675
+ }
30676
+ }
30677
+ origins.push(origin);
30678
+ }
30679
+ if (!input.allow || typeof input.allow !== "object" || Array.isArray(input.allow)) return null;
30680
+ const allowInput = input.allow;
30681
+ if (Object.keys(allowInput).some((key) => !ALLOW_KEYS.has(key))) return null;
30682
+ const allow = {};
30683
+ for (const key of ["tools", "endpoints", "userEndpoints", "agents"]) {
30684
+ const list = allowInput[key];
30685
+ if (list === void 0) continue;
30686
+ if (!Array.isArray(list) || list.length > 128 || !list.every((item) => typeof item === "string" && item.length > 0 && item.length <= 256)) {
30687
+ return null;
30688
+ }
30689
+ allow[key] = [...list];
30690
+ }
30691
+ if (Object.keys(allow).length === 0) return null;
30692
+ const inject = input.inject;
30693
+ if (inject !== void 0 && (!inject || typeof inject !== "object" || Array.isArray(inject))) return null;
30694
+ if (inject !== void 0) {
30695
+ const injection = inject;
30696
+ if (Object.keys(injection).some((key) => !INJECT_KEYS.has(key))) return null;
30697
+ if (injection.header !== void 0 && (typeof injection.header !== "string" || !HEADER_NAME.test(injection.header))) return null;
30698
+ if (injection.format !== void 0 && (typeof injection.format !== "string" || injection.format.length > 1024 || /[\r\n]/.test(injection.format))) return null;
30699
+ if (injection.query !== void 0 && (typeof injection.query !== "string" || !QUERY_NAME.test(injection.query))) return null;
30700
+ }
30701
+ const metadata = input.metadata;
30702
+ if (metadata !== void 0 && (!metadata || typeof metadata !== "object" || Array.isArray(metadata))) return null;
30703
+ if (metadata !== void 0) {
30704
+ try {
30705
+ if (JSON.stringify(metadata).length > 16 * 1024) return null;
30706
+ } catch {
30707
+ return null;
30708
+ }
30709
+ }
30710
+ const grant = {
30711
+ name,
30712
+ kind: input.kind,
30713
+ origins,
30714
+ allow,
30715
+ ...inject === void 0 ? {} : { inject },
30716
+ ...metadata === void 0 ? {} : { metadata }
30717
+ };
30718
+ return { grant, secret: input.secret };
30719
+ }
30564
30720
  function originAllowed(origins, target) {
30565
30721
  if (!Array.isArray(origins)) return false;
30566
30722
  for (const allowed of origins) {
@@ -30701,11 +30857,7 @@ var DurableUserHub = class extends DurableObject {
30701
30857
  if (request.headers.get("Upgrade") === "websocket") {
30702
30858
  const pair = new WebSocketPair();
30703
30859
  const [client, server] = [pair[0], pair[1]];
30704
- const info = {
30705
- clientId: url.searchParams.get("client_id"),
30706
- clientName: url.searchParams.get("client_name"),
30707
- clientKind: url.searchParams.get("client_kind")
30708
- };
30860
+ const info = parseBridgeClientParams(url).info;
30709
30861
  this.ensureHeartbeatAutoResponse();
30710
30862
  this.ctx.acceptWebSocket(server);
30711
30863
  server.serializeAttachment({ type: "hub_client", ...info });
@@ -30804,13 +30956,21 @@ var DurableUserHub = class extends DurableObject {
30804
30956
  { status: 500 }
30805
30957
  );
30806
30958
  }
30959
+ const queuedAt = Date.now();
30807
30960
  return this.executionChain.run(async () => {
30961
+ const queueMs = Date.now() - queuedAt;
30962
+ const timed = withUpstreamTiming(
30963
+ request,
30964
+ appendUpstreamTiming(request.headers.get(UPSTREAM_TIMING_HEADER), "queue", queueMs)
30965
+ );
30808
30966
  const user = new UserStateImpl(userId, this.scopeHost(), {
30809
30967
  kind: "userEndpoint",
30810
30968
  name: match.key
30811
30969
  });
30812
30970
  try {
30813
- return await handler.__handler(request, user, match.params);
30971
+ const response = await handler.__handler(timed, user, match.params);
30972
+ const identityMs = user.timing().identity_ms;
30973
+ return identityMs === null ? response : responseWithUpstreamTiming(response, "identity", identityMs);
30814
30974
  } catch (error) {
30815
30975
  return Response.json(
30816
30976
  { error: error instanceof Error ? error.message : "User endpoint failed" },
@@ -30857,6 +31017,8 @@ var DurableUserHub = class extends DurableObject {
30857
31017
  this.ctx.waitUntil(run());
30858
31018
  }
30859
31019
  async webSocketClose(ws) {
31020
+ this.clientSockets.delete(ws);
31021
+ this.clientInfo.delete(ws);
30860
31022
  const attachment = ws.deserializeAttachment();
30861
31023
  if (attachment?.type === "user_stream") {
30862
31024
  this.dispatchUserHooks(String(attachment.userId), "user_client_detached", [
@@ -30865,10 +31027,7 @@ var DurableUserHub = class extends DurableObject {
30865
31027
  kind: typeof attachment.clientKind === "string" ? attachment.clientKind : void 0
30866
31028
  }
30867
31029
  ]);
30868
- return;
30869
31030
  }
30870
- this.clientSockets.delete(ws);
30871
- this.clientInfo.delete(ws);
30872
31031
  }
30873
31032
  // ─── User scope: credential grants (spec: Users §6) ───────────────────────
30874
31033
  /**
@@ -30906,14 +31065,20 @@ var DurableUserHub = class extends DurableObject {
30906
31065
  `SELECT name, config FROM user_grants WHERE user_id = ? ORDER BY name ASC`,
30907
31066
  userId
30908
31067
  );
30909
- return cursor.toArray().map((row) => {
31068
+ const grants = /* @__PURE__ */ new Map();
31069
+ for (const record of parseInstanceCredentialGrants(this.env.AGENT_BUILDER_CREDENTIAL_GRANTS).values()) {
31070
+ const { name, ...config } = record.grant;
31071
+ grants.set(name, { name, ...config });
31072
+ }
31073
+ for (const row of cursor.toArray()) {
30910
31074
  let config = {};
30911
31075
  try {
30912
31076
  config = JSON.parse(row.config);
30913
31077
  } catch {
30914
31078
  }
30915
- return { name: row.name, ...config };
30916
- });
31079
+ grants.set(row.name, { name: row.name, ...config });
31080
+ }
31081
+ return [...grants.values()].sort((a, b) => a.name.localeCompare(b.name));
30917
31082
  }
30918
31083
  /** Perform a privileged fetch with the grant's credential injected. */
30919
31084
  async grantFetch(userId, grantName, req) {
@@ -30923,15 +31088,24 @@ var DurableUserHub = class extends DurableObject {
30923
31088
  userId,
30924
31089
  grantName
30925
31090
  );
30926
- const row = cursor.toArray()[0];
30927
- if (!row) {
31091
+ const stored = cursor.toArray()[0];
31092
+ const instance = stored ? null : parseInstanceCredentialGrants(this.env.AGENT_BUILDER_CREDENTIAL_GRANTS).get(grantName) ?? null;
31093
+ if (!stored && !instance) {
30928
31094
  return Response.json({ error: "grant_not_found", grant: grantName }, { status: 404 });
30929
31095
  }
30930
31096
  let config;
30931
- try {
30932
- config = JSON.parse(row.config);
30933
- } catch {
30934
- return Response.json({ error: "grant_config_invalid" }, { status: 500 });
31097
+ let secret;
31098
+ if (stored) {
31099
+ try {
31100
+ config = JSON.parse(stored.config);
31101
+ } catch {
31102
+ return Response.json({ error: "grant_config_invalid" }, { status: 500 });
31103
+ }
31104
+ secret = stored.secret;
31105
+ } else {
31106
+ const { name: _name, ...instanceConfig } = instance.grant;
31107
+ config = instanceConfig;
31108
+ secret = instance.secret;
30935
31109
  }
30936
31110
  let target;
30937
31111
  try {
@@ -30946,7 +31120,7 @@ var DurableUserHub = class extends DurableObject {
30946
31120
  return Response.json({ error: "grant_caller_denied" }, { status: 403 });
30947
31121
  }
30948
31122
  const headers = new Headers(req.headers);
30949
- if (!injectGrantCredential(config, row.secret, target, headers)) {
31123
+ if (!injectGrantCredential(config, secret, target, headers)) {
30950
31124
  return Response.json({ error: "grant_kind_unsupported" }, { status: 501 });
30951
31125
  }
30952
31126
  try {
@@ -30974,7 +31148,16 @@ var DurableUserHub = class extends DurableObject {
30974
31148
  });
30975
31149
  }
30976
31150
  async webSocketMessage(ws, _message) {
30977
- if (!this.clientSockets.has(ws)) this.clientSockets.add(ws);
31151
+ const attachment = ws.deserializeAttachment();
31152
+ if (attachment?.type !== "hub_client") return;
31153
+ if (!this.clientSockets.has(ws)) {
31154
+ this.clientSockets.add(ws);
31155
+ this.clientInfo.set(ws, {
31156
+ clientId: typeof attachment.clientId === "string" ? attachment.clientId : null,
31157
+ clientName: typeof attachment.clientName === "string" ? attachment.clientName : null,
31158
+ clientKind: typeof attachment.clientKind === "string" ? attachment.clientKind : null
31159
+ });
31160
+ }
30978
31161
  }
30979
31162
  async webSocketError(ws) {
30980
31163
  await this.webSocketClose(ws);
@@ -31404,6 +31587,6 @@ init_ProviderRegistry();
31404
31587
  init_types2();
31405
31588
  init_resolver();
31406
31589
 
31407
- 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 };
31590
+ 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 };
31408
31591
  //# sourceMappingURL=runtime.js.map
31409
31592
  //# sourceMappingURL=runtime.js.map