@personaai/runtime 0.21.0 → 0.23.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/index.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CAPABILITY_SCOPES: () => CAPABILITY_SCOPES,
23
24
  RUNTIME_VERSION: () => RUNTIME_VERSION,
24
25
  RuntimeHttpError: () => RuntimeHttpError,
25
26
  createRuntime: () => createRuntime
@@ -74,6 +75,8 @@ function createClientForRequest(options, userId, logger) {
74
75
  return new import_sdk.PersonaClient({
75
76
  baseUrl: options.baseUrl,
76
77
  credential: options.credential,
78
+ // No end user (`null`) = a Project-scoped client: `/health` and the
79
+ // Project-wide admin capabilities.
77
80
  externalUserId: userId ?? void 0,
78
81
  fetch: options.fetch,
79
82
  logger: clientLogger
@@ -98,6 +101,36 @@ function mapErrorToRuntimeError(err, mode) {
98
101
  if (err instanceof RuntimeHttpError) {
99
102
  return { status: err.status, code: err.code, message: err.message };
100
103
  }
104
+ if (err instanceof import_sdk2.ForbiddenScopeError) {
105
+ return {
106
+ status: 500,
107
+ code: "RUNTIME_MISCONFIGURED",
108
+ message: dev ? `The runtime's Persona API key lacks the "${err.requiredScope ?? "unknown"}" scope: ${err.message}` : "The server's Persona API key can't do this.",
109
+ ...dev ? { detail: { requiredScope: err.requiredScope, acceptedScopes: err.acceptedScopes } } : {}
110
+ };
111
+ }
112
+ if (err instanceof import_sdk2.BudgetExceededError) {
113
+ return {
114
+ status: 402,
115
+ code: err.code,
116
+ message: err.message,
117
+ ...err.scope ? { details: { scope: err.scope } } : {},
118
+ ...dev ? { detail: err.response } : {}
119
+ };
120
+ }
121
+ if (err instanceof import_sdk2.EvalGateError) {
122
+ return {
123
+ status: err.statusCode,
124
+ code: err.code,
125
+ message: err.message,
126
+ details: {
127
+ ...err.datasetId ? { datasetId: err.datasetId } : {},
128
+ ...err.minPassRate !== void 0 ? { minPassRate: err.minPassRate } : {},
129
+ ...err.passRate !== void 0 ? { passRate: err.passRate } : {},
130
+ ...err.runId ? { runId: err.runId } : {}
131
+ }
132
+ };
133
+ }
101
134
  if (err instanceof import_sdk2.PersonaApiError) {
102
135
  return {
103
136
  status: err.statusCode,
@@ -129,6 +162,7 @@ function errorToResponse(err, mode) {
129
162
  error: {
130
163
  code: mapped.code,
131
164
  message: mapped.message,
165
+ ...mapped.details !== void 0 ? { details: mapped.details } : {},
132
166
  ...mapped.detail !== void 0 ? { detail: mapped.detail } : {}
133
167
  }
134
168
  })
@@ -159,7 +193,7 @@ function evictStaleRuns(runs, now, graceMs = DEFAULT_RUN_GRACE_MS, maxTracked =
159
193
  }
160
194
 
161
195
  // src/version.ts
162
- var RUNTIME_VERSION = "0.21.0";
196
+ var RUNTIME_VERSION = "0.23.0";
163
197
 
164
198
  // src/routes/health.ts
165
199
  var alwaysOnCapabilities = {
@@ -169,15 +203,61 @@ var alwaysOnCapabilities = {
169
203
  files: true,
170
204
  memory: true,
171
205
  mcpOAuth: true,
172
- workflows: true
173
- };
206
+ workflows: true,
207
+ voice: true,
208
+ feedback: true
209
+ };
210
+ var CAPABILITY_SCOPES = {
211
+ agentsWrite: ["*"],
212
+ mcps: ["*"],
213
+ providers: ["*"],
214
+ skills: ["*"],
215
+ knowledge: ["*"],
216
+ stores: ["*"],
217
+ rcpSources: ["*"],
218
+ auditLogs: ["*"],
219
+ architect: ["*"],
220
+ workflowsWrite: ["*"],
221
+ secrets: ["*"],
222
+ restToolSources: ["*"],
223
+ messages: ["agent:invoke"],
224
+ deploy: ["deploy:write"],
225
+ traces: ["traces:read", "agent:invoke"],
226
+ evals: ["eval:run"],
227
+ observe: ["traces:read"]
228
+ };
229
+ function scopeWarnings(scopes, capabilities) {
230
+ if (!scopes || scopes.includes("*")) return [];
231
+ const has = (s) => scopes.includes(s);
232
+ const warnings = [];
233
+ if (!has("agent:invoke")) {
234
+ warnings.push(
235
+ 'The API key lacks "agent:invoke": chat, threads, files, memory and voice will fail.'
236
+ );
237
+ }
238
+ for (const [cap, needed] of Object.entries(CAPABILITY_SCOPES)) {
239
+ if (!capabilities[cap]) continue;
240
+ const missing = needed.filter((s) => !has(s));
241
+ if (missing.length) {
242
+ warnings.push(
243
+ `capabilities.${cap} is on but the API key lacks ${missing.map((s) => `"${s}"`).join(" and ")}.`
244
+ );
245
+ }
246
+ }
247
+ return warnings;
248
+ }
174
249
  var healthRoute = async (_request, ctx) => {
250
+ let principal;
175
251
  try {
176
- await ctx.client.whoami();
252
+ principal = await ctx.client.whoami();
177
253
  } catch (err) {
178
254
  const failed = errorToResponse(err, ctx.mode);
179
255
  return { ...failed, status: 503 };
180
256
  }
257
+ const raw = principal?.scopes;
258
+ const scopes = Array.isArray(raw) ? raw.filter((s) => typeof s === "string") : void 0;
259
+ const warnings = scopeWarnings(scopes, ctx.capabilities);
260
+ for (const w of warnings) ctx.logger.warn(w);
181
261
  return {
182
262
  kind: "buffered",
183
263
  status: 200,
@@ -185,7 +265,9 @@ var healthRoute = async (_request, ctx) => {
185
265
  body: JSON.stringify({
186
266
  status: "ok",
187
267
  version: RUNTIME_VERSION,
188
- capabilities: { ...alwaysOnCapabilities, ...ctx.capabilities }
268
+ capabilities: { ...alwaysOnCapabilities, ...ctx.capabilities },
269
+ ...scopes ? { scopes } : {},
270
+ ...warnings.length ? { warnings } : {}
189
271
  })
190
272
  };
191
273
  };
@@ -276,6 +358,54 @@ function validateTurnContext(value) {
276
358
  }
277
359
  return context;
278
360
  }
361
+ var ENVIRONMENTS = ["development", "production"];
362
+ function clientVersionFields(body) {
363
+ const out = {};
364
+ if (body.environment !== void 0 && body.environment !== null) {
365
+ if (!ENVIRONMENTS.includes(body.environment)) {
366
+ throw new RuntimeHttpError(
367
+ 400,
368
+ "INVALID_REQUEST",
369
+ '"environment" must be "development" or "production".'
370
+ );
371
+ }
372
+ out.environment = body.environment;
373
+ }
374
+ if (body.agentVersion !== void 0 && body.agentVersion !== null) {
375
+ const v = body.agentVersion;
376
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 1) {
377
+ throw new RuntimeHttpError(
378
+ 400,
379
+ "INVALID_REQUEST",
380
+ '"agentVersion" must be a positive integer.'
381
+ );
382
+ }
383
+ out.agentVersion = v;
384
+ }
385
+ return out;
386
+ }
387
+ async function resolveRunVersion(body, ctx, selection) {
388
+ const { selectVersion, defaultEnvironment, allowClientVersionSelection } = ctx.versioning;
389
+ if (allowClientVersionSelection) {
390
+ const fromClient = clientVersionFields(body);
391
+ if (fromClient.environment || fromClient.agentVersion) return fromClient;
392
+ }
393
+ if (selectVersion) {
394
+ const picked = await selectVersion(selection);
395
+ if (picked && (picked.environment || picked.agentVersion)) {
396
+ return {
397
+ ...picked.environment ? { environment: picked.environment } : {},
398
+ ...picked.agentVersion ? { agentVersion: picked.agentVersion } : {}
399
+ };
400
+ }
401
+ }
402
+ return defaultEnvironment ? { environment: defaultEnvironment } : {};
403
+ }
404
+ function incomingTraceparent(headers, enabled) {
405
+ if (!enabled) return void 0;
406
+ const value = headers.traceparent ?? headers.Traceparent;
407
+ return value && /^[0-9a-f]{2}-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}$/i.test(value.trim()) ? value.trim() : void 0;
408
+ }
279
409
 
280
410
  // src/routes/restToolsManifest.ts
281
411
  var restToolsManifestRoute = async (request, ctx) => {
@@ -334,6 +464,8 @@ function newAccumulator() {
334
464
  interrupted: false,
335
465
  erroredInBand: false,
336
466
  threadCreateFired: false,
467
+ personaRunId: void 0,
468
+ threadId: void 0,
337
469
  toolNames: /* @__PURE__ */ new Map(),
338
470
  nodeMeta: /* @__PURE__ */ new Map()
339
471
  };
@@ -343,6 +475,8 @@ async function processEvent(event, acc, runCtx, hooks) {
343
475
  switch (e.type) {
344
476
  case import_sdk3.EventType.RUN_STARTED: {
345
477
  const threadId = typeof e.threadId === "string" ? e.threadId : void 0;
478
+ if (!acc.personaRunId && typeof e.runId === "string") acc.personaRunId = e.runId;
479
+ if (!acc.threadId && threadId) acc.threadId = threadId;
346
480
  if (runCtx.kind === "chat" && runCtx.agentId && !acc.threadCreateFired && !runCtx.threadId && threadId) {
347
481
  acc.threadCreateFired = true;
348
482
  await hooks?.onThreadCreate?.({ userId: runCtx.userId, agentId: runCtx.agentId, threadId });
@@ -451,9 +585,9 @@ var RunDriver = class {
451
585
  constructor(runId, runCtx, source, hooks, mode) {
452
586
  this.runId = runId;
453
587
  this.runCtx = runCtx;
454
- this.firstFramePromise = new Promise((resolve, reject) => {
588
+ this.firstFramePromise = new Promise((resolve, reject2) => {
455
589
  this.resolveFirstFrame = resolve;
456
- this.rejectFirstFrame = reject;
590
+ this.rejectFirstFrame = reject2;
457
591
  });
458
592
  void this.pump(source, hooks, mode);
459
593
  }
@@ -488,7 +622,9 @@ var RunDriver = class {
488
622
  text: acc.text,
489
623
  eventCount: count,
490
624
  interrupted: acc.interrupted,
491
- erroredInBand: acc.erroredInBand
625
+ erroredInBand: acc.erroredInBand,
626
+ ...acc.personaRunId ? { personaRunId: acc.personaRunId } : {},
627
+ ...acc.threadId || this.runCtx.threadId ? { threadId: acc.threadId ?? this.runCtx.threadId } : {}
492
628
  };
493
629
  try {
494
630
  await hooks?.afterRun?.(this.runCtx, result);
@@ -538,14 +674,13 @@ var RunDriver = class {
538
674
  * original connection plus a reconnect that raced it) are supported.
539
675
  */
540
676
  subscribe(sinceSeq) {
541
- const buffered = this.frames.slice(Math.max(sinceSeq + 1, 0));
542
- const finishedAtSubscribeTime = this.finished;
677
+ const driver = this;
543
678
  const listeners = this.listeners;
544
679
  const doneListeners = this.doneListeners;
545
680
  return {
546
681
  [Symbol.asyncIterator]() {
547
- const queue = [...buffered];
548
- let live = !finishedAtSubscribeTime;
682
+ const queue = driver.frames.slice(Math.max(sinceSeq + 1, 0));
683
+ let live = !driver.finished;
549
684
  let wake = null;
550
685
  const onFrame = (_seq, frame) => {
551
686
  queue.push(frame);
@@ -685,12 +820,19 @@ var chatRoute = async (request, ctx) => {
685
820
  hasContext: !!body.context
686
821
  });
687
822
  const userId = request.userId;
823
+ const version = await resolveRunVersion(request.body, ctx, {
824
+ userId,
825
+ agentId: body.agentId,
826
+ kind: "chat"
827
+ });
828
+ const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
688
829
  const runCtx = {
689
830
  userId,
690
831
  kind: "chat",
691
832
  agentId: body.agentId,
692
833
  threadId: body.threadId,
693
- messages: body.messages
834
+ messages: body.messages,
835
+ ...version
694
836
  };
695
837
  logger.debug("beforeRun hook", { agentId: body.agentId, threadId: body.threadId });
696
838
  await ctx.hooks?.beforeRun?.(runCtx);
@@ -703,7 +845,9 @@ var chatRoute = async (request, ctx) => {
703
845
  contextOverride: body.contextOverride,
704
846
  context: body.context,
705
847
  tools: body.tools,
706
- attachments: body.attachments
848
+ attachments: body.attachments,
849
+ ...version,
850
+ ...traceparent ? { traceparent } : {}
707
851
  });
708
852
  const runId = crypto.randomUUID();
709
853
  logger.info("chat run created", {
@@ -902,7 +1046,13 @@ var createVoiceSession = async (request, ctx) => {
902
1046
  const contextOverride = typeof body.contextOverride === "string" && body.contextOverride ? body.contextOverride : void 0;
903
1047
  const context = validateTurnContext(body.context);
904
1048
  const tools = parseFrontendTools(body.tools);
1049
+ const version = await resolveRunVersion(body, ctx, {
1050
+ userId: request.userId,
1051
+ agentId,
1052
+ kind: "voice"
1053
+ });
905
1054
  const ticket = await ctx.client.voice.createSession(agentId, {
1055
+ ...version,
906
1056
  ...threadId ? { threadId } : {},
907
1057
  ...contextOverride ? { contextOverride } : {},
908
1058
  ...context ? { context } : {},
@@ -911,7 +1061,8 @@ var createVoiceSession = async (request, ctx) => {
911
1061
  await ctx.hooks?.onVoiceSessionCreate?.({
912
1062
  userId: request.userId,
913
1063
  agentId,
914
- threadId
1064
+ threadId,
1065
+ ...version
915
1066
  });
916
1067
  return json(201, ticket);
917
1068
  };
@@ -1745,15 +1896,15 @@ function resolveSignedUrl(request, publicUrl) {
1745
1896
  }
1746
1897
  async function computeTwilioSignature(authToken, url, params) {
1747
1898
  const payload = url + Object.keys(params).sort().map((key2) => `${key2}${params[key2]}`).join("");
1748
- const encoder = new TextEncoder();
1899
+ const encoder2 = new TextEncoder();
1749
1900
  const key = await crypto.subtle.importKey(
1750
1901
  "raw",
1751
- encoder.encode(authToken),
1902
+ encoder2.encode(authToken),
1752
1903
  { name: "HMAC", hash: "SHA-1" },
1753
1904
  false,
1754
1905
  ["sign"]
1755
1906
  );
1756
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
1907
+ const signature = await crypto.subtle.sign("HMAC", key, encoder2.encode(payload));
1757
1908
  const bytes = new Uint8Array(signature);
1758
1909
  let binary = "";
1759
1910
  for (const byte of bytes) binary += String.fromCharCode(byte);
@@ -1918,6 +2069,598 @@ var twilioMessageChannel = async (request, ctx) => {
1918
2069
  }
1919
2070
  };
1920
2071
 
2072
+ // src/routes/feedback.ts
2073
+ var MAX_COMMENT = 2e3;
2074
+ var MAX_TAGS = 10;
2075
+ var MAX_TAG = 50;
2076
+ var createFeedback = async (request, ctx) => {
2077
+ const body = requireBodyObject(request.body);
2078
+ const runId = requireStringField(body, "runId");
2079
+ if (body.score !== 1 && body.score !== -1) {
2080
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", '"score" must be 1 or -1.');
2081
+ }
2082
+ const score = body.score;
2083
+ let comment;
2084
+ if (body.comment !== void 0 && body.comment !== null) {
2085
+ if (typeof body.comment !== "string" || body.comment.length > MAX_COMMENT) {
2086
+ throw new RuntimeHttpError(
2087
+ 400,
2088
+ "INVALID_REQUEST",
2089
+ `"comment" must be a string of at most ${MAX_COMMENT} characters.`
2090
+ );
2091
+ }
2092
+ comment = body.comment.trim() || void 0;
2093
+ }
2094
+ let tags;
2095
+ if (body.tags !== void 0 && body.tags !== null) {
2096
+ if (!Array.isArray(body.tags) || body.tags.length > MAX_TAGS || !body.tags.every((t) => typeof t === "string" && t.length > 0 && t.length <= MAX_TAG)) {
2097
+ throw new RuntimeHttpError(
2098
+ 400,
2099
+ "INVALID_REQUEST",
2100
+ `"tags" must be up to ${MAX_TAGS} strings of at most ${MAX_TAG} characters.`
2101
+ );
2102
+ }
2103
+ tags = body.tags;
2104
+ }
2105
+ const result = await ctx.client.feedback.create({
2106
+ runId,
2107
+ score,
2108
+ ...comment ? { comment } : {},
2109
+ ...tags?.length ? { tags } : {}
2110
+ });
2111
+ await ctx.hooks?.onFeedback?.({
2112
+ userId: request.userId,
2113
+ runId,
2114
+ score,
2115
+ ...comment ? { comment } : {},
2116
+ ...tags?.length ? { tags } : {}
2117
+ });
2118
+ return json(200, result);
2119
+ };
2120
+
2121
+ // src/routes/webhooks.ts
2122
+ var encoder = new TextEncoder();
2123
+ var DEFAULT_SEEN_LIMIT = 1e3;
2124
+ function rawBodyBytes(request) {
2125
+ const raw = request.rawBody;
2126
+ if (raw === void 0 || raw === null) return null;
2127
+ return typeof raw === "string" ? encoder.encode(raw) : raw;
2128
+ }
2129
+ function parseSignature(header) {
2130
+ let timestamp = null;
2131
+ const signatures = [];
2132
+ for (const part of header.split(",")) {
2133
+ const eq = part.indexOf("=");
2134
+ if (eq <= 0) continue;
2135
+ const key = part.slice(0, eq).trim();
2136
+ const value = part.slice(eq + 1).trim();
2137
+ if (key === "t" && /^\d+$/.test(value)) timestamp = Number(value);
2138
+ else if (key === "v1" && /^[0-9a-f]{64}$/i.test(value)) signatures.push(value.toLowerCase());
2139
+ }
2140
+ return timestamp === null || signatures.length === 0 ? null : { timestamp, signatures };
2141
+ }
2142
+ function toHex(bytes) {
2143
+ return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
2144
+ }
2145
+ function safeEqual(a, b) {
2146
+ if (a.length !== b.length) return false;
2147
+ let diff = 0;
2148
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2149
+ return diff === 0;
2150
+ }
2151
+ async function hmacHex(secret, message) {
2152
+ const key = await crypto.subtle.importKey(
2153
+ "raw",
2154
+ encoder.encode(secret),
2155
+ { name: "HMAC", hash: "SHA-256" },
2156
+ false,
2157
+ ["sign"]
2158
+ );
2159
+ return toHex(await crypto.subtle.sign("HMAC", key, message));
2160
+ }
2161
+ function concat(a, b) {
2162
+ const out = new Uint8Array(a.length + b.length);
2163
+ out.set(a, 0);
2164
+ out.set(b, a.length);
2165
+ return out;
2166
+ }
2167
+ var reject = (code, message) => json(400, { error: { code, message } });
2168
+ function createSeenSet(limit = DEFAULT_SEEN_LIMIT) {
2169
+ const ids = /* @__PURE__ */ new Set();
2170
+ return {
2171
+ has: (id) => ids.has(id),
2172
+ add(id) {
2173
+ ids.add(id);
2174
+ if (ids.size > limit) ids.delete(ids.values().next().value);
2175
+ }
2176
+ };
2177
+ }
2178
+ function createWebhookRoute() {
2179
+ const memory = createSeenSet();
2180
+ return async (request, ctx) => {
2181
+ const options = ctx.webhooks;
2182
+ if (!options) return json(404, { error: { code: "NOT_FOUND", message: "Not found" } });
2183
+ const logger = ctx.logger.child("webhooks");
2184
+ const body = rawBodyBytes(request);
2185
+ if (!body) {
2186
+ logger.error("webhook request has no rawBody: the adapter must pass the raw bytes");
2187
+ return json(500, {
2188
+ error: {
2189
+ code: "RUNTIME_MISCONFIGURED",
2190
+ message: "The server did not keep the raw request body for webhook verification."
2191
+ }
2192
+ });
2193
+ }
2194
+ const header = request.headers["x-persona-signature"];
2195
+ if (!header) return reject("MISSING_SIGNATURE", "Missing X-Persona-Signature header");
2196
+ const parsed = parseSignature(header);
2197
+ if (!parsed) return reject("INVALID_SIGNATURE", "Malformed X-Persona-Signature header");
2198
+ const expected = await hmacHex(
2199
+ options.secret,
2200
+ concat(encoder.encode(`${parsed.timestamp}.`), body)
2201
+ );
2202
+ if (!parsed.signatures.some((sig) => safeEqual(sig, expected))) {
2203
+ logger.warn("webhook signature does not match");
2204
+ return reject("INVALID_SIGNATURE", "Webhook signature does not match");
2205
+ }
2206
+ const tolerance = options.toleranceSec ?? 300;
2207
+ if (tolerance > 0 && Math.abs(Math.floor(Date.now() / 1e3) - parsed.timestamp) > tolerance) {
2208
+ return reject("TIMESTAMP_OUT_OF_RANGE", "Webhook timestamp is outside the allowed tolerance");
2209
+ }
2210
+ let event;
2211
+ try {
2212
+ event = JSON.parse(new TextDecoder().decode(body));
2213
+ } catch {
2214
+ return reject("INVALID_PAYLOAD", "Webhook body is not valid JSON");
2215
+ }
2216
+ if (!event || typeof event !== "object" || typeof event.type !== "string") {
2217
+ return reject("INVALID_PAYLOAD", "Webhook body is not a Persona event");
2218
+ }
2219
+ const deliveryId = request.headers["x-persona-delivery"];
2220
+ if (deliveryId) {
2221
+ const store = options.dedupe ?? memory;
2222
+ if (await store.has(deliveryId)) {
2223
+ logger.debug("duplicate webhook delivery ignored", { deliveryId, type: event.type });
2224
+ return json(200, { received: true, duplicate: true });
2225
+ }
2226
+ }
2227
+ const type = event.type;
2228
+ const handlers = [options.on[type], options.on["*"]].filter(
2229
+ (h) => typeof h === "function"
2230
+ );
2231
+ try {
2232
+ for (const handler of handlers) await handler(event, { id: deliveryId, type });
2233
+ } catch (err) {
2234
+ logger.error("webhook handler failed", {
2235
+ type,
2236
+ deliveryId,
2237
+ error: err instanceof Error ? err.message : String(err)
2238
+ });
2239
+ return json(500, { error: { code: "HANDLER_FAILED", message: "Webhook handler failed" } });
2240
+ }
2241
+ if (deliveryId) await (options.dedupe ?? memory).add(deliveryId);
2242
+ logger.info("webhook handled", { type, deliveryId, handlers: handlers.length });
2243
+ return json(200, { received: true });
2244
+ };
2245
+ }
2246
+
2247
+ // src/routes/secrets.ts
2248
+ var listSecrets = async (_request, ctx) => {
2249
+ return json(200, await ctx.client.secrets.list());
2250
+ };
2251
+ var createSecret = async (request, ctx) => {
2252
+ const body = requireBodyObject(request.body);
2253
+ const secret = await ctx.client.secrets.create(
2254
+ {
2255
+ label: requireStringField(body, "label"),
2256
+ value: requireStringField(body, "value")
2257
+ },
2258
+ request.headers["idempotency-key"]
2259
+ );
2260
+ return json(201, secret);
2261
+ };
2262
+ var getSecret = async (_request, ctx) => {
2263
+ return json(200, await ctx.client.secrets.get(requireParam(ctx.params, "id")));
2264
+ };
2265
+ var updateSecret = async (request, ctx) => {
2266
+ const body = request.body ?? {};
2267
+ return json(200, await ctx.client.secrets.update(requireParam(ctx.params, "id"), body));
2268
+ };
2269
+ var deleteSecret = async (_request, ctx) => {
2270
+ await ctx.client.secrets.delete(requireParam(ctx.params, "id"));
2271
+ return noContent();
2272
+ };
2273
+ var getSecretUsage = async (_request, ctx) => {
2274
+ return json(200, await ctx.client.secrets.getUsage(requireParam(ctx.params, "id")));
2275
+ };
2276
+ var bulkDeleteSecrets = async (request, ctx) => {
2277
+ const body = requireBodyObject(request.body);
2278
+ const ids = Array.isArray(body.ids) ? body.ids : [];
2279
+ return json(200, await ctx.client.secrets.bulkDelete(ids));
2280
+ };
2281
+
2282
+ // src/routes/restToolSources.ts
2283
+ var listRestToolSources = async (request, ctx) => {
2284
+ const items = await ctx.client.restToolSources.list({
2285
+ page: toInt(request.query.page),
2286
+ limit: toInt(request.query.limit),
2287
+ search: request.query.search
2288
+ });
2289
+ return json(200, items);
2290
+ };
2291
+ var createRestToolSource = async (request, ctx) => {
2292
+ const body = requireBodyObject(request.body);
2293
+ const source = await ctx.client.restToolSources.create(body, request.headers["idempotency-key"]);
2294
+ return json(201, source);
2295
+ };
2296
+ var getRestToolSource = async (_request, ctx) => {
2297
+ return json(200, await ctx.client.restToolSources.get(requireParam(ctx.params, "id")));
2298
+ };
2299
+ var updateRestToolSource = async (request, ctx) => {
2300
+ const body = request.body ?? {};
2301
+ return json(200, await ctx.client.restToolSources.update(requireParam(ctx.params, "id"), body));
2302
+ };
2303
+ var deleteRestToolSource = async (_request, ctx) => {
2304
+ await ctx.client.restToolSources.delete(requireParam(ctx.params, "id"));
2305
+ return noContent();
2306
+ };
2307
+ var getRestToolSourceUsage = async (_request, ctx) => {
2308
+ return json(200, await ctx.client.restToolSources.getUsage(requireParam(ctx.params, "id")));
2309
+ };
2310
+ var testRestToolSource = async (_request, ctx) => {
2311
+ return json(200, await ctx.client.restToolSources.testConnection(requireParam(ctx.params, "id")));
2312
+ };
2313
+ var bulkDeleteRestToolSources = async (request, ctx) => {
2314
+ const body = requireBodyObject(request.body);
2315
+ const ids = Array.isArray(body.ids) ? body.ids : [];
2316
+ return json(200, await ctx.client.restToolSources.bulkDelete(ids));
2317
+ };
2318
+
2319
+ // src/routes/deploy.ts
2320
+ function environmentParam(ctx) {
2321
+ const env = requireParam(ctx.params, "environment");
2322
+ if (env !== "development" && env !== "production") {
2323
+ throw new RuntimeHttpError(
2324
+ 400,
2325
+ "INVALID_REQUEST",
2326
+ 'environment must be "development" or "production".'
2327
+ );
2328
+ }
2329
+ return env;
2330
+ }
2331
+ function positiveInt(raw, name) {
2332
+ const v = Number(raw);
2333
+ if (!Number.isInteger(v) || v < 1) {
2334
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", `${name} must be a positive integer.`);
2335
+ }
2336
+ return v;
2337
+ }
2338
+ function versionOrDraft(raw, name) {
2339
+ if (!raw || raw === "draft") return "draft";
2340
+ return positiveInt(raw, name);
2341
+ }
2342
+ async function audit(request, ctx, action, target) {
2343
+ await ctx.hooks?.onAdminAction?.({
2344
+ userId: request.userId,
2345
+ capability: "deploy",
2346
+ action,
2347
+ target
2348
+ });
2349
+ }
2350
+ var listAgentVersions = async (request, ctx) => {
2351
+ return json(
2352
+ 200,
2353
+ await ctx.projectClient().agents.versions.list(requireParam(ctx.params, "id"), {
2354
+ page: toInt(request.query.page),
2355
+ limit: toInt(request.query.limit)
2356
+ })
2357
+ );
2358
+ };
2359
+ var publishAgentVersion = async (request, ctx) => {
2360
+ const agentId = requireParam(ctx.params, "id");
2361
+ const body = request.body ?? {};
2362
+ const changelog = typeof body.changelog === "string" ? body.changelog : void 0;
2363
+ const version = await ctx.projectClient().agents.versions.publish(agentId, changelog ? { changelog } : {});
2364
+ await audit(request, ctx, "agent.version.publish", { agentId, version: version.version });
2365
+ return json(201, version);
2366
+ };
2367
+ var agentVersionStats = async (request, ctx) => {
2368
+ const source = request.query.source;
2369
+ return json(
2370
+ 200,
2371
+ await ctx.projectClient().agents.versions.stats(requireParam(ctx.params, "id"), {
2372
+ days: toInt(request.query.days),
2373
+ ...source ? { source } : {}
2374
+ })
2375
+ );
2376
+ };
2377
+ var getAgentVersion = async (_request, ctx) => {
2378
+ const version = positiveInt(requireParam(ctx.params, "version"), "version");
2379
+ return json(
2380
+ 200,
2381
+ await ctx.projectClient().agents.versions.get(requireParam(ctx.params, "id"), version)
2382
+ );
2383
+ };
2384
+ var diffAgentVersion = async (request, ctx) => {
2385
+ const from = versionOrDraft(requireParam(ctx.params, "version"), "version");
2386
+ const to = versionOrDraft(request.query.against, '"against"');
2387
+ return json(
2388
+ 200,
2389
+ await ctx.projectClient().agents.versions.diff(requireParam(ctx.params, "id"), from, to)
2390
+ );
2391
+ };
2392
+ var getAgentDeployments = async (_request, ctx) => {
2393
+ return json(
2394
+ 200,
2395
+ await ctx.projectClient().agents.versions.deployments(requireParam(ctx.params, "id"))
2396
+ );
2397
+ };
2398
+ var setAgentServePolicy = async (request, ctx) => {
2399
+ const agentId = requireParam(ctx.params, "id");
2400
+ const body = requireBodyObject(request.body);
2401
+ if (typeof body.serveDeployedVersion !== "boolean") {
2402
+ throw new RuntimeHttpError(
2403
+ 400,
2404
+ "INVALID_REQUEST",
2405
+ '"serveDeployedVersion" is required and must be a boolean.'
2406
+ );
2407
+ }
2408
+ const result = await ctx.projectClient().agents.versions.setServePolicy(agentId, body.serveDeployedVersion);
2409
+ await audit(request, ctx, "agent.serve_policy.set", { agentId, ...result });
2410
+ return json(200, result);
2411
+ };
2412
+ var deployAgentVersion = async (request, ctx) => {
2413
+ const agentId = requireParam(ctx.params, "id");
2414
+ const environment = environmentParam(ctx);
2415
+ const body = request.body ?? {};
2416
+ let version;
2417
+ if (body.version !== void 0 && body.version !== null) {
2418
+ if (typeof body.version !== "number" || !Number.isInteger(body.version) || body.version < 1) {
2419
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", '"version" must be a positive integer.');
2420
+ }
2421
+ version = body.version;
2422
+ }
2423
+ const deployment = await ctx.projectClient().agents.versions.deploy(agentId, { environment, ...version ? { version } : {} });
2424
+ await audit(request, ctx, "agent.version.deploy", {
2425
+ agentId,
2426
+ environment,
2427
+ version: deployment.version
2428
+ });
2429
+ return json(200, deployment);
2430
+ };
2431
+ var rollbackAgentDeployment = async (request, ctx) => {
2432
+ const agentId = requireParam(ctx.params, "id");
2433
+ const environment = environmentParam(ctx);
2434
+ const deployment = await ctx.projectClient().agents.versions.rollback(agentId, environment);
2435
+ await audit(request, ctx, "agent.version.rollback", {
2436
+ agentId,
2437
+ environment,
2438
+ version: deployment.version
2439
+ });
2440
+ return json(200, deployment);
2441
+ };
2442
+
2443
+ // src/routes/traces.ts
2444
+ var STRING_FILTERS = [
2445
+ "agentId",
2446
+ "threadId",
2447
+ "runId",
2448
+ "status",
2449
+ "source",
2450
+ "channel",
2451
+ "environment",
2452
+ "feedback",
2453
+ "from",
2454
+ "to"
2455
+ ];
2456
+ var listTraces = async (request, ctx) => {
2457
+ const params = {
2458
+ page: toInt(request.query.page),
2459
+ limit: toInt(request.query.limit),
2460
+ agentVersion: toInt(request.query.agentVersion)
2461
+ };
2462
+ for (const key of STRING_FILTERS) {
2463
+ const value = request.query[key];
2464
+ if (value) params[key] = value;
2465
+ }
2466
+ return json(200, await ctx.projectClient().traces.list(params));
2467
+ };
2468
+ var getTrace = async (request, ctx) => {
2469
+ return json(
2470
+ 200,
2471
+ await ctx.projectClient().traces.get(requireParam(ctx.params, "id"), {
2472
+ spans: request.query.spans !== "false"
2473
+ })
2474
+ );
2475
+ };
2476
+ var getTraceFeedback = async (_request, ctx) => {
2477
+ return json(200, await ctx.projectClient().traces.feedback(requireParam(ctx.params, "id")));
2478
+ };
2479
+ var getReplayPlan = async (_request, ctx) => {
2480
+ return json(200, await ctx.projectClient().traces.replayPlan(requireParam(ctx.params, "id")));
2481
+ };
2482
+ var replayTrace = async (request, ctx) => {
2483
+ const traceId = requireParam(ctx.params, "id");
2484
+ const options = request.body ?? {};
2485
+ const result = await ctx.projectClient().traces.replay(traceId, options);
2486
+ await ctx.hooks?.onAdminAction?.({
2487
+ userId: request.userId,
2488
+ capability: "traces",
2489
+ action: "trace.replay",
2490
+ target: { traceId, replayTraceId: result.traceId }
2491
+ });
2492
+ return json(201, result);
2493
+ };
2494
+ var listTraceReplays = async (_request, ctx) => {
2495
+ return json(200, await ctx.projectClient().traces.replays(requireParam(ctx.params, "id")));
2496
+ };
2497
+
2498
+ // src/routes/evals.ts
2499
+ async function audit2(request, ctx, action, target) {
2500
+ await ctx.hooks?.onAdminAction?.({
2501
+ userId: request.userId,
2502
+ capability: "evals",
2503
+ action,
2504
+ target
2505
+ });
2506
+ }
2507
+ var listDatasets = async (_request, ctx) => {
2508
+ return json(200, await ctx.projectClient().datasets.list());
2509
+ };
2510
+ var createDataset = async (request, ctx) => {
2511
+ const body = requireBodyObject(request.body);
2512
+ const dataset = await ctx.projectClient().datasets.create(body);
2513
+ await audit2(request, ctx, "dataset.create", { datasetId: dataset._id });
2514
+ return json(201, dataset);
2515
+ };
2516
+ var getDataset = async (_request, ctx) => {
2517
+ return json(200, await ctx.projectClient().datasets.get(requireParam(ctx.params, "id")));
2518
+ };
2519
+ var updateDataset = async (request, ctx) => {
2520
+ const body = requireBodyObject(request.body);
2521
+ return json(200, await ctx.projectClient().datasets.update(requireParam(ctx.params, "id"), body));
2522
+ };
2523
+ var deleteDataset = async (request, ctx) => {
2524
+ const datasetId = requireParam(ctx.params, "id");
2525
+ const result = await ctx.projectClient().datasets.delete(datasetId);
2526
+ await audit2(request, ctx, "dataset.delete", { datasetId });
2527
+ return json(200, result);
2528
+ };
2529
+ var listDatasetItems = async (request, ctx) => {
2530
+ return json(
2531
+ 200,
2532
+ await ctx.projectClient().datasets.items.list(requireParam(ctx.params, "id"), {
2533
+ page: toInt(request.query.page),
2534
+ limit: toInt(request.query.limit)
2535
+ })
2536
+ );
2537
+ };
2538
+ var addDatasetItems = async (request, ctx) => {
2539
+ const datasetId = requireParam(ctx.params, "id");
2540
+ const body = requireBodyObject(request.body);
2541
+ let cases;
2542
+ if (typeof body.text === "string") cases = body.text;
2543
+ else if (Array.isArray(body.items)) cases = body.items;
2544
+ else {
2545
+ throw new RuntimeHttpError(
2546
+ 400,
2547
+ "INVALID_REQUEST",
2548
+ 'Send "items" (an array of cases) or "text" (CSV / JSON).'
2549
+ );
2550
+ }
2551
+ try {
2552
+ return json(201, await ctx.projectClient().datasets.import(datasetId, cases));
2553
+ } catch (err) {
2554
+ if (err instanceof Error && err.message.startsWith("parseDatasetCases:")) {
2555
+ throw new RuntimeHttpError(
2556
+ 400,
2557
+ "INVALID_REQUEST",
2558
+ err.message.replace("parseDatasetCases: ", "")
2559
+ );
2560
+ }
2561
+ throw err;
2562
+ }
2563
+ };
2564
+ var addDatasetItemFromTrace = async (request, ctx) => {
2565
+ const body = requireBodyObject(request.body);
2566
+ const traceId = requireStringField(body, "traceId");
2567
+ const options = body.expected === null ? { expected: null } : typeof body.expected === "string" ? { expected: body.expected } : {};
2568
+ return json(
2569
+ 201,
2570
+ await ctx.projectClient().datasets.items.addFromTrace(requireParam(ctx.params, "id"), traceId, options)
2571
+ );
2572
+ };
2573
+ var updateDatasetItem = async (request, ctx) => {
2574
+ const body = requireBodyObject(request.body);
2575
+ return json(
2576
+ 200,
2577
+ await ctx.projectClient().datasets.items.update(
2578
+ requireParam(ctx.params, "id"),
2579
+ requireParam(ctx.params, "itemId"),
2580
+ body
2581
+ )
2582
+ );
2583
+ };
2584
+ var deleteDatasetItem = async (_request, ctx) => {
2585
+ return json(
2586
+ 200,
2587
+ await ctx.projectClient().datasets.items.delete(requireParam(ctx.params, "id"), requireParam(ctx.params, "itemId"))
2588
+ );
2589
+ };
2590
+ var listEvalRuns = async (request, ctx) => {
2591
+ return json(
2592
+ 200,
2593
+ await ctx.projectClient().evals.list({
2594
+ page: toInt(request.query.page),
2595
+ limit: toInt(request.query.limit),
2596
+ ...request.query.agentId ? { agentId: request.query.agentId } : {},
2597
+ ...request.query.datasetId ? { datasetId: request.query.datasetId } : {}
2598
+ })
2599
+ );
2600
+ };
2601
+ var startEvalRun = async (request, ctx) => {
2602
+ const body = requireBodyObject(request.body);
2603
+ const run = await ctx.projectClient().evals.create(body);
2604
+ await audit2(request, ctx, "eval_run.start", {
2605
+ runId: run.id,
2606
+ agentId: run.agentId,
2607
+ datasetId: run.datasetId
2608
+ });
2609
+ return json(201, run);
2610
+ };
2611
+ var compareEvalRuns = async (request, ctx) => {
2612
+ const { a, b } = request.query;
2613
+ if (!a || !b) {
2614
+ throw new RuntimeHttpError(
2615
+ 400,
2616
+ "INVALID_REQUEST",
2617
+ '"a" and "b" query parameters are required.'
2618
+ );
2619
+ }
2620
+ return json(200, await ctx.projectClient().evals.compare(a, b));
2621
+ };
2622
+ var getEvalRun = async (_request, ctx) => {
2623
+ return json(200, await ctx.projectClient().evals.get(requireParam(ctx.params, "id")));
2624
+ };
2625
+ var deleteEvalRun = async (request, ctx) => {
2626
+ const runId = requireParam(ctx.params, "id");
2627
+ const result = await ctx.projectClient().evals.delete(runId);
2628
+ await audit2(request, ctx, "eval_run.delete", { runId });
2629
+ return json(200, result);
2630
+ };
2631
+ var listEvalResults = async (request, ctx) => {
2632
+ return json(
2633
+ 200,
2634
+ await ctx.projectClient().evals.results(requireParam(ctx.params, "id"), {
2635
+ page: toInt(request.query.page),
2636
+ limit: toInt(request.query.limit),
2637
+ ...request.query.failed === "true" ? { failed: true } : {}
2638
+ })
2639
+ );
2640
+ };
2641
+ var cancelEvalRun = async (request, ctx) => {
2642
+ const runId = requireParam(ctx.params, "id");
2643
+ const run = await ctx.projectClient().evals.cancel(runId);
2644
+ await audit2(request, ctx, "eval_run.cancel", { runId });
2645
+ return json(200, run);
2646
+ };
2647
+
2648
+ // src/routes/observe.ts
2649
+ var getBudget = async (_request, ctx) => {
2650
+ return json(200, await ctx.projectClient().observe.budget());
2651
+ };
2652
+ var getUsage = async (request, ctx) => {
2653
+ const source = request.query.source;
2654
+ return json(
2655
+ 200,
2656
+ await ctx.projectClient().observe.usage({
2657
+ days: toInt(request.query.days),
2658
+ ...request.query.agentId ? { agentId: request.query.agentId } : {},
2659
+ ...source ? { source } : {}
2660
+ })
2661
+ );
2662
+ };
2663
+
1921
2664
  // src/runtime.ts
1922
2665
  function resolveCapabilities(capabilities) {
1923
2666
  return {
@@ -1931,7 +2674,13 @@ function resolveCapabilities(capabilities) {
1931
2674
  auditLogs: capabilities?.auditLogs ?? false,
1932
2675
  architect: capabilities?.architect ?? false,
1933
2676
  workflowsWrite: capabilities?.workflowsWrite ?? false,
1934
- messages: capabilities?.messages ?? false
2677
+ messages: capabilities?.messages ?? false,
2678
+ secrets: capabilities?.secrets ?? false,
2679
+ restToolSources: capabilities?.restToolSources ?? false,
2680
+ deploy: capabilities?.deploy ?? false,
2681
+ traces: capabilities?.traces ?? false,
2682
+ evals: capabilities?.evals ?? false,
2683
+ observe: capabilities?.observe ?? false
1935
2684
  };
1936
2685
  }
1937
2686
  function buildRoutes(capabilities) {
@@ -1953,6 +2702,9 @@ function buildRoutes(capabilities) {
1953
2702
  // (powered by Gemini Live). Mints a ticket only; the host's own
1954
2703
  // frontend opens the WebSocket directly, this runtime never proxies it.
1955
2704
  { method: "POST", pattern: ["voice", "sessions"], handler: createVoiceSession },
2705
+ // Feedback — always on: an end user rates one of their own runs
2706
+ // (Persona refuses ratings on anyone else's).
2707
+ { method: "POST", pattern: ["feedback"], handler: createFeedback },
1956
2708
  // Agents — read-only discovery always on; write ops behind agentsWrite.
1957
2709
  { method: "GET", pattern: ["agents"], handler: listAgents },
1958
2710
  // Files — always on, end-user-scoped uploads.
@@ -2157,8 +2909,128 @@ function buildRoutes(capabilities) {
2157
2909
  { method: "GET", pattern: ["workflows", "runs", ":runId"], handler: getWorkflowRun }
2158
2910
  );
2159
2911
  }
2912
+ if (capabilities.secrets) {
2913
+ routes.push(
2914
+ { method: "GET", pattern: ["secrets"], handler: listSecrets },
2915
+ { method: "POST", pattern: ["secrets"], handler: createSecret },
2916
+ { method: "POST", pattern: ["secrets", "bulk-delete"], handler: bulkDeleteSecrets },
2917
+ { method: "GET", pattern: ["secrets", ":id"], handler: getSecret },
2918
+ { method: "PATCH", pattern: ["secrets", ":id"], handler: updateSecret },
2919
+ { method: "DELETE", pattern: ["secrets", ":id"], handler: deleteSecret },
2920
+ { method: "GET", pattern: ["secrets", ":id", "usage"], handler: getSecretUsage }
2921
+ );
2922
+ }
2923
+ if (capabilities.restToolSources) {
2924
+ routes.push(
2925
+ { method: "GET", pattern: ["rest-tool-sources"], handler: listRestToolSources },
2926
+ { method: "POST", pattern: ["rest-tool-sources"], handler: createRestToolSource },
2927
+ {
2928
+ method: "POST",
2929
+ pattern: ["rest-tool-sources", "bulk-delete"],
2930
+ handler: bulkDeleteRestToolSources
2931
+ },
2932
+ { method: "GET", pattern: ["rest-tool-sources", ":id"], handler: getRestToolSource },
2933
+ { method: "PATCH", pattern: ["rest-tool-sources", ":id"], handler: updateRestToolSource },
2934
+ { method: "DELETE", pattern: ["rest-tool-sources", ":id"], handler: deleteRestToolSource },
2935
+ {
2936
+ method: "GET",
2937
+ pattern: ["rest-tool-sources", ":id", "usage"],
2938
+ handler: getRestToolSourceUsage
2939
+ },
2940
+ { method: "POST", pattern: ["rest-tool-sources", ":id", "test"], handler: testRestToolSource }
2941
+ );
2942
+ }
2943
+ if (capabilities.deploy) {
2944
+ routes.push(
2945
+ { method: "GET", pattern: ["agents", ":id", "versions"], handler: listAgentVersions },
2946
+ { method: "POST", pattern: ["agents", ":id", "versions"], handler: publishAgentVersion },
2947
+ // Before `versions/:version`, which would otherwise capture "stats".
2948
+ {
2949
+ method: "GET",
2950
+ pattern: ["agents", ":id", "versions", "stats"],
2951
+ handler: agentVersionStats
2952
+ },
2953
+ {
2954
+ method: "GET",
2955
+ pattern: ["agents", ":id", "versions", ":version"],
2956
+ handler: getAgentVersion
2957
+ },
2958
+ {
2959
+ method: "GET",
2960
+ pattern: ["agents", ":id", "versions", ":version", "diff"],
2961
+ handler: diffAgentVersion
2962
+ },
2963
+ { method: "GET", pattern: ["agents", ":id", "deployments"], handler: getAgentDeployments },
2964
+ {
2965
+ method: "PUT",
2966
+ pattern: ["agents", ":id", "deployments", "policy"],
2967
+ handler: setAgentServePolicy
2968
+ },
2969
+ {
2970
+ method: "POST",
2971
+ pattern: ["agents", ":id", "deployments", ":environment"],
2972
+ handler: deployAgentVersion
2973
+ },
2974
+ {
2975
+ method: "POST",
2976
+ pattern: ["agents", ":id", "deployments", ":environment", "rollback"],
2977
+ handler: rollbackAgentDeployment
2978
+ }
2979
+ );
2980
+ }
2981
+ if (capabilities.traces) {
2982
+ routes.push(
2983
+ { method: "GET", pattern: ["traces"], handler: listTraces },
2984
+ { method: "GET", pattern: ["traces", ":id"], handler: getTrace },
2985
+ { method: "GET", pattern: ["traces", ":id", "feedback"], handler: getTraceFeedback },
2986
+ { method: "GET", pattern: ["traces", ":id", "replay"], handler: getReplayPlan },
2987
+ { method: "POST", pattern: ["traces", ":id", "replay"], handler: replayTrace },
2988
+ { method: "GET", pattern: ["traces", ":id", "replays"], handler: listTraceReplays }
2989
+ );
2990
+ }
2991
+ if (capabilities.evals) {
2992
+ routes.push(
2993
+ { method: "GET", pattern: ["datasets"], handler: listDatasets },
2994
+ { method: "POST", pattern: ["datasets"], handler: createDataset },
2995
+ { method: "GET", pattern: ["datasets", ":id"], handler: getDataset },
2996
+ { method: "PATCH", pattern: ["datasets", ":id"], handler: updateDataset },
2997
+ { method: "DELETE", pattern: ["datasets", ":id"], handler: deleteDataset },
2998
+ { method: "GET", pattern: ["datasets", ":id", "items"], handler: listDatasetItems },
2999
+ { method: "POST", pattern: ["datasets", ":id", "items"], handler: addDatasetItems },
3000
+ {
3001
+ method: "POST",
3002
+ pattern: ["datasets", ":id", "items", "from-trace"],
3003
+ handler: addDatasetItemFromTrace
3004
+ },
3005
+ {
3006
+ method: "PATCH",
3007
+ pattern: ["datasets", ":id", "items", ":itemId"],
3008
+ handler: updateDatasetItem
3009
+ },
3010
+ {
3011
+ method: "DELETE",
3012
+ pattern: ["datasets", ":id", "items", ":itemId"],
3013
+ handler: deleteDatasetItem
3014
+ },
3015
+ { method: "GET", pattern: ["eval-runs"], handler: listEvalRuns },
3016
+ { method: "POST", pattern: ["eval-runs"], handler: startEvalRun },
3017
+ // Before `eval-runs/:id`, which would otherwise capture "compare".
3018
+ { method: "GET", pattern: ["eval-runs", "compare"], handler: compareEvalRuns },
3019
+ { method: "GET", pattern: ["eval-runs", ":id"], handler: getEvalRun },
3020
+ { method: "DELETE", pattern: ["eval-runs", ":id"], handler: deleteEvalRun },
3021
+ { method: "GET", pattern: ["eval-runs", ":id", "results"], handler: listEvalResults },
3022
+ { method: "POST", pattern: ["eval-runs", ":id", "cancel"], handler: cancelEvalRun }
3023
+ );
3024
+ }
3025
+ if (capabilities.observe) {
3026
+ routes.push(
3027
+ { method: "GET", pattern: ["observe", "budget"], handler: getBudget },
3028
+ { method: "GET", pattern: ["observe", "usage"], handler: getUsage }
3029
+ );
3030
+ }
2160
3031
  return routes;
2161
3032
  }
3033
+ var ADMIN_CAPABILITIES = ["deploy", "traces", "evals", "observe"];
2162
3034
  function resolveMode(options) {
2163
3035
  if (options.mode) return options.mode;
2164
3036
  return process.env.NODE_ENV === "development" ? "development" : "production";
@@ -2205,6 +3077,15 @@ function createRuntime(options) {
2205
3077
  }
2206
3078
  );
2207
3079
  }
3080
+ if (options.webhooks) {
3081
+ if (!options.webhooks.secret) throw new Error('createRuntime: "webhooks.secret" is required');
3082
+ routes.push({
3083
+ method: "POST",
3084
+ pattern: ["webhooks", "persona"],
3085
+ handler: createWebhookRoute(),
3086
+ requiresAuth: false
3087
+ });
3088
+ }
2208
3089
  const mode = resolveMode(options);
2209
3090
  const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
2210
3091
  const runGraceMs = options.runGraceMs ?? DEFAULT_RUN_GRACE_MS;
@@ -2223,6 +3104,18 @@ function createRuntime(options) {
2223
3104
  mountPath: options.mountPath ?? "",
2224
3105
  enabledCapabilities: enabledCaps.length ? enabledCaps : ["(core only)"]
2225
3106
  });
3107
+ const adminCaps = enabledCaps.filter((c) => ADMIN_CAPABILITIES.includes(c));
3108
+ if (adminCaps.length) {
3109
+ logger.warn(
3110
+ "Project-wide admin capabilities are on: they act for the whole Project, not the resolved user. Serve them only behind a resolveUser that admits your own team.",
3111
+ {
3112
+ capabilities: adminCaps,
3113
+ scopes: Object.fromEntries(
3114
+ adminCaps.map((c) => [c, CAPABILITY_SCOPES[c]])
3115
+ )
3116
+ }
3117
+ );
3118
+ }
2226
3119
  logger.trace("runtime config", {
2227
3120
  baseUrl: options.baseUrl,
2228
3121
  hasHooks: !!options.hooks,
@@ -2355,6 +3248,7 @@ function createRuntime(options) {
2355
3248
  }
2356
3249
  const resolvedRequest = { ...request, userId };
2357
3250
  const client = createClientForRequest(options, userId, logger);
3251
+ let projectClient;
2358
3252
  const routeLogger = logger.child("route");
2359
3253
  logger.debug("handler start", {
2360
3254
  method: request.method,
@@ -2373,7 +3267,15 @@ function createRuntime(options) {
2373
3267
  logger: routeLogger,
2374
3268
  restToolsManifest: options.restToolsManifest,
2375
3269
  rcpManifest: options.rcpManifest,
2376
- channels: options.channels
3270
+ channels: options.channels,
3271
+ projectClient: () => userId === null ? client : projectClient ??= createClientForRequest(options, null, logger),
3272
+ webhooks: options.webhooks,
3273
+ versioning: {
3274
+ selectVersion: options.selectVersion,
3275
+ defaultEnvironment: options.defaultEnvironment,
3276
+ allowClientVersionSelection: options.allowClientVersionSelection
3277
+ },
3278
+ forwardTraceparent: options.forwardTraceparent ?? true
2377
3279
  });
2378
3280
  const durationMs = Date.now() - startMs;
2379
3281
  logger.info("handle succeeded", {
@@ -2434,6 +3336,7 @@ function createRuntime(options) {
2434
3336
  }
2435
3337
  // Annotate the CommonJS export names for ESM import in node:
2436
3338
  0 && (module.exports = {
3339
+ CAPABILITY_SCOPES,
2437
3340
  RUNTIME_VERSION,
2438
3341
  RuntimeHttpError,
2439
3342
  createRuntime