@personaai/runtime 0.22.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.js CHANGED
@@ -46,6 +46,8 @@ function createClientForRequest(options, userId, logger) {
46
46
  return new PersonaClient({
47
47
  baseUrl: options.baseUrl,
48
48
  credential: options.credential,
49
+ // No end user (`null`) = a Project-scoped client: `/health` and the
50
+ // Project-wide admin capabilities.
49
51
  externalUserId: userId ?? void 0,
50
52
  fetch: options.fetch,
51
53
  logger: clientLogger
@@ -53,7 +55,12 @@ function createClientForRequest(options, userId, logger) {
53
55
  }
54
56
 
55
57
  // src/errors.ts
56
- import { PersonaApiError } from "@personaai/sdk";
58
+ import {
59
+ BudgetExceededError,
60
+ EvalGateError,
61
+ ForbiddenScopeError,
62
+ PersonaApiError
63
+ } from "@personaai/sdk";
57
64
  var RuntimeHttpError = class _RuntimeHttpError extends Error {
58
65
  status;
59
66
  code;
@@ -70,6 +77,36 @@ function mapErrorToRuntimeError(err, mode) {
70
77
  if (err instanceof RuntimeHttpError) {
71
78
  return { status: err.status, code: err.code, message: err.message };
72
79
  }
80
+ if (err instanceof ForbiddenScopeError) {
81
+ return {
82
+ status: 500,
83
+ code: "RUNTIME_MISCONFIGURED",
84
+ 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.",
85
+ ...dev ? { detail: { requiredScope: err.requiredScope, acceptedScopes: err.acceptedScopes } } : {}
86
+ };
87
+ }
88
+ if (err instanceof BudgetExceededError) {
89
+ return {
90
+ status: 402,
91
+ code: err.code,
92
+ message: err.message,
93
+ ...err.scope ? { details: { scope: err.scope } } : {},
94
+ ...dev ? { detail: err.response } : {}
95
+ };
96
+ }
97
+ if (err instanceof EvalGateError) {
98
+ return {
99
+ status: err.statusCode,
100
+ code: err.code,
101
+ message: err.message,
102
+ details: {
103
+ ...err.datasetId ? { datasetId: err.datasetId } : {},
104
+ ...err.minPassRate !== void 0 ? { minPassRate: err.minPassRate } : {},
105
+ ...err.passRate !== void 0 ? { passRate: err.passRate } : {},
106
+ ...err.runId ? { runId: err.runId } : {}
107
+ }
108
+ };
109
+ }
73
110
  if (err instanceof PersonaApiError) {
74
111
  return {
75
112
  status: err.statusCode,
@@ -101,6 +138,7 @@ function errorToResponse(err, mode) {
101
138
  error: {
102
139
  code: mapped.code,
103
140
  message: mapped.message,
141
+ ...mapped.details !== void 0 ? { details: mapped.details } : {},
104
142
  ...mapped.detail !== void 0 ? { detail: mapped.detail } : {}
105
143
  }
106
144
  })
@@ -131,7 +169,7 @@ function evictStaleRuns(runs, now, graceMs = DEFAULT_RUN_GRACE_MS, maxTracked =
131
169
  }
132
170
 
133
171
  // src/version.ts
134
- var RUNTIME_VERSION = "0.22.0";
172
+ var RUNTIME_VERSION = "0.23.0";
135
173
 
136
174
  // src/routes/health.ts
137
175
  var alwaysOnCapabilities = {
@@ -141,15 +179,61 @@ var alwaysOnCapabilities = {
141
179
  files: true,
142
180
  memory: true,
143
181
  mcpOAuth: true,
144
- workflows: true
145
- };
182
+ workflows: true,
183
+ voice: true,
184
+ feedback: true
185
+ };
186
+ var CAPABILITY_SCOPES = {
187
+ agentsWrite: ["*"],
188
+ mcps: ["*"],
189
+ providers: ["*"],
190
+ skills: ["*"],
191
+ knowledge: ["*"],
192
+ stores: ["*"],
193
+ rcpSources: ["*"],
194
+ auditLogs: ["*"],
195
+ architect: ["*"],
196
+ workflowsWrite: ["*"],
197
+ secrets: ["*"],
198
+ restToolSources: ["*"],
199
+ messages: ["agent:invoke"],
200
+ deploy: ["deploy:write"],
201
+ traces: ["traces:read", "agent:invoke"],
202
+ evals: ["eval:run"],
203
+ observe: ["traces:read"]
204
+ };
205
+ function scopeWarnings(scopes, capabilities) {
206
+ if (!scopes || scopes.includes("*")) return [];
207
+ const has = (s) => scopes.includes(s);
208
+ const warnings = [];
209
+ if (!has("agent:invoke")) {
210
+ warnings.push(
211
+ 'The API key lacks "agent:invoke": chat, threads, files, memory and voice will fail.'
212
+ );
213
+ }
214
+ for (const [cap, needed] of Object.entries(CAPABILITY_SCOPES)) {
215
+ if (!capabilities[cap]) continue;
216
+ const missing = needed.filter((s) => !has(s));
217
+ if (missing.length) {
218
+ warnings.push(
219
+ `capabilities.${cap} is on but the API key lacks ${missing.map((s) => `"${s}"`).join(" and ")}.`
220
+ );
221
+ }
222
+ }
223
+ return warnings;
224
+ }
146
225
  var healthRoute = async (_request, ctx) => {
226
+ let principal;
147
227
  try {
148
- await ctx.client.whoami();
228
+ principal = await ctx.client.whoami();
149
229
  } catch (err) {
150
230
  const failed = errorToResponse(err, ctx.mode);
151
231
  return { ...failed, status: 503 };
152
232
  }
233
+ const raw = principal?.scopes;
234
+ const scopes = Array.isArray(raw) ? raw.filter((s) => typeof s === "string") : void 0;
235
+ const warnings = scopeWarnings(scopes, ctx.capabilities);
236
+ for (const w of warnings) ctx.logger.warn(w);
153
237
  return {
154
238
  kind: "buffered",
155
239
  status: 200,
@@ -157,7 +241,9 @@ var healthRoute = async (_request, ctx) => {
157
241
  body: JSON.stringify({
158
242
  status: "ok",
159
243
  version: RUNTIME_VERSION,
160
- capabilities: { ...alwaysOnCapabilities, ...ctx.capabilities }
244
+ capabilities: { ...alwaysOnCapabilities, ...ctx.capabilities },
245
+ ...scopes ? { scopes } : {},
246
+ ...warnings.length ? { warnings } : {}
161
247
  })
162
248
  };
163
249
  };
@@ -248,6 +334,54 @@ function validateTurnContext(value) {
248
334
  }
249
335
  return context;
250
336
  }
337
+ var ENVIRONMENTS = ["development", "production"];
338
+ function clientVersionFields(body) {
339
+ const out = {};
340
+ if (body.environment !== void 0 && body.environment !== null) {
341
+ if (!ENVIRONMENTS.includes(body.environment)) {
342
+ throw new RuntimeHttpError(
343
+ 400,
344
+ "INVALID_REQUEST",
345
+ '"environment" must be "development" or "production".'
346
+ );
347
+ }
348
+ out.environment = body.environment;
349
+ }
350
+ if (body.agentVersion !== void 0 && body.agentVersion !== null) {
351
+ const v = body.agentVersion;
352
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 1) {
353
+ throw new RuntimeHttpError(
354
+ 400,
355
+ "INVALID_REQUEST",
356
+ '"agentVersion" must be a positive integer.'
357
+ );
358
+ }
359
+ out.agentVersion = v;
360
+ }
361
+ return out;
362
+ }
363
+ async function resolveRunVersion(body, ctx, selection) {
364
+ const { selectVersion, defaultEnvironment, allowClientVersionSelection } = ctx.versioning;
365
+ if (allowClientVersionSelection) {
366
+ const fromClient = clientVersionFields(body);
367
+ if (fromClient.environment || fromClient.agentVersion) return fromClient;
368
+ }
369
+ if (selectVersion) {
370
+ const picked = await selectVersion(selection);
371
+ if (picked && (picked.environment || picked.agentVersion)) {
372
+ return {
373
+ ...picked.environment ? { environment: picked.environment } : {},
374
+ ...picked.agentVersion ? { agentVersion: picked.agentVersion } : {}
375
+ };
376
+ }
377
+ }
378
+ return defaultEnvironment ? { environment: defaultEnvironment } : {};
379
+ }
380
+ function incomingTraceparent(headers, enabled) {
381
+ if (!enabled) return void 0;
382
+ const value = headers.traceparent ?? headers.Traceparent;
383
+ 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;
384
+ }
251
385
 
252
386
  // src/routes/restToolsManifest.ts
253
387
  var restToolsManifestRoute = async (request, ctx) => {
@@ -306,6 +440,8 @@ function newAccumulator() {
306
440
  interrupted: false,
307
441
  erroredInBand: false,
308
442
  threadCreateFired: false,
443
+ personaRunId: void 0,
444
+ threadId: void 0,
309
445
  toolNames: /* @__PURE__ */ new Map(),
310
446
  nodeMeta: /* @__PURE__ */ new Map()
311
447
  };
@@ -315,6 +451,8 @@ async function processEvent(event, acc, runCtx, hooks) {
315
451
  switch (e.type) {
316
452
  case EventType.RUN_STARTED: {
317
453
  const threadId = typeof e.threadId === "string" ? e.threadId : void 0;
454
+ if (!acc.personaRunId && typeof e.runId === "string") acc.personaRunId = e.runId;
455
+ if (!acc.threadId && threadId) acc.threadId = threadId;
318
456
  if (runCtx.kind === "chat" && runCtx.agentId && !acc.threadCreateFired && !runCtx.threadId && threadId) {
319
457
  acc.threadCreateFired = true;
320
458
  await hooks?.onThreadCreate?.({ userId: runCtx.userId, agentId: runCtx.agentId, threadId });
@@ -423,9 +561,9 @@ var RunDriver = class {
423
561
  constructor(runId, runCtx, source, hooks, mode) {
424
562
  this.runId = runId;
425
563
  this.runCtx = runCtx;
426
- this.firstFramePromise = new Promise((resolve, reject) => {
564
+ this.firstFramePromise = new Promise((resolve, reject2) => {
427
565
  this.resolveFirstFrame = resolve;
428
- this.rejectFirstFrame = reject;
566
+ this.rejectFirstFrame = reject2;
429
567
  });
430
568
  void this.pump(source, hooks, mode);
431
569
  }
@@ -460,7 +598,9 @@ var RunDriver = class {
460
598
  text: acc.text,
461
599
  eventCount: count,
462
600
  interrupted: acc.interrupted,
463
- erroredInBand: acc.erroredInBand
601
+ erroredInBand: acc.erroredInBand,
602
+ ...acc.personaRunId ? { personaRunId: acc.personaRunId } : {},
603
+ ...acc.threadId || this.runCtx.threadId ? { threadId: acc.threadId ?? this.runCtx.threadId } : {}
464
604
  };
465
605
  try {
466
606
  await hooks?.afterRun?.(this.runCtx, result);
@@ -510,14 +650,13 @@ var RunDriver = class {
510
650
  * original connection plus a reconnect that raced it) are supported.
511
651
  */
512
652
  subscribe(sinceSeq) {
513
- const buffered = this.frames.slice(Math.max(sinceSeq + 1, 0));
514
- const finishedAtSubscribeTime = this.finished;
653
+ const driver = this;
515
654
  const listeners = this.listeners;
516
655
  const doneListeners = this.doneListeners;
517
656
  return {
518
657
  [Symbol.asyncIterator]() {
519
- const queue = [...buffered];
520
- let live = !finishedAtSubscribeTime;
658
+ const queue = driver.frames.slice(Math.max(sinceSeq + 1, 0));
659
+ let live = !driver.finished;
521
660
  let wake = null;
522
661
  const onFrame = (_seq, frame) => {
523
662
  queue.push(frame);
@@ -657,12 +796,19 @@ var chatRoute = async (request, ctx) => {
657
796
  hasContext: !!body.context
658
797
  });
659
798
  const userId = request.userId;
799
+ const version = await resolveRunVersion(request.body, ctx, {
800
+ userId,
801
+ agentId: body.agentId,
802
+ kind: "chat"
803
+ });
804
+ const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
660
805
  const runCtx = {
661
806
  userId,
662
807
  kind: "chat",
663
808
  agentId: body.agentId,
664
809
  threadId: body.threadId,
665
- messages: body.messages
810
+ messages: body.messages,
811
+ ...version
666
812
  };
667
813
  logger.debug("beforeRun hook", { agentId: body.agentId, threadId: body.threadId });
668
814
  await ctx.hooks?.beforeRun?.(runCtx);
@@ -675,7 +821,9 @@ var chatRoute = async (request, ctx) => {
675
821
  contextOverride: body.contextOverride,
676
822
  context: body.context,
677
823
  tools: body.tools,
678
- attachments: body.attachments
824
+ attachments: body.attachments,
825
+ ...version,
826
+ ...traceparent ? { traceparent } : {}
679
827
  });
680
828
  const runId = crypto.randomUUID();
681
829
  logger.info("chat run created", {
@@ -874,7 +1022,13 @@ var createVoiceSession = async (request, ctx) => {
874
1022
  const contextOverride = typeof body.contextOverride === "string" && body.contextOverride ? body.contextOverride : void 0;
875
1023
  const context = validateTurnContext(body.context);
876
1024
  const tools = parseFrontendTools(body.tools);
1025
+ const version = await resolveRunVersion(body, ctx, {
1026
+ userId: request.userId,
1027
+ agentId,
1028
+ kind: "voice"
1029
+ });
877
1030
  const ticket = await ctx.client.voice.createSession(agentId, {
1031
+ ...version,
878
1032
  ...threadId ? { threadId } : {},
879
1033
  ...contextOverride ? { contextOverride } : {},
880
1034
  ...context ? { context } : {},
@@ -883,7 +1037,8 @@ var createVoiceSession = async (request, ctx) => {
883
1037
  await ctx.hooks?.onVoiceSessionCreate?.({
884
1038
  userId: request.userId,
885
1039
  agentId,
886
- threadId
1040
+ threadId,
1041
+ ...version
887
1042
  });
888
1043
  return json(201, ticket);
889
1044
  };
@@ -1717,15 +1872,15 @@ function resolveSignedUrl(request, publicUrl) {
1717
1872
  }
1718
1873
  async function computeTwilioSignature(authToken, url, params) {
1719
1874
  const payload = url + Object.keys(params).sort().map((key2) => `${key2}${params[key2]}`).join("");
1720
- const encoder = new TextEncoder();
1875
+ const encoder2 = new TextEncoder();
1721
1876
  const key = await crypto.subtle.importKey(
1722
1877
  "raw",
1723
- encoder.encode(authToken),
1878
+ encoder2.encode(authToken),
1724
1879
  { name: "HMAC", hash: "SHA-1" },
1725
1880
  false,
1726
1881
  ["sign"]
1727
1882
  );
1728
- const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
1883
+ const signature = await crypto.subtle.sign("HMAC", key, encoder2.encode(payload));
1729
1884
  const bytes = new Uint8Array(signature);
1730
1885
  let binary = "";
1731
1886
  for (const byte of bytes) binary += String.fromCharCode(byte);
@@ -1890,6 +2045,598 @@ var twilioMessageChannel = async (request, ctx) => {
1890
2045
  }
1891
2046
  };
1892
2047
 
2048
+ // src/routes/feedback.ts
2049
+ var MAX_COMMENT = 2e3;
2050
+ var MAX_TAGS = 10;
2051
+ var MAX_TAG = 50;
2052
+ var createFeedback = async (request, ctx) => {
2053
+ const body = requireBodyObject(request.body);
2054
+ const runId = requireStringField(body, "runId");
2055
+ if (body.score !== 1 && body.score !== -1) {
2056
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", '"score" must be 1 or -1.');
2057
+ }
2058
+ const score = body.score;
2059
+ let comment;
2060
+ if (body.comment !== void 0 && body.comment !== null) {
2061
+ if (typeof body.comment !== "string" || body.comment.length > MAX_COMMENT) {
2062
+ throw new RuntimeHttpError(
2063
+ 400,
2064
+ "INVALID_REQUEST",
2065
+ `"comment" must be a string of at most ${MAX_COMMENT} characters.`
2066
+ );
2067
+ }
2068
+ comment = body.comment.trim() || void 0;
2069
+ }
2070
+ let tags;
2071
+ if (body.tags !== void 0 && body.tags !== null) {
2072
+ if (!Array.isArray(body.tags) || body.tags.length > MAX_TAGS || !body.tags.every((t) => typeof t === "string" && t.length > 0 && t.length <= MAX_TAG)) {
2073
+ throw new RuntimeHttpError(
2074
+ 400,
2075
+ "INVALID_REQUEST",
2076
+ `"tags" must be up to ${MAX_TAGS} strings of at most ${MAX_TAG} characters.`
2077
+ );
2078
+ }
2079
+ tags = body.tags;
2080
+ }
2081
+ const result = await ctx.client.feedback.create({
2082
+ runId,
2083
+ score,
2084
+ ...comment ? { comment } : {},
2085
+ ...tags?.length ? { tags } : {}
2086
+ });
2087
+ await ctx.hooks?.onFeedback?.({
2088
+ userId: request.userId,
2089
+ runId,
2090
+ score,
2091
+ ...comment ? { comment } : {},
2092
+ ...tags?.length ? { tags } : {}
2093
+ });
2094
+ return json(200, result);
2095
+ };
2096
+
2097
+ // src/routes/webhooks.ts
2098
+ var encoder = new TextEncoder();
2099
+ var DEFAULT_SEEN_LIMIT = 1e3;
2100
+ function rawBodyBytes(request) {
2101
+ const raw = request.rawBody;
2102
+ if (raw === void 0 || raw === null) return null;
2103
+ return typeof raw === "string" ? encoder.encode(raw) : raw;
2104
+ }
2105
+ function parseSignature(header) {
2106
+ let timestamp = null;
2107
+ const signatures = [];
2108
+ for (const part of header.split(",")) {
2109
+ const eq = part.indexOf("=");
2110
+ if (eq <= 0) continue;
2111
+ const key = part.slice(0, eq).trim();
2112
+ const value = part.slice(eq + 1).trim();
2113
+ if (key === "t" && /^\d+$/.test(value)) timestamp = Number(value);
2114
+ else if (key === "v1" && /^[0-9a-f]{64}$/i.test(value)) signatures.push(value.toLowerCase());
2115
+ }
2116
+ return timestamp === null || signatures.length === 0 ? null : { timestamp, signatures };
2117
+ }
2118
+ function toHex(bytes) {
2119
+ return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
2120
+ }
2121
+ function safeEqual(a, b) {
2122
+ if (a.length !== b.length) return false;
2123
+ let diff = 0;
2124
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
2125
+ return diff === 0;
2126
+ }
2127
+ async function hmacHex(secret, message) {
2128
+ const key = await crypto.subtle.importKey(
2129
+ "raw",
2130
+ encoder.encode(secret),
2131
+ { name: "HMAC", hash: "SHA-256" },
2132
+ false,
2133
+ ["sign"]
2134
+ );
2135
+ return toHex(await crypto.subtle.sign("HMAC", key, message));
2136
+ }
2137
+ function concat(a, b) {
2138
+ const out = new Uint8Array(a.length + b.length);
2139
+ out.set(a, 0);
2140
+ out.set(b, a.length);
2141
+ return out;
2142
+ }
2143
+ var reject = (code, message) => json(400, { error: { code, message } });
2144
+ function createSeenSet(limit = DEFAULT_SEEN_LIMIT) {
2145
+ const ids = /* @__PURE__ */ new Set();
2146
+ return {
2147
+ has: (id) => ids.has(id),
2148
+ add(id) {
2149
+ ids.add(id);
2150
+ if (ids.size > limit) ids.delete(ids.values().next().value);
2151
+ }
2152
+ };
2153
+ }
2154
+ function createWebhookRoute() {
2155
+ const memory = createSeenSet();
2156
+ return async (request, ctx) => {
2157
+ const options = ctx.webhooks;
2158
+ if (!options) return json(404, { error: { code: "NOT_FOUND", message: "Not found" } });
2159
+ const logger = ctx.logger.child("webhooks");
2160
+ const body = rawBodyBytes(request);
2161
+ if (!body) {
2162
+ logger.error("webhook request has no rawBody: the adapter must pass the raw bytes");
2163
+ return json(500, {
2164
+ error: {
2165
+ code: "RUNTIME_MISCONFIGURED",
2166
+ message: "The server did not keep the raw request body for webhook verification."
2167
+ }
2168
+ });
2169
+ }
2170
+ const header = request.headers["x-persona-signature"];
2171
+ if (!header) return reject("MISSING_SIGNATURE", "Missing X-Persona-Signature header");
2172
+ const parsed = parseSignature(header);
2173
+ if (!parsed) return reject("INVALID_SIGNATURE", "Malformed X-Persona-Signature header");
2174
+ const expected = await hmacHex(
2175
+ options.secret,
2176
+ concat(encoder.encode(`${parsed.timestamp}.`), body)
2177
+ );
2178
+ if (!parsed.signatures.some((sig) => safeEqual(sig, expected))) {
2179
+ logger.warn("webhook signature does not match");
2180
+ return reject("INVALID_SIGNATURE", "Webhook signature does not match");
2181
+ }
2182
+ const tolerance = options.toleranceSec ?? 300;
2183
+ if (tolerance > 0 && Math.abs(Math.floor(Date.now() / 1e3) - parsed.timestamp) > tolerance) {
2184
+ return reject("TIMESTAMP_OUT_OF_RANGE", "Webhook timestamp is outside the allowed tolerance");
2185
+ }
2186
+ let event;
2187
+ try {
2188
+ event = JSON.parse(new TextDecoder().decode(body));
2189
+ } catch {
2190
+ return reject("INVALID_PAYLOAD", "Webhook body is not valid JSON");
2191
+ }
2192
+ if (!event || typeof event !== "object" || typeof event.type !== "string") {
2193
+ return reject("INVALID_PAYLOAD", "Webhook body is not a Persona event");
2194
+ }
2195
+ const deliveryId = request.headers["x-persona-delivery"];
2196
+ if (deliveryId) {
2197
+ const store = options.dedupe ?? memory;
2198
+ if (await store.has(deliveryId)) {
2199
+ logger.debug("duplicate webhook delivery ignored", { deliveryId, type: event.type });
2200
+ return json(200, { received: true, duplicate: true });
2201
+ }
2202
+ }
2203
+ const type = event.type;
2204
+ const handlers = [options.on[type], options.on["*"]].filter(
2205
+ (h) => typeof h === "function"
2206
+ );
2207
+ try {
2208
+ for (const handler of handlers) await handler(event, { id: deliveryId, type });
2209
+ } catch (err) {
2210
+ logger.error("webhook handler failed", {
2211
+ type,
2212
+ deliveryId,
2213
+ error: err instanceof Error ? err.message : String(err)
2214
+ });
2215
+ return json(500, { error: { code: "HANDLER_FAILED", message: "Webhook handler failed" } });
2216
+ }
2217
+ if (deliveryId) await (options.dedupe ?? memory).add(deliveryId);
2218
+ logger.info("webhook handled", { type, deliveryId, handlers: handlers.length });
2219
+ return json(200, { received: true });
2220
+ };
2221
+ }
2222
+
2223
+ // src/routes/secrets.ts
2224
+ var listSecrets = async (_request, ctx) => {
2225
+ return json(200, await ctx.client.secrets.list());
2226
+ };
2227
+ var createSecret = async (request, ctx) => {
2228
+ const body = requireBodyObject(request.body);
2229
+ const secret = await ctx.client.secrets.create(
2230
+ {
2231
+ label: requireStringField(body, "label"),
2232
+ value: requireStringField(body, "value")
2233
+ },
2234
+ request.headers["idempotency-key"]
2235
+ );
2236
+ return json(201, secret);
2237
+ };
2238
+ var getSecret = async (_request, ctx) => {
2239
+ return json(200, await ctx.client.secrets.get(requireParam(ctx.params, "id")));
2240
+ };
2241
+ var updateSecret = async (request, ctx) => {
2242
+ const body = request.body ?? {};
2243
+ return json(200, await ctx.client.secrets.update(requireParam(ctx.params, "id"), body));
2244
+ };
2245
+ var deleteSecret = async (_request, ctx) => {
2246
+ await ctx.client.secrets.delete(requireParam(ctx.params, "id"));
2247
+ return noContent();
2248
+ };
2249
+ var getSecretUsage = async (_request, ctx) => {
2250
+ return json(200, await ctx.client.secrets.getUsage(requireParam(ctx.params, "id")));
2251
+ };
2252
+ var bulkDeleteSecrets = async (request, ctx) => {
2253
+ const body = requireBodyObject(request.body);
2254
+ const ids = Array.isArray(body.ids) ? body.ids : [];
2255
+ return json(200, await ctx.client.secrets.bulkDelete(ids));
2256
+ };
2257
+
2258
+ // src/routes/restToolSources.ts
2259
+ var listRestToolSources = async (request, ctx) => {
2260
+ const items = await ctx.client.restToolSources.list({
2261
+ page: toInt(request.query.page),
2262
+ limit: toInt(request.query.limit),
2263
+ search: request.query.search
2264
+ });
2265
+ return json(200, items);
2266
+ };
2267
+ var createRestToolSource = async (request, ctx) => {
2268
+ const body = requireBodyObject(request.body);
2269
+ const source = await ctx.client.restToolSources.create(body, request.headers["idempotency-key"]);
2270
+ return json(201, source);
2271
+ };
2272
+ var getRestToolSource = async (_request, ctx) => {
2273
+ return json(200, await ctx.client.restToolSources.get(requireParam(ctx.params, "id")));
2274
+ };
2275
+ var updateRestToolSource = async (request, ctx) => {
2276
+ const body = request.body ?? {};
2277
+ return json(200, await ctx.client.restToolSources.update(requireParam(ctx.params, "id"), body));
2278
+ };
2279
+ var deleteRestToolSource = async (_request, ctx) => {
2280
+ await ctx.client.restToolSources.delete(requireParam(ctx.params, "id"));
2281
+ return noContent();
2282
+ };
2283
+ var getRestToolSourceUsage = async (_request, ctx) => {
2284
+ return json(200, await ctx.client.restToolSources.getUsage(requireParam(ctx.params, "id")));
2285
+ };
2286
+ var testRestToolSource = async (_request, ctx) => {
2287
+ return json(200, await ctx.client.restToolSources.testConnection(requireParam(ctx.params, "id")));
2288
+ };
2289
+ var bulkDeleteRestToolSources = async (request, ctx) => {
2290
+ const body = requireBodyObject(request.body);
2291
+ const ids = Array.isArray(body.ids) ? body.ids : [];
2292
+ return json(200, await ctx.client.restToolSources.bulkDelete(ids));
2293
+ };
2294
+
2295
+ // src/routes/deploy.ts
2296
+ function environmentParam(ctx) {
2297
+ const env = requireParam(ctx.params, "environment");
2298
+ if (env !== "development" && env !== "production") {
2299
+ throw new RuntimeHttpError(
2300
+ 400,
2301
+ "INVALID_REQUEST",
2302
+ 'environment must be "development" or "production".'
2303
+ );
2304
+ }
2305
+ return env;
2306
+ }
2307
+ function positiveInt(raw, name) {
2308
+ const v = Number(raw);
2309
+ if (!Number.isInteger(v) || v < 1) {
2310
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", `${name} must be a positive integer.`);
2311
+ }
2312
+ return v;
2313
+ }
2314
+ function versionOrDraft(raw, name) {
2315
+ if (!raw || raw === "draft") return "draft";
2316
+ return positiveInt(raw, name);
2317
+ }
2318
+ async function audit(request, ctx, action, target) {
2319
+ await ctx.hooks?.onAdminAction?.({
2320
+ userId: request.userId,
2321
+ capability: "deploy",
2322
+ action,
2323
+ target
2324
+ });
2325
+ }
2326
+ var listAgentVersions = async (request, ctx) => {
2327
+ return json(
2328
+ 200,
2329
+ await ctx.projectClient().agents.versions.list(requireParam(ctx.params, "id"), {
2330
+ page: toInt(request.query.page),
2331
+ limit: toInt(request.query.limit)
2332
+ })
2333
+ );
2334
+ };
2335
+ var publishAgentVersion = async (request, ctx) => {
2336
+ const agentId = requireParam(ctx.params, "id");
2337
+ const body = request.body ?? {};
2338
+ const changelog = typeof body.changelog === "string" ? body.changelog : void 0;
2339
+ const version = await ctx.projectClient().agents.versions.publish(agentId, changelog ? { changelog } : {});
2340
+ await audit(request, ctx, "agent.version.publish", { agentId, version: version.version });
2341
+ return json(201, version);
2342
+ };
2343
+ var agentVersionStats = async (request, ctx) => {
2344
+ const source = request.query.source;
2345
+ return json(
2346
+ 200,
2347
+ await ctx.projectClient().agents.versions.stats(requireParam(ctx.params, "id"), {
2348
+ days: toInt(request.query.days),
2349
+ ...source ? { source } : {}
2350
+ })
2351
+ );
2352
+ };
2353
+ var getAgentVersion = async (_request, ctx) => {
2354
+ const version = positiveInt(requireParam(ctx.params, "version"), "version");
2355
+ return json(
2356
+ 200,
2357
+ await ctx.projectClient().agents.versions.get(requireParam(ctx.params, "id"), version)
2358
+ );
2359
+ };
2360
+ var diffAgentVersion = async (request, ctx) => {
2361
+ const from = versionOrDraft(requireParam(ctx.params, "version"), "version");
2362
+ const to = versionOrDraft(request.query.against, '"against"');
2363
+ return json(
2364
+ 200,
2365
+ await ctx.projectClient().agents.versions.diff(requireParam(ctx.params, "id"), from, to)
2366
+ );
2367
+ };
2368
+ var getAgentDeployments = async (_request, ctx) => {
2369
+ return json(
2370
+ 200,
2371
+ await ctx.projectClient().agents.versions.deployments(requireParam(ctx.params, "id"))
2372
+ );
2373
+ };
2374
+ var setAgentServePolicy = async (request, ctx) => {
2375
+ const agentId = requireParam(ctx.params, "id");
2376
+ const body = requireBodyObject(request.body);
2377
+ if (typeof body.serveDeployedVersion !== "boolean") {
2378
+ throw new RuntimeHttpError(
2379
+ 400,
2380
+ "INVALID_REQUEST",
2381
+ '"serveDeployedVersion" is required and must be a boolean.'
2382
+ );
2383
+ }
2384
+ const result = await ctx.projectClient().agents.versions.setServePolicy(agentId, body.serveDeployedVersion);
2385
+ await audit(request, ctx, "agent.serve_policy.set", { agentId, ...result });
2386
+ return json(200, result);
2387
+ };
2388
+ var deployAgentVersion = async (request, ctx) => {
2389
+ const agentId = requireParam(ctx.params, "id");
2390
+ const environment = environmentParam(ctx);
2391
+ const body = request.body ?? {};
2392
+ let version;
2393
+ if (body.version !== void 0 && body.version !== null) {
2394
+ if (typeof body.version !== "number" || !Number.isInteger(body.version) || body.version < 1) {
2395
+ throw new RuntimeHttpError(400, "INVALID_REQUEST", '"version" must be a positive integer.');
2396
+ }
2397
+ version = body.version;
2398
+ }
2399
+ const deployment = await ctx.projectClient().agents.versions.deploy(agentId, { environment, ...version ? { version } : {} });
2400
+ await audit(request, ctx, "agent.version.deploy", {
2401
+ agentId,
2402
+ environment,
2403
+ version: deployment.version
2404
+ });
2405
+ return json(200, deployment);
2406
+ };
2407
+ var rollbackAgentDeployment = async (request, ctx) => {
2408
+ const agentId = requireParam(ctx.params, "id");
2409
+ const environment = environmentParam(ctx);
2410
+ const deployment = await ctx.projectClient().agents.versions.rollback(agentId, environment);
2411
+ await audit(request, ctx, "agent.version.rollback", {
2412
+ agentId,
2413
+ environment,
2414
+ version: deployment.version
2415
+ });
2416
+ return json(200, deployment);
2417
+ };
2418
+
2419
+ // src/routes/traces.ts
2420
+ var STRING_FILTERS = [
2421
+ "agentId",
2422
+ "threadId",
2423
+ "runId",
2424
+ "status",
2425
+ "source",
2426
+ "channel",
2427
+ "environment",
2428
+ "feedback",
2429
+ "from",
2430
+ "to"
2431
+ ];
2432
+ var listTraces = async (request, ctx) => {
2433
+ const params = {
2434
+ page: toInt(request.query.page),
2435
+ limit: toInt(request.query.limit),
2436
+ agentVersion: toInt(request.query.agentVersion)
2437
+ };
2438
+ for (const key of STRING_FILTERS) {
2439
+ const value = request.query[key];
2440
+ if (value) params[key] = value;
2441
+ }
2442
+ return json(200, await ctx.projectClient().traces.list(params));
2443
+ };
2444
+ var getTrace = async (request, ctx) => {
2445
+ return json(
2446
+ 200,
2447
+ await ctx.projectClient().traces.get(requireParam(ctx.params, "id"), {
2448
+ spans: request.query.spans !== "false"
2449
+ })
2450
+ );
2451
+ };
2452
+ var getTraceFeedback = async (_request, ctx) => {
2453
+ return json(200, await ctx.projectClient().traces.feedback(requireParam(ctx.params, "id")));
2454
+ };
2455
+ var getReplayPlan = async (_request, ctx) => {
2456
+ return json(200, await ctx.projectClient().traces.replayPlan(requireParam(ctx.params, "id")));
2457
+ };
2458
+ var replayTrace = async (request, ctx) => {
2459
+ const traceId = requireParam(ctx.params, "id");
2460
+ const options = request.body ?? {};
2461
+ const result = await ctx.projectClient().traces.replay(traceId, options);
2462
+ await ctx.hooks?.onAdminAction?.({
2463
+ userId: request.userId,
2464
+ capability: "traces",
2465
+ action: "trace.replay",
2466
+ target: { traceId, replayTraceId: result.traceId }
2467
+ });
2468
+ return json(201, result);
2469
+ };
2470
+ var listTraceReplays = async (_request, ctx) => {
2471
+ return json(200, await ctx.projectClient().traces.replays(requireParam(ctx.params, "id")));
2472
+ };
2473
+
2474
+ // src/routes/evals.ts
2475
+ async function audit2(request, ctx, action, target) {
2476
+ await ctx.hooks?.onAdminAction?.({
2477
+ userId: request.userId,
2478
+ capability: "evals",
2479
+ action,
2480
+ target
2481
+ });
2482
+ }
2483
+ var listDatasets = async (_request, ctx) => {
2484
+ return json(200, await ctx.projectClient().datasets.list());
2485
+ };
2486
+ var createDataset = async (request, ctx) => {
2487
+ const body = requireBodyObject(request.body);
2488
+ const dataset = await ctx.projectClient().datasets.create(body);
2489
+ await audit2(request, ctx, "dataset.create", { datasetId: dataset._id });
2490
+ return json(201, dataset);
2491
+ };
2492
+ var getDataset = async (_request, ctx) => {
2493
+ return json(200, await ctx.projectClient().datasets.get(requireParam(ctx.params, "id")));
2494
+ };
2495
+ var updateDataset = async (request, ctx) => {
2496
+ const body = requireBodyObject(request.body);
2497
+ return json(200, await ctx.projectClient().datasets.update(requireParam(ctx.params, "id"), body));
2498
+ };
2499
+ var deleteDataset = async (request, ctx) => {
2500
+ const datasetId = requireParam(ctx.params, "id");
2501
+ const result = await ctx.projectClient().datasets.delete(datasetId);
2502
+ await audit2(request, ctx, "dataset.delete", { datasetId });
2503
+ return json(200, result);
2504
+ };
2505
+ var listDatasetItems = async (request, ctx) => {
2506
+ return json(
2507
+ 200,
2508
+ await ctx.projectClient().datasets.items.list(requireParam(ctx.params, "id"), {
2509
+ page: toInt(request.query.page),
2510
+ limit: toInt(request.query.limit)
2511
+ })
2512
+ );
2513
+ };
2514
+ var addDatasetItems = async (request, ctx) => {
2515
+ const datasetId = requireParam(ctx.params, "id");
2516
+ const body = requireBodyObject(request.body);
2517
+ let cases;
2518
+ if (typeof body.text === "string") cases = body.text;
2519
+ else if (Array.isArray(body.items)) cases = body.items;
2520
+ else {
2521
+ throw new RuntimeHttpError(
2522
+ 400,
2523
+ "INVALID_REQUEST",
2524
+ 'Send "items" (an array of cases) or "text" (CSV / JSON).'
2525
+ );
2526
+ }
2527
+ try {
2528
+ return json(201, await ctx.projectClient().datasets.import(datasetId, cases));
2529
+ } catch (err) {
2530
+ if (err instanceof Error && err.message.startsWith("parseDatasetCases:")) {
2531
+ throw new RuntimeHttpError(
2532
+ 400,
2533
+ "INVALID_REQUEST",
2534
+ err.message.replace("parseDatasetCases: ", "")
2535
+ );
2536
+ }
2537
+ throw err;
2538
+ }
2539
+ };
2540
+ var addDatasetItemFromTrace = async (request, ctx) => {
2541
+ const body = requireBodyObject(request.body);
2542
+ const traceId = requireStringField(body, "traceId");
2543
+ const options = body.expected === null ? { expected: null } : typeof body.expected === "string" ? { expected: body.expected } : {};
2544
+ return json(
2545
+ 201,
2546
+ await ctx.projectClient().datasets.items.addFromTrace(requireParam(ctx.params, "id"), traceId, options)
2547
+ );
2548
+ };
2549
+ var updateDatasetItem = async (request, ctx) => {
2550
+ const body = requireBodyObject(request.body);
2551
+ return json(
2552
+ 200,
2553
+ await ctx.projectClient().datasets.items.update(
2554
+ requireParam(ctx.params, "id"),
2555
+ requireParam(ctx.params, "itemId"),
2556
+ body
2557
+ )
2558
+ );
2559
+ };
2560
+ var deleteDatasetItem = async (_request, ctx) => {
2561
+ return json(
2562
+ 200,
2563
+ await ctx.projectClient().datasets.items.delete(requireParam(ctx.params, "id"), requireParam(ctx.params, "itemId"))
2564
+ );
2565
+ };
2566
+ var listEvalRuns = async (request, ctx) => {
2567
+ return json(
2568
+ 200,
2569
+ await ctx.projectClient().evals.list({
2570
+ page: toInt(request.query.page),
2571
+ limit: toInt(request.query.limit),
2572
+ ...request.query.agentId ? { agentId: request.query.agentId } : {},
2573
+ ...request.query.datasetId ? { datasetId: request.query.datasetId } : {}
2574
+ })
2575
+ );
2576
+ };
2577
+ var startEvalRun = async (request, ctx) => {
2578
+ const body = requireBodyObject(request.body);
2579
+ const run = await ctx.projectClient().evals.create(body);
2580
+ await audit2(request, ctx, "eval_run.start", {
2581
+ runId: run.id,
2582
+ agentId: run.agentId,
2583
+ datasetId: run.datasetId
2584
+ });
2585
+ return json(201, run);
2586
+ };
2587
+ var compareEvalRuns = async (request, ctx) => {
2588
+ const { a, b } = request.query;
2589
+ if (!a || !b) {
2590
+ throw new RuntimeHttpError(
2591
+ 400,
2592
+ "INVALID_REQUEST",
2593
+ '"a" and "b" query parameters are required.'
2594
+ );
2595
+ }
2596
+ return json(200, await ctx.projectClient().evals.compare(a, b));
2597
+ };
2598
+ var getEvalRun = async (_request, ctx) => {
2599
+ return json(200, await ctx.projectClient().evals.get(requireParam(ctx.params, "id")));
2600
+ };
2601
+ var deleteEvalRun = async (request, ctx) => {
2602
+ const runId = requireParam(ctx.params, "id");
2603
+ const result = await ctx.projectClient().evals.delete(runId);
2604
+ await audit2(request, ctx, "eval_run.delete", { runId });
2605
+ return json(200, result);
2606
+ };
2607
+ var listEvalResults = async (request, ctx) => {
2608
+ return json(
2609
+ 200,
2610
+ await ctx.projectClient().evals.results(requireParam(ctx.params, "id"), {
2611
+ page: toInt(request.query.page),
2612
+ limit: toInt(request.query.limit),
2613
+ ...request.query.failed === "true" ? { failed: true } : {}
2614
+ })
2615
+ );
2616
+ };
2617
+ var cancelEvalRun = async (request, ctx) => {
2618
+ const runId = requireParam(ctx.params, "id");
2619
+ const run = await ctx.projectClient().evals.cancel(runId);
2620
+ await audit2(request, ctx, "eval_run.cancel", { runId });
2621
+ return json(200, run);
2622
+ };
2623
+
2624
+ // src/routes/observe.ts
2625
+ var getBudget = async (_request, ctx) => {
2626
+ return json(200, await ctx.projectClient().observe.budget());
2627
+ };
2628
+ var getUsage = async (request, ctx) => {
2629
+ const source = request.query.source;
2630
+ return json(
2631
+ 200,
2632
+ await ctx.projectClient().observe.usage({
2633
+ days: toInt(request.query.days),
2634
+ ...request.query.agentId ? { agentId: request.query.agentId } : {},
2635
+ ...source ? { source } : {}
2636
+ })
2637
+ );
2638
+ };
2639
+
1893
2640
  // src/runtime.ts
1894
2641
  function resolveCapabilities(capabilities) {
1895
2642
  return {
@@ -1903,7 +2650,13 @@ function resolveCapabilities(capabilities) {
1903
2650
  auditLogs: capabilities?.auditLogs ?? false,
1904
2651
  architect: capabilities?.architect ?? false,
1905
2652
  workflowsWrite: capabilities?.workflowsWrite ?? false,
1906
- messages: capabilities?.messages ?? false
2653
+ messages: capabilities?.messages ?? false,
2654
+ secrets: capabilities?.secrets ?? false,
2655
+ restToolSources: capabilities?.restToolSources ?? false,
2656
+ deploy: capabilities?.deploy ?? false,
2657
+ traces: capabilities?.traces ?? false,
2658
+ evals: capabilities?.evals ?? false,
2659
+ observe: capabilities?.observe ?? false
1907
2660
  };
1908
2661
  }
1909
2662
  function buildRoutes(capabilities) {
@@ -1925,6 +2678,9 @@ function buildRoutes(capabilities) {
1925
2678
  // (powered by Gemini Live). Mints a ticket only; the host's own
1926
2679
  // frontend opens the WebSocket directly, this runtime never proxies it.
1927
2680
  { method: "POST", pattern: ["voice", "sessions"], handler: createVoiceSession },
2681
+ // Feedback — always on: an end user rates one of their own runs
2682
+ // (Persona refuses ratings on anyone else's).
2683
+ { method: "POST", pattern: ["feedback"], handler: createFeedback },
1928
2684
  // Agents — read-only discovery always on; write ops behind agentsWrite.
1929
2685
  { method: "GET", pattern: ["agents"], handler: listAgents },
1930
2686
  // Files — always on, end-user-scoped uploads.
@@ -2129,8 +2885,128 @@ function buildRoutes(capabilities) {
2129
2885
  { method: "GET", pattern: ["workflows", "runs", ":runId"], handler: getWorkflowRun }
2130
2886
  );
2131
2887
  }
2888
+ if (capabilities.secrets) {
2889
+ routes.push(
2890
+ { method: "GET", pattern: ["secrets"], handler: listSecrets },
2891
+ { method: "POST", pattern: ["secrets"], handler: createSecret },
2892
+ { method: "POST", pattern: ["secrets", "bulk-delete"], handler: bulkDeleteSecrets },
2893
+ { method: "GET", pattern: ["secrets", ":id"], handler: getSecret },
2894
+ { method: "PATCH", pattern: ["secrets", ":id"], handler: updateSecret },
2895
+ { method: "DELETE", pattern: ["secrets", ":id"], handler: deleteSecret },
2896
+ { method: "GET", pattern: ["secrets", ":id", "usage"], handler: getSecretUsage }
2897
+ );
2898
+ }
2899
+ if (capabilities.restToolSources) {
2900
+ routes.push(
2901
+ { method: "GET", pattern: ["rest-tool-sources"], handler: listRestToolSources },
2902
+ { method: "POST", pattern: ["rest-tool-sources"], handler: createRestToolSource },
2903
+ {
2904
+ method: "POST",
2905
+ pattern: ["rest-tool-sources", "bulk-delete"],
2906
+ handler: bulkDeleteRestToolSources
2907
+ },
2908
+ { method: "GET", pattern: ["rest-tool-sources", ":id"], handler: getRestToolSource },
2909
+ { method: "PATCH", pattern: ["rest-tool-sources", ":id"], handler: updateRestToolSource },
2910
+ { method: "DELETE", pattern: ["rest-tool-sources", ":id"], handler: deleteRestToolSource },
2911
+ {
2912
+ method: "GET",
2913
+ pattern: ["rest-tool-sources", ":id", "usage"],
2914
+ handler: getRestToolSourceUsage
2915
+ },
2916
+ { method: "POST", pattern: ["rest-tool-sources", ":id", "test"], handler: testRestToolSource }
2917
+ );
2918
+ }
2919
+ if (capabilities.deploy) {
2920
+ routes.push(
2921
+ { method: "GET", pattern: ["agents", ":id", "versions"], handler: listAgentVersions },
2922
+ { method: "POST", pattern: ["agents", ":id", "versions"], handler: publishAgentVersion },
2923
+ // Before `versions/:version`, which would otherwise capture "stats".
2924
+ {
2925
+ method: "GET",
2926
+ pattern: ["agents", ":id", "versions", "stats"],
2927
+ handler: agentVersionStats
2928
+ },
2929
+ {
2930
+ method: "GET",
2931
+ pattern: ["agents", ":id", "versions", ":version"],
2932
+ handler: getAgentVersion
2933
+ },
2934
+ {
2935
+ method: "GET",
2936
+ pattern: ["agents", ":id", "versions", ":version", "diff"],
2937
+ handler: diffAgentVersion
2938
+ },
2939
+ { method: "GET", pattern: ["agents", ":id", "deployments"], handler: getAgentDeployments },
2940
+ {
2941
+ method: "PUT",
2942
+ pattern: ["agents", ":id", "deployments", "policy"],
2943
+ handler: setAgentServePolicy
2944
+ },
2945
+ {
2946
+ method: "POST",
2947
+ pattern: ["agents", ":id", "deployments", ":environment"],
2948
+ handler: deployAgentVersion
2949
+ },
2950
+ {
2951
+ method: "POST",
2952
+ pattern: ["agents", ":id", "deployments", ":environment", "rollback"],
2953
+ handler: rollbackAgentDeployment
2954
+ }
2955
+ );
2956
+ }
2957
+ if (capabilities.traces) {
2958
+ routes.push(
2959
+ { method: "GET", pattern: ["traces"], handler: listTraces },
2960
+ { method: "GET", pattern: ["traces", ":id"], handler: getTrace },
2961
+ { method: "GET", pattern: ["traces", ":id", "feedback"], handler: getTraceFeedback },
2962
+ { method: "GET", pattern: ["traces", ":id", "replay"], handler: getReplayPlan },
2963
+ { method: "POST", pattern: ["traces", ":id", "replay"], handler: replayTrace },
2964
+ { method: "GET", pattern: ["traces", ":id", "replays"], handler: listTraceReplays }
2965
+ );
2966
+ }
2967
+ if (capabilities.evals) {
2968
+ routes.push(
2969
+ { method: "GET", pattern: ["datasets"], handler: listDatasets },
2970
+ { method: "POST", pattern: ["datasets"], handler: createDataset },
2971
+ { method: "GET", pattern: ["datasets", ":id"], handler: getDataset },
2972
+ { method: "PATCH", pattern: ["datasets", ":id"], handler: updateDataset },
2973
+ { method: "DELETE", pattern: ["datasets", ":id"], handler: deleteDataset },
2974
+ { method: "GET", pattern: ["datasets", ":id", "items"], handler: listDatasetItems },
2975
+ { method: "POST", pattern: ["datasets", ":id", "items"], handler: addDatasetItems },
2976
+ {
2977
+ method: "POST",
2978
+ pattern: ["datasets", ":id", "items", "from-trace"],
2979
+ handler: addDatasetItemFromTrace
2980
+ },
2981
+ {
2982
+ method: "PATCH",
2983
+ pattern: ["datasets", ":id", "items", ":itemId"],
2984
+ handler: updateDatasetItem
2985
+ },
2986
+ {
2987
+ method: "DELETE",
2988
+ pattern: ["datasets", ":id", "items", ":itemId"],
2989
+ handler: deleteDatasetItem
2990
+ },
2991
+ { method: "GET", pattern: ["eval-runs"], handler: listEvalRuns },
2992
+ { method: "POST", pattern: ["eval-runs"], handler: startEvalRun },
2993
+ // Before `eval-runs/:id`, which would otherwise capture "compare".
2994
+ { method: "GET", pattern: ["eval-runs", "compare"], handler: compareEvalRuns },
2995
+ { method: "GET", pattern: ["eval-runs", ":id"], handler: getEvalRun },
2996
+ { method: "DELETE", pattern: ["eval-runs", ":id"], handler: deleteEvalRun },
2997
+ { method: "GET", pattern: ["eval-runs", ":id", "results"], handler: listEvalResults },
2998
+ { method: "POST", pattern: ["eval-runs", ":id", "cancel"], handler: cancelEvalRun }
2999
+ );
3000
+ }
3001
+ if (capabilities.observe) {
3002
+ routes.push(
3003
+ { method: "GET", pattern: ["observe", "budget"], handler: getBudget },
3004
+ { method: "GET", pattern: ["observe", "usage"], handler: getUsage }
3005
+ );
3006
+ }
2132
3007
  return routes;
2133
3008
  }
3009
+ var ADMIN_CAPABILITIES = ["deploy", "traces", "evals", "observe"];
2134
3010
  function resolveMode(options) {
2135
3011
  if (options.mode) return options.mode;
2136
3012
  return process.env.NODE_ENV === "development" ? "development" : "production";
@@ -2177,6 +3053,15 @@ function createRuntime(options) {
2177
3053
  }
2178
3054
  );
2179
3055
  }
3056
+ if (options.webhooks) {
3057
+ if (!options.webhooks.secret) throw new Error('createRuntime: "webhooks.secret" is required');
3058
+ routes.push({
3059
+ method: "POST",
3060
+ pattern: ["webhooks", "persona"],
3061
+ handler: createWebhookRoute(),
3062
+ requiresAuth: false
3063
+ });
3064
+ }
2180
3065
  const mode = resolveMode(options);
2181
3066
  const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
2182
3067
  const runGraceMs = options.runGraceMs ?? DEFAULT_RUN_GRACE_MS;
@@ -2195,6 +3080,18 @@ function createRuntime(options) {
2195
3080
  mountPath: options.mountPath ?? "",
2196
3081
  enabledCapabilities: enabledCaps.length ? enabledCaps : ["(core only)"]
2197
3082
  });
3083
+ const adminCaps = enabledCaps.filter((c) => ADMIN_CAPABILITIES.includes(c));
3084
+ if (adminCaps.length) {
3085
+ logger.warn(
3086
+ "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.",
3087
+ {
3088
+ capabilities: adminCaps,
3089
+ scopes: Object.fromEntries(
3090
+ adminCaps.map((c) => [c, CAPABILITY_SCOPES[c]])
3091
+ )
3092
+ }
3093
+ );
3094
+ }
2198
3095
  logger.trace("runtime config", {
2199
3096
  baseUrl: options.baseUrl,
2200
3097
  hasHooks: !!options.hooks,
@@ -2327,6 +3224,7 @@ function createRuntime(options) {
2327
3224
  }
2328
3225
  const resolvedRequest = { ...request, userId };
2329
3226
  const client = createClientForRequest(options, userId, logger);
3227
+ let projectClient;
2330
3228
  const routeLogger = logger.child("route");
2331
3229
  logger.debug("handler start", {
2332
3230
  method: request.method,
@@ -2345,7 +3243,15 @@ function createRuntime(options) {
2345
3243
  logger: routeLogger,
2346
3244
  restToolsManifest: options.restToolsManifest,
2347
3245
  rcpManifest: options.rcpManifest,
2348
- channels: options.channels
3246
+ channels: options.channels,
3247
+ projectClient: () => userId === null ? client : projectClient ??= createClientForRequest(options, null, logger),
3248
+ webhooks: options.webhooks,
3249
+ versioning: {
3250
+ selectVersion: options.selectVersion,
3251
+ defaultEnvironment: options.defaultEnvironment,
3252
+ allowClientVersionSelection: options.allowClientVersionSelection
3253
+ },
3254
+ forwardTraceparent: options.forwardTraceparent ?? true
2349
3255
  });
2350
3256
  const durationMs = Date.now() - startMs;
2351
3257
  logger.info("handle succeeded", {
@@ -2405,6 +3311,7 @@ function createRuntime(options) {
2405
3311
  };
2406
3312
  }
2407
3313
  export {
3314
+ CAPABILITY_SCOPES,
2408
3315
  RUNTIME_VERSION,
2409
3316
  RuntimeHttpError,
2410
3317
  createRuntime