@personaai/runtime 0.22.0 → 0.24.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/README.md +619 -453
- package/dist/index.cjs +955 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +150 -3
- package/dist/index.d.ts +150 -3
- package/dist/index.js +960 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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 {
|
|
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.
|
|
172
|
+
var RUNTIME_VERSION = "0.24.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,55 @@ function validateTurnContext(value) {
|
|
|
248
334
|
}
|
|
249
335
|
return context;
|
|
250
336
|
}
|
|
337
|
+
var ENVIRONMENTS = ["development", "production"];
|
|
338
|
+
function clientVersionFields(body, envField = "environment") {
|
|
339
|
+
const out = {};
|
|
340
|
+
const env = body[envField];
|
|
341
|
+
if (env !== void 0 && env !== null) {
|
|
342
|
+
if (!ENVIRONMENTS.includes(env)) {
|
|
343
|
+
throw new RuntimeHttpError(
|
|
344
|
+
400,
|
|
345
|
+
"INVALID_REQUEST",
|
|
346
|
+
`"${envField}" must be "development" or "production".`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
out.environment = env;
|
|
350
|
+
}
|
|
351
|
+
if (body.agentVersion !== void 0 && body.agentVersion !== null) {
|
|
352
|
+
const v = body.agentVersion;
|
|
353
|
+
if (typeof v !== "number" || !Number.isInteger(v) || v < 1) {
|
|
354
|
+
throw new RuntimeHttpError(
|
|
355
|
+
400,
|
|
356
|
+
"INVALID_REQUEST",
|
|
357
|
+
'"agentVersion" must be a positive integer.'
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
out.agentVersion = v;
|
|
361
|
+
}
|
|
362
|
+
return out;
|
|
363
|
+
}
|
|
364
|
+
async function resolveRunVersion(body, ctx, selection, envField = "environment") {
|
|
365
|
+
const { selectVersion, defaultEnvironment, allowClientVersionSelection } = ctx.versioning;
|
|
366
|
+
if (allowClientVersionSelection) {
|
|
367
|
+
const fromClient = clientVersionFields(body, envField);
|
|
368
|
+
if (fromClient.environment || fromClient.agentVersion) return fromClient;
|
|
369
|
+
}
|
|
370
|
+
if (selectVersion) {
|
|
371
|
+
const picked = await selectVersion(selection);
|
|
372
|
+
if (picked && (picked.environment || picked.agentVersion)) {
|
|
373
|
+
return {
|
|
374
|
+
...picked.environment ? { environment: picked.environment } : {},
|
|
375
|
+
...picked.agentVersion ? { agentVersion: picked.agentVersion } : {}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return defaultEnvironment ? { environment: defaultEnvironment } : {};
|
|
380
|
+
}
|
|
381
|
+
function incomingTraceparent(headers, enabled) {
|
|
382
|
+
if (!enabled) return void 0;
|
|
383
|
+
const value = headers.traceparent ?? headers.Traceparent;
|
|
384
|
+
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;
|
|
385
|
+
}
|
|
251
386
|
|
|
252
387
|
// src/routes/restToolsManifest.ts
|
|
253
388
|
var restToolsManifestRoute = async (request, ctx) => {
|
|
@@ -306,6 +441,8 @@ function newAccumulator() {
|
|
|
306
441
|
interrupted: false,
|
|
307
442
|
erroredInBand: false,
|
|
308
443
|
threadCreateFired: false,
|
|
444
|
+
personaRunId: void 0,
|
|
445
|
+
threadId: void 0,
|
|
309
446
|
toolNames: /* @__PURE__ */ new Map(),
|
|
310
447
|
nodeMeta: /* @__PURE__ */ new Map()
|
|
311
448
|
};
|
|
@@ -315,6 +452,8 @@ async function processEvent(event, acc, runCtx, hooks) {
|
|
|
315
452
|
switch (e.type) {
|
|
316
453
|
case EventType.RUN_STARTED: {
|
|
317
454
|
const threadId = typeof e.threadId === "string" ? e.threadId : void 0;
|
|
455
|
+
if (!acc.personaRunId && typeof e.runId === "string") acc.personaRunId = e.runId;
|
|
456
|
+
if (!acc.threadId && threadId) acc.threadId = threadId;
|
|
318
457
|
if (runCtx.kind === "chat" && runCtx.agentId && !acc.threadCreateFired && !runCtx.threadId && threadId) {
|
|
319
458
|
acc.threadCreateFired = true;
|
|
320
459
|
await hooks?.onThreadCreate?.({ userId: runCtx.userId, agentId: runCtx.agentId, threadId });
|
|
@@ -423,9 +562,9 @@ var RunDriver = class {
|
|
|
423
562
|
constructor(runId, runCtx, source, hooks, mode) {
|
|
424
563
|
this.runId = runId;
|
|
425
564
|
this.runCtx = runCtx;
|
|
426
|
-
this.firstFramePromise = new Promise((resolve,
|
|
565
|
+
this.firstFramePromise = new Promise((resolve, reject2) => {
|
|
427
566
|
this.resolveFirstFrame = resolve;
|
|
428
|
-
this.rejectFirstFrame =
|
|
567
|
+
this.rejectFirstFrame = reject2;
|
|
429
568
|
});
|
|
430
569
|
void this.pump(source, hooks, mode);
|
|
431
570
|
}
|
|
@@ -460,7 +599,9 @@ var RunDriver = class {
|
|
|
460
599
|
text: acc.text,
|
|
461
600
|
eventCount: count,
|
|
462
601
|
interrupted: acc.interrupted,
|
|
463
|
-
erroredInBand: acc.erroredInBand
|
|
602
|
+
erroredInBand: acc.erroredInBand,
|
|
603
|
+
...acc.personaRunId ? { personaRunId: acc.personaRunId } : {},
|
|
604
|
+
...acc.threadId || this.runCtx.threadId ? { threadId: acc.threadId ?? this.runCtx.threadId } : {}
|
|
464
605
|
};
|
|
465
606
|
try {
|
|
466
607
|
await hooks?.afterRun?.(this.runCtx, result);
|
|
@@ -510,14 +651,13 @@ var RunDriver = class {
|
|
|
510
651
|
* original connection plus a reconnect that raced it) are supported.
|
|
511
652
|
*/
|
|
512
653
|
subscribe(sinceSeq) {
|
|
513
|
-
const
|
|
514
|
-
const finishedAtSubscribeTime = this.finished;
|
|
654
|
+
const driver = this;
|
|
515
655
|
const listeners = this.listeners;
|
|
516
656
|
const doneListeners = this.doneListeners;
|
|
517
657
|
return {
|
|
518
658
|
[Symbol.asyncIterator]() {
|
|
519
|
-
const queue =
|
|
520
|
-
let live = !
|
|
659
|
+
const queue = driver.frames.slice(Math.max(sinceSeq + 1, 0));
|
|
660
|
+
let live = !driver.finished;
|
|
521
661
|
let wake = null;
|
|
522
662
|
const onFrame = (_seq, frame) => {
|
|
523
663
|
queue.push(frame);
|
|
@@ -657,12 +797,19 @@ var chatRoute = async (request, ctx) => {
|
|
|
657
797
|
hasContext: !!body.context
|
|
658
798
|
});
|
|
659
799
|
const userId = request.userId;
|
|
800
|
+
const version = await resolveRunVersion(request.body, ctx, {
|
|
801
|
+
userId,
|
|
802
|
+
agentId: body.agentId,
|
|
803
|
+
kind: "chat"
|
|
804
|
+
});
|
|
805
|
+
const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
|
|
660
806
|
const runCtx = {
|
|
661
807
|
userId,
|
|
662
808
|
kind: "chat",
|
|
663
809
|
agentId: body.agentId,
|
|
664
810
|
threadId: body.threadId,
|
|
665
|
-
messages: body.messages
|
|
811
|
+
messages: body.messages,
|
|
812
|
+
...version
|
|
666
813
|
};
|
|
667
814
|
logger.debug("beforeRun hook", { agentId: body.agentId, threadId: body.threadId });
|
|
668
815
|
await ctx.hooks?.beforeRun?.(runCtx);
|
|
@@ -675,7 +822,9 @@ var chatRoute = async (request, ctx) => {
|
|
|
675
822
|
contextOverride: body.contextOverride,
|
|
676
823
|
context: body.context,
|
|
677
824
|
tools: body.tools,
|
|
678
|
-
attachments: body.attachments
|
|
825
|
+
attachments: body.attachments,
|
|
826
|
+
...version,
|
|
827
|
+
...traceparent ? { traceparent } : {}
|
|
679
828
|
});
|
|
680
829
|
const runId = crypto.randomUUID();
|
|
681
830
|
logger.info("chat run created", {
|
|
@@ -874,7 +1023,16 @@ var createVoiceSession = async (request, ctx) => {
|
|
|
874
1023
|
const contextOverride = typeof body.contextOverride === "string" && body.contextOverride ? body.contextOverride : void 0;
|
|
875
1024
|
const context = validateTurnContext(body.context);
|
|
876
1025
|
const tools = parseFrontendTools(body.tools);
|
|
1026
|
+
const version = await resolveRunVersion(body, ctx, {
|
|
1027
|
+
userId: request.userId,
|
|
1028
|
+
agentId,
|
|
1029
|
+
kind: "voice"
|
|
1030
|
+
});
|
|
1031
|
+
const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
|
|
877
1032
|
const ticket = await ctx.client.voice.createSession(agentId, {
|
|
1033
|
+
...version,
|
|
1034
|
+
// `traceparent` needs @personaai/sdk >= 0.27 (older SDKs ignore it).
|
|
1035
|
+
...traceparent ? { traceparent } : {},
|
|
878
1036
|
...threadId ? { threadId } : {},
|
|
879
1037
|
...contextOverride ? { contextOverride } : {},
|
|
880
1038
|
...context ? { context } : {},
|
|
@@ -883,7 +1041,8 @@ var createVoiceSession = async (request, ctx) => {
|
|
|
883
1041
|
await ctx.hooks?.onVoiceSessionCreate?.({
|
|
884
1042
|
userId: request.userId,
|
|
885
1043
|
agentId,
|
|
886
|
-
threadId
|
|
1044
|
+
threadId,
|
|
1045
|
+
...version
|
|
887
1046
|
});
|
|
888
1047
|
return json(201, ticket);
|
|
889
1048
|
};
|
|
@@ -964,12 +1123,23 @@ var sendAgentMessage = async (request, ctx) => {
|
|
|
964
1123
|
const prompt = requireStringField(body, "prompt");
|
|
965
1124
|
const channel = optionalChannel(body);
|
|
966
1125
|
const displayName = optionalStringField(body, "displayName");
|
|
1126
|
+
const version = await resolveRunVersion(body, ctx, {
|
|
1127
|
+
userId: request.userId,
|
|
1128
|
+
agentId,
|
|
1129
|
+
kind: "messages"
|
|
1130
|
+
});
|
|
1131
|
+
const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
|
|
1132
|
+
const runOptions = {
|
|
1133
|
+
...version,
|
|
1134
|
+
...traceparent ? { traceparent } : {}
|
|
1135
|
+
};
|
|
967
1136
|
const delivery = await ctx.client.messages.sendFromAgent({
|
|
968
1137
|
agentId,
|
|
969
1138
|
to,
|
|
970
1139
|
prompt,
|
|
971
1140
|
...channel ? { channel } : {},
|
|
972
|
-
...displayName ? { displayName } : {}
|
|
1141
|
+
...displayName ? { displayName } : {},
|
|
1142
|
+
...runOptions
|
|
973
1143
|
});
|
|
974
1144
|
return json(200, delivery);
|
|
975
1145
|
};
|
|
@@ -1665,11 +1835,26 @@ var workflowStreamRoute = async (request, ctx) => {
|
|
|
1665
1835
|
workflowId,
|
|
1666
1836
|
input: body.input
|
|
1667
1837
|
};
|
|
1838
|
+
const agentVersion = await resolveRunVersion(
|
|
1839
|
+
request.body ?? {},
|
|
1840
|
+
ctx,
|
|
1841
|
+
{ userId, workflowId, kind: "workflow" },
|
|
1842
|
+
"agentEnvironment"
|
|
1843
|
+
);
|
|
1844
|
+
if (agentVersion.environment) runCtx.environment = agentVersion.environment;
|
|
1845
|
+
if (agentVersion.agentVersion) runCtx.agentVersion = agentVersion.agentVersion;
|
|
1846
|
+
const traceparent = incomingTraceparent(request.headers, ctx.forwardTraceparent);
|
|
1668
1847
|
await ctx.hooks?.beforeRun?.(runCtx);
|
|
1848
|
+
const runOptions = {
|
|
1849
|
+
...agentVersion.environment ? { agentEnvironment: agentVersion.environment } : {},
|
|
1850
|
+
...agentVersion.agentVersion ? { agentVersion: agentVersion.agentVersion } : {},
|
|
1851
|
+
...traceparent ? { traceparent } : {}
|
|
1852
|
+
};
|
|
1669
1853
|
const stream = ctx.client.workflows.stream(workflowId, {
|
|
1670
1854
|
input: body.input,
|
|
1671
1855
|
dryRun: body.dryRun,
|
|
1672
|
-
version: body.version
|
|
1856
|
+
version: body.version,
|
|
1857
|
+
...runOptions
|
|
1673
1858
|
});
|
|
1674
1859
|
const runId = crypto.randomUUID();
|
|
1675
1860
|
const driver = new RunDriver(runId, runCtx, stream, ctx.hooks, ctx.mode);
|
|
@@ -1717,15 +1902,15 @@ function resolveSignedUrl(request, publicUrl) {
|
|
|
1717
1902
|
}
|
|
1718
1903
|
async function computeTwilioSignature(authToken, url, params) {
|
|
1719
1904
|
const payload = url + Object.keys(params).sort().map((key2) => `${key2}${params[key2]}`).join("");
|
|
1720
|
-
const
|
|
1905
|
+
const encoder2 = new TextEncoder();
|
|
1721
1906
|
const key = await crypto.subtle.importKey(
|
|
1722
1907
|
"raw",
|
|
1723
|
-
|
|
1908
|
+
encoder2.encode(authToken),
|
|
1724
1909
|
{ name: "HMAC", hash: "SHA-1" },
|
|
1725
1910
|
false,
|
|
1726
1911
|
["sign"]
|
|
1727
1912
|
);
|
|
1728
|
-
const signature = await crypto.subtle.sign("HMAC", key,
|
|
1913
|
+
const signature = await crypto.subtle.sign("HMAC", key, encoder2.encode(payload));
|
|
1729
1914
|
const bytes = new Uint8Array(signature);
|
|
1730
1915
|
let binary = "";
|
|
1731
1916
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
@@ -1890,6 +2075,598 @@ var twilioMessageChannel = async (request, ctx) => {
|
|
|
1890
2075
|
}
|
|
1891
2076
|
};
|
|
1892
2077
|
|
|
2078
|
+
// src/routes/feedback.ts
|
|
2079
|
+
var MAX_COMMENT = 2e3;
|
|
2080
|
+
var MAX_TAGS = 10;
|
|
2081
|
+
var MAX_TAG = 50;
|
|
2082
|
+
var createFeedback = async (request, ctx) => {
|
|
2083
|
+
const body = requireBodyObject(request.body);
|
|
2084
|
+
const runId = requireStringField(body, "runId");
|
|
2085
|
+
if (body.score !== 1 && body.score !== -1) {
|
|
2086
|
+
throw new RuntimeHttpError(400, "INVALID_REQUEST", '"score" must be 1 or -1.');
|
|
2087
|
+
}
|
|
2088
|
+
const score = body.score;
|
|
2089
|
+
let comment;
|
|
2090
|
+
if (body.comment !== void 0 && body.comment !== null) {
|
|
2091
|
+
if (typeof body.comment !== "string" || body.comment.length > MAX_COMMENT) {
|
|
2092
|
+
throw new RuntimeHttpError(
|
|
2093
|
+
400,
|
|
2094
|
+
"INVALID_REQUEST",
|
|
2095
|
+
`"comment" must be a string of at most ${MAX_COMMENT} characters.`
|
|
2096
|
+
);
|
|
2097
|
+
}
|
|
2098
|
+
comment = body.comment.trim() || void 0;
|
|
2099
|
+
}
|
|
2100
|
+
let tags;
|
|
2101
|
+
if (body.tags !== void 0 && body.tags !== null) {
|
|
2102
|
+
if (!Array.isArray(body.tags) || body.tags.length > MAX_TAGS || !body.tags.every((t) => typeof t === "string" && t.length > 0 && t.length <= MAX_TAG)) {
|
|
2103
|
+
throw new RuntimeHttpError(
|
|
2104
|
+
400,
|
|
2105
|
+
"INVALID_REQUEST",
|
|
2106
|
+
`"tags" must be up to ${MAX_TAGS} strings of at most ${MAX_TAG} characters.`
|
|
2107
|
+
);
|
|
2108
|
+
}
|
|
2109
|
+
tags = body.tags;
|
|
2110
|
+
}
|
|
2111
|
+
const result = await ctx.client.feedback.create({
|
|
2112
|
+
runId,
|
|
2113
|
+
score,
|
|
2114
|
+
...comment ? { comment } : {},
|
|
2115
|
+
...tags?.length ? { tags } : {}
|
|
2116
|
+
});
|
|
2117
|
+
await ctx.hooks?.onFeedback?.({
|
|
2118
|
+
userId: request.userId,
|
|
2119
|
+
runId,
|
|
2120
|
+
score,
|
|
2121
|
+
...comment ? { comment } : {},
|
|
2122
|
+
...tags?.length ? { tags } : {}
|
|
2123
|
+
});
|
|
2124
|
+
return json(200, result);
|
|
2125
|
+
};
|
|
2126
|
+
|
|
2127
|
+
// src/routes/webhooks.ts
|
|
2128
|
+
var encoder = new TextEncoder();
|
|
2129
|
+
var DEFAULT_SEEN_LIMIT = 1e3;
|
|
2130
|
+
function rawBodyBytes(request) {
|
|
2131
|
+
const raw = request.rawBody;
|
|
2132
|
+
if (raw === void 0 || raw === null) return null;
|
|
2133
|
+
return typeof raw === "string" ? encoder.encode(raw) : raw;
|
|
2134
|
+
}
|
|
2135
|
+
function parseSignature(header) {
|
|
2136
|
+
let timestamp = null;
|
|
2137
|
+
const signatures = [];
|
|
2138
|
+
for (const part of header.split(",")) {
|
|
2139
|
+
const eq = part.indexOf("=");
|
|
2140
|
+
if (eq <= 0) continue;
|
|
2141
|
+
const key = part.slice(0, eq).trim();
|
|
2142
|
+
const value = part.slice(eq + 1).trim();
|
|
2143
|
+
if (key === "t" && /^\d+$/.test(value)) timestamp = Number(value);
|
|
2144
|
+
else if (key === "v1" && /^[0-9a-f]{64}$/i.test(value)) signatures.push(value.toLowerCase());
|
|
2145
|
+
}
|
|
2146
|
+
return timestamp === null || signatures.length === 0 ? null : { timestamp, signatures };
|
|
2147
|
+
}
|
|
2148
|
+
function toHex(bytes) {
|
|
2149
|
+
return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
2150
|
+
}
|
|
2151
|
+
function safeEqual(a, b) {
|
|
2152
|
+
if (a.length !== b.length) return false;
|
|
2153
|
+
let diff = 0;
|
|
2154
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
2155
|
+
return diff === 0;
|
|
2156
|
+
}
|
|
2157
|
+
async function hmacHex(secret, message) {
|
|
2158
|
+
const key = await crypto.subtle.importKey(
|
|
2159
|
+
"raw",
|
|
2160
|
+
encoder.encode(secret),
|
|
2161
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
2162
|
+
false,
|
|
2163
|
+
["sign"]
|
|
2164
|
+
);
|
|
2165
|
+
return toHex(await crypto.subtle.sign("HMAC", key, message));
|
|
2166
|
+
}
|
|
2167
|
+
function concat(a, b) {
|
|
2168
|
+
const out = new Uint8Array(a.length + b.length);
|
|
2169
|
+
out.set(a, 0);
|
|
2170
|
+
out.set(b, a.length);
|
|
2171
|
+
return out;
|
|
2172
|
+
}
|
|
2173
|
+
var reject = (code, message) => json(400, { error: { code, message } });
|
|
2174
|
+
function createSeenSet(limit = DEFAULT_SEEN_LIMIT) {
|
|
2175
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2176
|
+
return {
|
|
2177
|
+
has: (id) => ids.has(id),
|
|
2178
|
+
add(id) {
|
|
2179
|
+
ids.add(id);
|
|
2180
|
+
if (ids.size > limit) ids.delete(ids.values().next().value);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
}
|
|
2184
|
+
function createWebhookRoute() {
|
|
2185
|
+
const memory = createSeenSet();
|
|
2186
|
+
return async (request, ctx) => {
|
|
2187
|
+
const options = ctx.webhooks;
|
|
2188
|
+
if (!options) return json(404, { error: { code: "NOT_FOUND", message: "Not found" } });
|
|
2189
|
+
const logger = ctx.logger.child("webhooks");
|
|
2190
|
+
const body = rawBodyBytes(request);
|
|
2191
|
+
if (!body) {
|
|
2192
|
+
logger.error("webhook request has no rawBody: the adapter must pass the raw bytes");
|
|
2193
|
+
return json(500, {
|
|
2194
|
+
error: {
|
|
2195
|
+
code: "RUNTIME_MISCONFIGURED",
|
|
2196
|
+
message: "The server did not keep the raw request body for webhook verification."
|
|
2197
|
+
}
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
const header = request.headers["x-persona-signature"];
|
|
2201
|
+
if (!header) return reject("MISSING_SIGNATURE", "Missing X-Persona-Signature header");
|
|
2202
|
+
const parsed = parseSignature(header);
|
|
2203
|
+
if (!parsed) return reject("INVALID_SIGNATURE", "Malformed X-Persona-Signature header");
|
|
2204
|
+
const expected = await hmacHex(
|
|
2205
|
+
options.secret,
|
|
2206
|
+
concat(encoder.encode(`${parsed.timestamp}.`), body)
|
|
2207
|
+
);
|
|
2208
|
+
if (!parsed.signatures.some((sig) => safeEqual(sig, expected))) {
|
|
2209
|
+
logger.warn("webhook signature does not match");
|
|
2210
|
+
return reject("INVALID_SIGNATURE", "Webhook signature does not match");
|
|
2211
|
+
}
|
|
2212
|
+
const tolerance = options.toleranceSec ?? 300;
|
|
2213
|
+
if (tolerance > 0 && Math.abs(Math.floor(Date.now() / 1e3) - parsed.timestamp) > tolerance) {
|
|
2214
|
+
return reject("TIMESTAMP_OUT_OF_RANGE", "Webhook timestamp is outside the allowed tolerance");
|
|
2215
|
+
}
|
|
2216
|
+
let event;
|
|
2217
|
+
try {
|
|
2218
|
+
event = JSON.parse(new TextDecoder().decode(body));
|
|
2219
|
+
} catch {
|
|
2220
|
+
return reject("INVALID_PAYLOAD", "Webhook body is not valid JSON");
|
|
2221
|
+
}
|
|
2222
|
+
if (!event || typeof event !== "object" || typeof event.type !== "string") {
|
|
2223
|
+
return reject("INVALID_PAYLOAD", "Webhook body is not a Persona event");
|
|
2224
|
+
}
|
|
2225
|
+
const deliveryId = request.headers["x-persona-delivery"];
|
|
2226
|
+
if (deliveryId) {
|
|
2227
|
+
const store = options.dedupe ?? memory;
|
|
2228
|
+
if (await store.has(deliveryId)) {
|
|
2229
|
+
logger.debug("duplicate webhook delivery ignored", { deliveryId, type: event.type });
|
|
2230
|
+
return json(200, { received: true, duplicate: true });
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
const type = event.type;
|
|
2234
|
+
const handlers = [options.on[type], options.on["*"]].filter(
|
|
2235
|
+
(h) => typeof h === "function"
|
|
2236
|
+
);
|
|
2237
|
+
try {
|
|
2238
|
+
for (const handler of handlers) await handler(event, { id: deliveryId, type });
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
logger.error("webhook handler failed", {
|
|
2241
|
+
type,
|
|
2242
|
+
deliveryId,
|
|
2243
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2244
|
+
});
|
|
2245
|
+
return json(500, { error: { code: "HANDLER_FAILED", message: "Webhook handler failed" } });
|
|
2246
|
+
}
|
|
2247
|
+
if (deliveryId) await (options.dedupe ?? memory).add(deliveryId);
|
|
2248
|
+
logger.info("webhook handled", { type, deliveryId, handlers: handlers.length });
|
|
2249
|
+
return json(200, { received: true });
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// src/routes/secrets.ts
|
|
2254
|
+
var listSecrets = async (_request, ctx) => {
|
|
2255
|
+
return json(200, await ctx.client.secrets.list());
|
|
2256
|
+
};
|
|
2257
|
+
var createSecret = async (request, ctx) => {
|
|
2258
|
+
const body = requireBodyObject(request.body);
|
|
2259
|
+
const secret = await ctx.client.secrets.create(
|
|
2260
|
+
{
|
|
2261
|
+
label: requireStringField(body, "label"),
|
|
2262
|
+
value: requireStringField(body, "value")
|
|
2263
|
+
},
|
|
2264
|
+
request.headers["idempotency-key"]
|
|
2265
|
+
);
|
|
2266
|
+
return json(201, secret);
|
|
2267
|
+
};
|
|
2268
|
+
var getSecret = async (_request, ctx) => {
|
|
2269
|
+
return json(200, await ctx.client.secrets.get(requireParam(ctx.params, "id")));
|
|
2270
|
+
};
|
|
2271
|
+
var updateSecret = async (request, ctx) => {
|
|
2272
|
+
const body = request.body ?? {};
|
|
2273
|
+
return json(200, await ctx.client.secrets.update(requireParam(ctx.params, "id"), body));
|
|
2274
|
+
};
|
|
2275
|
+
var deleteSecret = async (_request, ctx) => {
|
|
2276
|
+
await ctx.client.secrets.delete(requireParam(ctx.params, "id"));
|
|
2277
|
+
return noContent();
|
|
2278
|
+
};
|
|
2279
|
+
var getSecretUsage = async (_request, ctx) => {
|
|
2280
|
+
return json(200, await ctx.client.secrets.getUsage(requireParam(ctx.params, "id")));
|
|
2281
|
+
};
|
|
2282
|
+
var bulkDeleteSecrets = async (request, ctx) => {
|
|
2283
|
+
const body = requireBodyObject(request.body);
|
|
2284
|
+
const ids = Array.isArray(body.ids) ? body.ids : [];
|
|
2285
|
+
return json(200, await ctx.client.secrets.bulkDelete(ids));
|
|
2286
|
+
};
|
|
2287
|
+
|
|
2288
|
+
// src/routes/restToolSources.ts
|
|
2289
|
+
var listRestToolSources = async (request, ctx) => {
|
|
2290
|
+
const items = await ctx.client.restToolSources.list({
|
|
2291
|
+
page: toInt(request.query.page),
|
|
2292
|
+
limit: toInt(request.query.limit),
|
|
2293
|
+
search: request.query.search
|
|
2294
|
+
});
|
|
2295
|
+
return json(200, items);
|
|
2296
|
+
};
|
|
2297
|
+
var createRestToolSource = async (request, ctx) => {
|
|
2298
|
+
const body = requireBodyObject(request.body);
|
|
2299
|
+
const source = await ctx.client.restToolSources.create(body, request.headers["idempotency-key"]);
|
|
2300
|
+
return json(201, source);
|
|
2301
|
+
};
|
|
2302
|
+
var getRestToolSource = async (_request, ctx) => {
|
|
2303
|
+
return json(200, await ctx.client.restToolSources.get(requireParam(ctx.params, "id")));
|
|
2304
|
+
};
|
|
2305
|
+
var updateRestToolSource = async (request, ctx) => {
|
|
2306
|
+
const body = request.body ?? {};
|
|
2307
|
+
return json(200, await ctx.client.restToolSources.update(requireParam(ctx.params, "id"), body));
|
|
2308
|
+
};
|
|
2309
|
+
var deleteRestToolSource = async (_request, ctx) => {
|
|
2310
|
+
await ctx.client.restToolSources.delete(requireParam(ctx.params, "id"));
|
|
2311
|
+
return noContent();
|
|
2312
|
+
};
|
|
2313
|
+
var getRestToolSourceUsage = async (_request, ctx) => {
|
|
2314
|
+
return json(200, await ctx.client.restToolSources.getUsage(requireParam(ctx.params, "id")));
|
|
2315
|
+
};
|
|
2316
|
+
var testRestToolSource = async (_request, ctx) => {
|
|
2317
|
+
return json(200, await ctx.client.restToolSources.testConnection(requireParam(ctx.params, "id")));
|
|
2318
|
+
};
|
|
2319
|
+
var bulkDeleteRestToolSources = async (request, ctx) => {
|
|
2320
|
+
const body = requireBodyObject(request.body);
|
|
2321
|
+
const ids = Array.isArray(body.ids) ? body.ids : [];
|
|
2322
|
+
return json(200, await ctx.client.restToolSources.bulkDelete(ids));
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
// src/routes/deploy.ts
|
|
2326
|
+
function environmentParam(ctx) {
|
|
2327
|
+
const env = requireParam(ctx.params, "environment");
|
|
2328
|
+
if (env !== "development" && env !== "production") {
|
|
2329
|
+
throw new RuntimeHttpError(
|
|
2330
|
+
400,
|
|
2331
|
+
"INVALID_REQUEST",
|
|
2332
|
+
'environment must be "development" or "production".'
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
return env;
|
|
2336
|
+
}
|
|
2337
|
+
function positiveInt(raw, name) {
|
|
2338
|
+
const v = Number(raw);
|
|
2339
|
+
if (!Number.isInteger(v) || v < 1) {
|
|
2340
|
+
throw new RuntimeHttpError(400, "INVALID_REQUEST", `${name} must be a positive integer.`);
|
|
2341
|
+
}
|
|
2342
|
+
return v;
|
|
2343
|
+
}
|
|
2344
|
+
function versionOrDraft(raw, name) {
|
|
2345
|
+
if (!raw || raw === "draft") return "draft";
|
|
2346
|
+
return positiveInt(raw, name);
|
|
2347
|
+
}
|
|
2348
|
+
async function audit(request, ctx, action, target) {
|
|
2349
|
+
await ctx.hooks?.onAdminAction?.({
|
|
2350
|
+
userId: request.userId,
|
|
2351
|
+
capability: "deploy",
|
|
2352
|
+
action,
|
|
2353
|
+
target
|
|
2354
|
+
});
|
|
2355
|
+
}
|
|
2356
|
+
var listAgentVersions = async (request, ctx) => {
|
|
2357
|
+
return json(
|
|
2358
|
+
200,
|
|
2359
|
+
await ctx.projectClient().agents.versions.list(requireParam(ctx.params, "id"), {
|
|
2360
|
+
page: toInt(request.query.page),
|
|
2361
|
+
limit: toInt(request.query.limit)
|
|
2362
|
+
})
|
|
2363
|
+
);
|
|
2364
|
+
};
|
|
2365
|
+
var publishAgentVersion = async (request, ctx) => {
|
|
2366
|
+
const agentId = requireParam(ctx.params, "id");
|
|
2367
|
+
const body = request.body ?? {};
|
|
2368
|
+
const changelog = typeof body.changelog === "string" ? body.changelog : void 0;
|
|
2369
|
+
const version = await ctx.projectClient().agents.versions.publish(agentId, changelog ? { changelog } : {});
|
|
2370
|
+
await audit(request, ctx, "agent.version.publish", { agentId, version: version.version });
|
|
2371
|
+
return json(201, version);
|
|
2372
|
+
};
|
|
2373
|
+
var agentVersionStats = async (request, ctx) => {
|
|
2374
|
+
const source = request.query.source;
|
|
2375
|
+
return json(
|
|
2376
|
+
200,
|
|
2377
|
+
await ctx.projectClient().agents.versions.stats(requireParam(ctx.params, "id"), {
|
|
2378
|
+
days: toInt(request.query.days),
|
|
2379
|
+
...source ? { source } : {}
|
|
2380
|
+
})
|
|
2381
|
+
);
|
|
2382
|
+
};
|
|
2383
|
+
var getAgentVersion = async (_request, ctx) => {
|
|
2384
|
+
const version = positiveInt(requireParam(ctx.params, "version"), "version");
|
|
2385
|
+
return json(
|
|
2386
|
+
200,
|
|
2387
|
+
await ctx.projectClient().agents.versions.get(requireParam(ctx.params, "id"), version)
|
|
2388
|
+
);
|
|
2389
|
+
};
|
|
2390
|
+
var diffAgentVersion = async (request, ctx) => {
|
|
2391
|
+
const from = versionOrDraft(requireParam(ctx.params, "version"), "version");
|
|
2392
|
+
const to = versionOrDraft(request.query.against, '"against"');
|
|
2393
|
+
return json(
|
|
2394
|
+
200,
|
|
2395
|
+
await ctx.projectClient().agents.versions.diff(requireParam(ctx.params, "id"), from, to)
|
|
2396
|
+
);
|
|
2397
|
+
};
|
|
2398
|
+
var getAgentDeployments = async (_request, ctx) => {
|
|
2399
|
+
return json(
|
|
2400
|
+
200,
|
|
2401
|
+
await ctx.projectClient().agents.versions.deployments(requireParam(ctx.params, "id"))
|
|
2402
|
+
);
|
|
2403
|
+
};
|
|
2404
|
+
var setAgentServePolicy = async (request, ctx) => {
|
|
2405
|
+
const agentId = requireParam(ctx.params, "id");
|
|
2406
|
+
const body = requireBodyObject(request.body);
|
|
2407
|
+
if (typeof body.serveDeployedVersion !== "boolean") {
|
|
2408
|
+
throw new RuntimeHttpError(
|
|
2409
|
+
400,
|
|
2410
|
+
"INVALID_REQUEST",
|
|
2411
|
+
'"serveDeployedVersion" is required and must be a boolean.'
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
const result = await ctx.projectClient().agents.versions.setServePolicy(agentId, body.serveDeployedVersion);
|
|
2415
|
+
await audit(request, ctx, "agent.serve_policy.set", { agentId, ...result });
|
|
2416
|
+
return json(200, result);
|
|
2417
|
+
};
|
|
2418
|
+
var deployAgentVersion = async (request, ctx) => {
|
|
2419
|
+
const agentId = requireParam(ctx.params, "id");
|
|
2420
|
+
const environment = environmentParam(ctx);
|
|
2421
|
+
const body = request.body ?? {};
|
|
2422
|
+
let version;
|
|
2423
|
+
if (body.version !== void 0 && body.version !== null) {
|
|
2424
|
+
if (typeof body.version !== "number" || !Number.isInteger(body.version) || body.version < 1) {
|
|
2425
|
+
throw new RuntimeHttpError(400, "INVALID_REQUEST", '"version" must be a positive integer.');
|
|
2426
|
+
}
|
|
2427
|
+
version = body.version;
|
|
2428
|
+
}
|
|
2429
|
+
const deployment = await ctx.projectClient().agents.versions.deploy(agentId, { environment, ...version ? { version } : {} });
|
|
2430
|
+
await audit(request, ctx, "agent.version.deploy", {
|
|
2431
|
+
agentId,
|
|
2432
|
+
environment,
|
|
2433
|
+
version: deployment.version
|
|
2434
|
+
});
|
|
2435
|
+
return json(200, deployment);
|
|
2436
|
+
};
|
|
2437
|
+
var rollbackAgentDeployment = async (request, ctx) => {
|
|
2438
|
+
const agentId = requireParam(ctx.params, "id");
|
|
2439
|
+
const environment = environmentParam(ctx);
|
|
2440
|
+
const deployment = await ctx.projectClient().agents.versions.rollback(agentId, environment);
|
|
2441
|
+
await audit(request, ctx, "agent.version.rollback", {
|
|
2442
|
+
agentId,
|
|
2443
|
+
environment,
|
|
2444
|
+
version: deployment.version
|
|
2445
|
+
});
|
|
2446
|
+
return json(200, deployment);
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
// src/routes/traces.ts
|
|
2450
|
+
var STRING_FILTERS = [
|
|
2451
|
+
"agentId",
|
|
2452
|
+
"threadId",
|
|
2453
|
+
"runId",
|
|
2454
|
+
"status",
|
|
2455
|
+
"source",
|
|
2456
|
+
"channel",
|
|
2457
|
+
"environment",
|
|
2458
|
+
"feedback",
|
|
2459
|
+
"from",
|
|
2460
|
+
"to"
|
|
2461
|
+
];
|
|
2462
|
+
var listTraces = async (request, ctx) => {
|
|
2463
|
+
const params = {
|
|
2464
|
+
page: toInt(request.query.page),
|
|
2465
|
+
limit: toInt(request.query.limit),
|
|
2466
|
+
agentVersion: toInt(request.query.agentVersion)
|
|
2467
|
+
};
|
|
2468
|
+
for (const key of STRING_FILTERS) {
|
|
2469
|
+
const value = request.query[key];
|
|
2470
|
+
if (value) params[key] = value;
|
|
2471
|
+
}
|
|
2472
|
+
return json(200, await ctx.projectClient().traces.list(params));
|
|
2473
|
+
};
|
|
2474
|
+
var getTrace = async (request, ctx) => {
|
|
2475
|
+
return json(
|
|
2476
|
+
200,
|
|
2477
|
+
await ctx.projectClient().traces.get(requireParam(ctx.params, "id"), {
|
|
2478
|
+
spans: request.query.spans !== "false"
|
|
2479
|
+
})
|
|
2480
|
+
);
|
|
2481
|
+
};
|
|
2482
|
+
var getTraceFeedback = async (_request, ctx) => {
|
|
2483
|
+
return json(200, await ctx.projectClient().traces.feedback(requireParam(ctx.params, "id")));
|
|
2484
|
+
};
|
|
2485
|
+
var getReplayPlan = async (_request, ctx) => {
|
|
2486
|
+
return json(200, await ctx.projectClient().traces.replayPlan(requireParam(ctx.params, "id")));
|
|
2487
|
+
};
|
|
2488
|
+
var replayTrace = async (request, ctx) => {
|
|
2489
|
+
const traceId = requireParam(ctx.params, "id");
|
|
2490
|
+
const options = request.body ?? {};
|
|
2491
|
+
const result = await ctx.projectClient().traces.replay(traceId, options);
|
|
2492
|
+
await ctx.hooks?.onAdminAction?.({
|
|
2493
|
+
userId: request.userId,
|
|
2494
|
+
capability: "traces",
|
|
2495
|
+
action: "trace.replay",
|
|
2496
|
+
target: { traceId, replayTraceId: result.traceId }
|
|
2497
|
+
});
|
|
2498
|
+
return json(201, result);
|
|
2499
|
+
};
|
|
2500
|
+
var listTraceReplays = async (_request, ctx) => {
|
|
2501
|
+
return json(200, await ctx.projectClient().traces.replays(requireParam(ctx.params, "id")));
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
// src/routes/evals.ts
|
|
2505
|
+
async function audit2(request, ctx, action, target) {
|
|
2506
|
+
await ctx.hooks?.onAdminAction?.({
|
|
2507
|
+
userId: request.userId,
|
|
2508
|
+
capability: "evals",
|
|
2509
|
+
action,
|
|
2510
|
+
target
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
var listDatasets = async (_request, ctx) => {
|
|
2514
|
+
return json(200, await ctx.projectClient().datasets.list());
|
|
2515
|
+
};
|
|
2516
|
+
var createDataset = async (request, ctx) => {
|
|
2517
|
+
const body = requireBodyObject(request.body);
|
|
2518
|
+
const dataset = await ctx.projectClient().datasets.create(body);
|
|
2519
|
+
await audit2(request, ctx, "dataset.create", { datasetId: dataset._id });
|
|
2520
|
+
return json(201, dataset);
|
|
2521
|
+
};
|
|
2522
|
+
var getDataset = async (_request, ctx) => {
|
|
2523
|
+
return json(200, await ctx.projectClient().datasets.get(requireParam(ctx.params, "id")));
|
|
2524
|
+
};
|
|
2525
|
+
var updateDataset = async (request, ctx) => {
|
|
2526
|
+
const body = requireBodyObject(request.body);
|
|
2527
|
+
return json(200, await ctx.projectClient().datasets.update(requireParam(ctx.params, "id"), body));
|
|
2528
|
+
};
|
|
2529
|
+
var deleteDataset = async (request, ctx) => {
|
|
2530
|
+
const datasetId = requireParam(ctx.params, "id");
|
|
2531
|
+
const result = await ctx.projectClient().datasets.delete(datasetId);
|
|
2532
|
+
await audit2(request, ctx, "dataset.delete", { datasetId });
|
|
2533
|
+
return json(200, result);
|
|
2534
|
+
};
|
|
2535
|
+
var listDatasetItems = async (request, ctx) => {
|
|
2536
|
+
return json(
|
|
2537
|
+
200,
|
|
2538
|
+
await ctx.projectClient().datasets.items.list(requireParam(ctx.params, "id"), {
|
|
2539
|
+
page: toInt(request.query.page),
|
|
2540
|
+
limit: toInt(request.query.limit)
|
|
2541
|
+
})
|
|
2542
|
+
);
|
|
2543
|
+
};
|
|
2544
|
+
var addDatasetItems = async (request, ctx) => {
|
|
2545
|
+
const datasetId = requireParam(ctx.params, "id");
|
|
2546
|
+
const body = requireBodyObject(request.body);
|
|
2547
|
+
let cases;
|
|
2548
|
+
if (typeof body.text === "string") cases = body.text;
|
|
2549
|
+
else if (Array.isArray(body.items)) cases = body.items;
|
|
2550
|
+
else {
|
|
2551
|
+
throw new RuntimeHttpError(
|
|
2552
|
+
400,
|
|
2553
|
+
"INVALID_REQUEST",
|
|
2554
|
+
'Send "items" (an array of cases) or "text" (CSV / JSON).'
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
try {
|
|
2558
|
+
return json(201, await ctx.projectClient().datasets.import(datasetId, cases));
|
|
2559
|
+
} catch (err) {
|
|
2560
|
+
if (err instanceof Error && err.message.startsWith("parseDatasetCases:")) {
|
|
2561
|
+
throw new RuntimeHttpError(
|
|
2562
|
+
400,
|
|
2563
|
+
"INVALID_REQUEST",
|
|
2564
|
+
err.message.replace("parseDatasetCases: ", "")
|
|
2565
|
+
);
|
|
2566
|
+
}
|
|
2567
|
+
throw err;
|
|
2568
|
+
}
|
|
2569
|
+
};
|
|
2570
|
+
var addDatasetItemFromTrace = async (request, ctx) => {
|
|
2571
|
+
const body = requireBodyObject(request.body);
|
|
2572
|
+
const traceId = requireStringField(body, "traceId");
|
|
2573
|
+
const options = body.expected === null ? { expected: null } : typeof body.expected === "string" ? { expected: body.expected } : {};
|
|
2574
|
+
return json(
|
|
2575
|
+
201,
|
|
2576
|
+
await ctx.projectClient().datasets.items.addFromTrace(requireParam(ctx.params, "id"), traceId, options)
|
|
2577
|
+
);
|
|
2578
|
+
};
|
|
2579
|
+
var updateDatasetItem = async (request, ctx) => {
|
|
2580
|
+
const body = requireBodyObject(request.body);
|
|
2581
|
+
return json(
|
|
2582
|
+
200,
|
|
2583
|
+
await ctx.projectClient().datasets.items.update(
|
|
2584
|
+
requireParam(ctx.params, "id"),
|
|
2585
|
+
requireParam(ctx.params, "itemId"),
|
|
2586
|
+
body
|
|
2587
|
+
)
|
|
2588
|
+
);
|
|
2589
|
+
};
|
|
2590
|
+
var deleteDatasetItem = async (_request, ctx) => {
|
|
2591
|
+
return json(
|
|
2592
|
+
200,
|
|
2593
|
+
await ctx.projectClient().datasets.items.delete(requireParam(ctx.params, "id"), requireParam(ctx.params, "itemId"))
|
|
2594
|
+
);
|
|
2595
|
+
};
|
|
2596
|
+
var listEvalRuns = async (request, ctx) => {
|
|
2597
|
+
return json(
|
|
2598
|
+
200,
|
|
2599
|
+
await ctx.projectClient().evals.list({
|
|
2600
|
+
page: toInt(request.query.page),
|
|
2601
|
+
limit: toInt(request.query.limit),
|
|
2602
|
+
...request.query.agentId ? { agentId: request.query.agentId } : {},
|
|
2603
|
+
...request.query.datasetId ? { datasetId: request.query.datasetId } : {}
|
|
2604
|
+
})
|
|
2605
|
+
);
|
|
2606
|
+
};
|
|
2607
|
+
var startEvalRun = async (request, ctx) => {
|
|
2608
|
+
const body = requireBodyObject(request.body);
|
|
2609
|
+
const run = await ctx.projectClient().evals.create(body);
|
|
2610
|
+
await audit2(request, ctx, "eval_run.start", {
|
|
2611
|
+
runId: run.id,
|
|
2612
|
+
agentId: run.agentId,
|
|
2613
|
+
datasetId: run.datasetId
|
|
2614
|
+
});
|
|
2615
|
+
return json(201, run);
|
|
2616
|
+
};
|
|
2617
|
+
var compareEvalRuns = async (request, ctx) => {
|
|
2618
|
+
const { a, b } = request.query;
|
|
2619
|
+
if (!a || !b) {
|
|
2620
|
+
throw new RuntimeHttpError(
|
|
2621
|
+
400,
|
|
2622
|
+
"INVALID_REQUEST",
|
|
2623
|
+
'"a" and "b" query parameters are required.'
|
|
2624
|
+
);
|
|
2625
|
+
}
|
|
2626
|
+
return json(200, await ctx.projectClient().evals.compare(a, b));
|
|
2627
|
+
};
|
|
2628
|
+
var getEvalRun = async (_request, ctx) => {
|
|
2629
|
+
return json(200, await ctx.projectClient().evals.get(requireParam(ctx.params, "id")));
|
|
2630
|
+
};
|
|
2631
|
+
var deleteEvalRun = async (request, ctx) => {
|
|
2632
|
+
const runId = requireParam(ctx.params, "id");
|
|
2633
|
+
const result = await ctx.projectClient().evals.delete(runId);
|
|
2634
|
+
await audit2(request, ctx, "eval_run.delete", { runId });
|
|
2635
|
+
return json(200, result);
|
|
2636
|
+
};
|
|
2637
|
+
var listEvalResults = async (request, ctx) => {
|
|
2638
|
+
return json(
|
|
2639
|
+
200,
|
|
2640
|
+
await ctx.projectClient().evals.results(requireParam(ctx.params, "id"), {
|
|
2641
|
+
page: toInt(request.query.page),
|
|
2642
|
+
limit: toInt(request.query.limit),
|
|
2643
|
+
...request.query.failed === "true" ? { failed: true } : {}
|
|
2644
|
+
})
|
|
2645
|
+
);
|
|
2646
|
+
};
|
|
2647
|
+
var cancelEvalRun = async (request, ctx) => {
|
|
2648
|
+
const runId = requireParam(ctx.params, "id");
|
|
2649
|
+
const run = await ctx.projectClient().evals.cancel(runId);
|
|
2650
|
+
await audit2(request, ctx, "eval_run.cancel", { runId });
|
|
2651
|
+
return json(200, run);
|
|
2652
|
+
};
|
|
2653
|
+
|
|
2654
|
+
// src/routes/observe.ts
|
|
2655
|
+
var getBudget = async (_request, ctx) => {
|
|
2656
|
+
return json(200, await ctx.projectClient().observe.budget());
|
|
2657
|
+
};
|
|
2658
|
+
var getUsage = async (request, ctx) => {
|
|
2659
|
+
const source = request.query.source;
|
|
2660
|
+
return json(
|
|
2661
|
+
200,
|
|
2662
|
+
await ctx.projectClient().observe.usage({
|
|
2663
|
+
days: toInt(request.query.days),
|
|
2664
|
+
...request.query.agentId ? { agentId: request.query.agentId } : {},
|
|
2665
|
+
...source ? { source } : {}
|
|
2666
|
+
})
|
|
2667
|
+
);
|
|
2668
|
+
};
|
|
2669
|
+
|
|
1893
2670
|
// src/runtime.ts
|
|
1894
2671
|
function resolveCapabilities(capabilities) {
|
|
1895
2672
|
return {
|
|
@@ -1903,7 +2680,13 @@ function resolveCapabilities(capabilities) {
|
|
|
1903
2680
|
auditLogs: capabilities?.auditLogs ?? false,
|
|
1904
2681
|
architect: capabilities?.architect ?? false,
|
|
1905
2682
|
workflowsWrite: capabilities?.workflowsWrite ?? false,
|
|
1906
|
-
messages: capabilities?.messages ?? false
|
|
2683
|
+
messages: capabilities?.messages ?? false,
|
|
2684
|
+
secrets: capabilities?.secrets ?? false,
|
|
2685
|
+
restToolSources: capabilities?.restToolSources ?? false,
|
|
2686
|
+
deploy: capabilities?.deploy ?? false,
|
|
2687
|
+
traces: capabilities?.traces ?? false,
|
|
2688
|
+
evals: capabilities?.evals ?? false,
|
|
2689
|
+
observe: capabilities?.observe ?? false
|
|
1907
2690
|
};
|
|
1908
2691
|
}
|
|
1909
2692
|
function buildRoutes(capabilities) {
|
|
@@ -1925,6 +2708,9 @@ function buildRoutes(capabilities) {
|
|
|
1925
2708
|
// (powered by Gemini Live). Mints a ticket only; the host's own
|
|
1926
2709
|
// frontend opens the WebSocket directly, this runtime never proxies it.
|
|
1927
2710
|
{ method: "POST", pattern: ["voice", "sessions"], handler: createVoiceSession },
|
|
2711
|
+
// Feedback — always on: an end user rates one of their own runs
|
|
2712
|
+
// (Persona refuses ratings on anyone else's).
|
|
2713
|
+
{ method: "POST", pattern: ["feedback"], handler: createFeedback },
|
|
1928
2714
|
// Agents — read-only discovery always on; write ops behind agentsWrite.
|
|
1929
2715
|
{ method: "GET", pattern: ["agents"], handler: listAgents },
|
|
1930
2716
|
// Files — always on, end-user-scoped uploads.
|
|
@@ -2129,8 +2915,128 @@ function buildRoutes(capabilities) {
|
|
|
2129
2915
|
{ method: "GET", pattern: ["workflows", "runs", ":runId"], handler: getWorkflowRun }
|
|
2130
2916
|
);
|
|
2131
2917
|
}
|
|
2918
|
+
if (capabilities.secrets) {
|
|
2919
|
+
routes.push(
|
|
2920
|
+
{ method: "GET", pattern: ["secrets"], handler: listSecrets },
|
|
2921
|
+
{ method: "POST", pattern: ["secrets"], handler: createSecret },
|
|
2922
|
+
{ method: "POST", pattern: ["secrets", "bulk-delete"], handler: bulkDeleteSecrets },
|
|
2923
|
+
{ method: "GET", pattern: ["secrets", ":id"], handler: getSecret },
|
|
2924
|
+
{ method: "PATCH", pattern: ["secrets", ":id"], handler: updateSecret },
|
|
2925
|
+
{ method: "DELETE", pattern: ["secrets", ":id"], handler: deleteSecret },
|
|
2926
|
+
{ method: "GET", pattern: ["secrets", ":id", "usage"], handler: getSecretUsage }
|
|
2927
|
+
);
|
|
2928
|
+
}
|
|
2929
|
+
if (capabilities.restToolSources) {
|
|
2930
|
+
routes.push(
|
|
2931
|
+
{ method: "GET", pattern: ["rest-tool-sources"], handler: listRestToolSources },
|
|
2932
|
+
{ method: "POST", pattern: ["rest-tool-sources"], handler: createRestToolSource },
|
|
2933
|
+
{
|
|
2934
|
+
method: "POST",
|
|
2935
|
+
pattern: ["rest-tool-sources", "bulk-delete"],
|
|
2936
|
+
handler: bulkDeleteRestToolSources
|
|
2937
|
+
},
|
|
2938
|
+
{ method: "GET", pattern: ["rest-tool-sources", ":id"], handler: getRestToolSource },
|
|
2939
|
+
{ method: "PATCH", pattern: ["rest-tool-sources", ":id"], handler: updateRestToolSource },
|
|
2940
|
+
{ method: "DELETE", pattern: ["rest-tool-sources", ":id"], handler: deleteRestToolSource },
|
|
2941
|
+
{
|
|
2942
|
+
method: "GET",
|
|
2943
|
+
pattern: ["rest-tool-sources", ":id", "usage"],
|
|
2944
|
+
handler: getRestToolSourceUsage
|
|
2945
|
+
},
|
|
2946
|
+
{ method: "POST", pattern: ["rest-tool-sources", ":id", "test"], handler: testRestToolSource }
|
|
2947
|
+
);
|
|
2948
|
+
}
|
|
2949
|
+
if (capabilities.deploy) {
|
|
2950
|
+
routes.push(
|
|
2951
|
+
{ method: "GET", pattern: ["agents", ":id", "versions"], handler: listAgentVersions },
|
|
2952
|
+
{ method: "POST", pattern: ["agents", ":id", "versions"], handler: publishAgentVersion },
|
|
2953
|
+
// Before `versions/:version`, which would otherwise capture "stats".
|
|
2954
|
+
{
|
|
2955
|
+
method: "GET",
|
|
2956
|
+
pattern: ["agents", ":id", "versions", "stats"],
|
|
2957
|
+
handler: agentVersionStats
|
|
2958
|
+
},
|
|
2959
|
+
{
|
|
2960
|
+
method: "GET",
|
|
2961
|
+
pattern: ["agents", ":id", "versions", ":version"],
|
|
2962
|
+
handler: getAgentVersion
|
|
2963
|
+
},
|
|
2964
|
+
{
|
|
2965
|
+
method: "GET",
|
|
2966
|
+
pattern: ["agents", ":id", "versions", ":version", "diff"],
|
|
2967
|
+
handler: diffAgentVersion
|
|
2968
|
+
},
|
|
2969
|
+
{ method: "GET", pattern: ["agents", ":id", "deployments"], handler: getAgentDeployments },
|
|
2970
|
+
{
|
|
2971
|
+
method: "PUT",
|
|
2972
|
+
pattern: ["agents", ":id", "deployments", "policy"],
|
|
2973
|
+
handler: setAgentServePolicy
|
|
2974
|
+
},
|
|
2975
|
+
{
|
|
2976
|
+
method: "POST",
|
|
2977
|
+
pattern: ["agents", ":id", "deployments", ":environment"],
|
|
2978
|
+
handler: deployAgentVersion
|
|
2979
|
+
},
|
|
2980
|
+
{
|
|
2981
|
+
method: "POST",
|
|
2982
|
+
pattern: ["agents", ":id", "deployments", ":environment", "rollback"],
|
|
2983
|
+
handler: rollbackAgentDeployment
|
|
2984
|
+
}
|
|
2985
|
+
);
|
|
2986
|
+
}
|
|
2987
|
+
if (capabilities.traces) {
|
|
2988
|
+
routes.push(
|
|
2989
|
+
{ method: "GET", pattern: ["traces"], handler: listTraces },
|
|
2990
|
+
{ method: "GET", pattern: ["traces", ":id"], handler: getTrace },
|
|
2991
|
+
{ method: "GET", pattern: ["traces", ":id", "feedback"], handler: getTraceFeedback },
|
|
2992
|
+
{ method: "GET", pattern: ["traces", ":id", "replay"], handler: getReplayPlan },
|
|
2993
|
+
{ method: "POST", pattern: ["traces", ":id", "replay"], handler: replayTrace },
|
|
2994
|
+
{ method: "GET", pattern: ["traces", ":id", "replays"], handler: listTraceReplays }
|
|
2995
|
+
);
|
|
2996
|
+
}
|
|
2997
|
+
if (capabilities.evals) {
|
|
2998
|
+
routes.push(
|
|
2999
|
+
{ method: "GET", pattern: ["datasets"], handler: listDatasets },
|
|
3000
|
+
{ method: "POST", pattern: ["datasets"], handler: createDataset },
|
|
3001
|
+
{ method: "GET", pattern: ["datasets", ":id"], handler: getDataset },
|
|
3002
|
+
{ method: "PATCH", pattern: ["datasets", ":id"], handler: updateDataset },
|
|
3003
|
+
{ method: "DELETE", pattern: ["datasets", ":id"], handler: deleteDataset },
|
|
3004
|
+
{ method: "GET", pattern: ["datasets", ":id", "items"], handler: listDatasetItems },
|
|
3005
|
+
{ method: "POST", pattern: ["datasets", ":id", "items"], handler: addDatasetItems },
|
|
3006
|
+
{
|
|
3007
|
+
method: "POST",
|
|
3008
|
+
pattern: ["datasets", ":id", "items", "from-trace"],
|
|
3009
|
+
handler: addDatasetItemFromTrace
|
|
3010
|
+
},
|
|
3011
|
+
{
|
|
3012
|
+
method: "PATCH",
|
|
3013
|
+
pattern: ["datasets", ":id", "items", ":itemId"],
|
|
3014
|
+
handler: updateDatasetItem
|
|
3015
|
+
},
|
|
3016
|
+
{
|
|
3017
|
+
method: "DELETE",
|
|
3018
|
+
pattern: ["datasets", ":id", "items", ":itemId"],
|
|
3019
|
+
handler: deleteDatasetItem
|
|
3020
|
+
},
|
|
3021
|
+
{ method: "GET", pattern: ["eval-runs"], handler: listEvalRuns },
|
|
3022
|
+
{ method: "POST", pattern: ["eval-runs"], handler: startEvalRun },
|
|
3023
|
+
// Before `eval-runs/:id`, which would otherwise capture "compare".
|
|
3024
|
+
{ method: "GET", pattern: ["eval-runs", "compare"], handler: compareEvalRuns },
|
|
3025
|
+
{ method: "GET", pattern: ["eval-runs", ":id"], handler: getEvalRun },
|
|
3026
|
+
{ method: "DELETE", pattern: ["eval-runs", ":id"], handler: deleteEvalRun },
|
|
3027
|
+
{ method: "GET", pattern: ["eval-runs", ":id", "results"], handler: listEvalResults },
|
|
3028
|
+
{ method: "POST", pattern: ["eval-runs", ":id", "cancel"], handler: cancelEvalRun }
|
|
3029
|
+
);
|
|
3030
|
+
}
|
|
3031
|
+
if (capabilities.observe) {
|
|
3032
|
+
routes.push(
|
|
3033
|
+
{ method: "GET", pattern: ["observe", "budget"], handler: getBudget },
|
|
3034
|
+
{ method: "GET", pattern: ["observe", "usage"], handler: getUsage }
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
2132
3037
|
return routes;
|
|
2133
3038
|
}
|
|
3039
|
+
var ADMIN_CAPABILITIES = ["deploy", "traces", "evals", "observe"];
|
|
2134
3040
|
function resolveMode(options) {
|
|
2135
3041
|
if (options.mode) return options.mode;
|
|
2136
3042
|
return process.env.NODE_ENV === "development" ? "development" : "production";
|
|
@@ -2177,6 +3083,15 @@ function createRuntime(options) {
|
|
|
2177
3083
|
}
|
|
2178
3084
|
);
|
|
2179
3085
|
}
|
|
3086
|
+
if (options.webhooks) {
|
|
3087
|
+
if (!options.webhooks.secret) throw new Error('createRuntime: "webhooks.secret" is required');
|
|
3088
|
+
routes.push({
|
|
3089
|
+
method: "POST",
|
|
3090
|
+
pattern: ["webhooks", "persona"],
|
|
3091
|
+
handler: createWebhookRoute(),
|
|
3092
|
+
requiresAuth: false
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
2180
3095
|
const mode = resolveMode(options);
|
|
2181
3096
|
const heartbeatIntervalMs = options.heartbeatIntervalMs ?? 15e3;
|
|
2182
3097
|
const runGraceMs = options.runGraceMs ?? DEFAULT_RUN_GRACE_MS;
|
|
@@ -2195,6 +3110,18 @@ function createRuntime(options) {
|
|
|
2195
3110
|
mountPath: options.mountPath ?? "",
|
|
2196
3111
|
enabledCapabilities: enabledCaps.length ? enabledCaps : ["(core only)"]
|
|
2197
3112
|
});
|
|
3113
|
+
const adminCaps = enabledCaps.filter((c) => ADMIN_CAPABILITIES.includes(c));
|
|
3114
|
+
if (adminCaps.length) {
|
|
3115
|
+
logger.warn(
|
|
3116
|
+
"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.",
|
|
3117
|
+
{
|
|
3118
|
+
capabilities: adminCaps,
|
|
3119
|
+
scopes: Object.fromEntries(
|
|
3120
|
+
adminCaps.map((c) => [c, CAPABILITY_SCOPES[c]])
|
|
3121
|
+
)
|
|
3122
|
+
}
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
2198
3125
|
logger.trace("runtime config", {
|
|
2199
3126
|
baseUrl: options.baseUrl,
|
|
2200
3127
|
hasHooks: !!options.hooks,
|
|
@@ -2327,6 +3254,7 @@ function createRuntime(options) {
|
|
|
2327
3254
|
}
|
|
2328
3255
|
const resolvedRequest = { ...request, userId };
|
|
2329
3256
|
const client = createClientForRequest(options, userId, logger);
|
|
3257
|
+
let projectClient;
|
|
2330
3258
|
const routeLogger = logger.child("route");
|
|
2331
3259
|
logger.debug("handler start", {
|
|
2332
3260
|
method: request.method,
|
|
@@ -2345,7 +3273,15 @@ function createRuntime(options) {
|
|
|
2345
3273
|
logger: routeLogger,
|
|
2346
3274
|
restToolsManifest: options.restToolsManifest,
|
|
2347
3275
|
rcpManifest: options.rcpManifest,
|
|
2348
|
-
channels: options.channels
|
|
3276
|
+
channels: options.channels,
|
|
3277
|
+
projectClient: () => userId === null ? client : projectClient ??= createClientForRequest(options, null, logger),
|
|
3278
|
+
webhooks: options.webhooks,
|
|
3279
|
+
versioning: {
|
|
3280
|
+
selectVersion: options.selectVersion,
|
|
3281
|
+
defaultEnvironment: options.defaultEnvironment,
|
|
3282
|
+
allowClientVersionSelection: options.allowClientVersionSelection
|
|
3283
|
+
},
|
|
3284
|
+
forwardTraceparent: options.forwardTraceparent ?? true
|
|
2349
3285
|
});
|
|
2350
3286
|
const durationMs = Date.now() - startMs;
|
|
2351
3287
|
logger.info("handle succeeded", {
|
|
@@ -2405,6 +3341,7 @@ function createRuntime(options) {
|
|
|
2405
3341
|
};
|
|
2406
3342
|
}
|
|
2407
3343
|
export {
|
|
3344
|
+
CAPABILITY_SCOPES,
|
|
2408
3345
|
RUNTIME_VERSION,
|
|
2409
3346
|
RuntimeHttpError,
|
|
2410
3347
|
createRuntime
|