@anvia/studio 0.1.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/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/index.d.ts +344 -0
- package/dist/index.js +2657 -0
- package/dist/index.js.map +1 -0
- package/dist/ui/assets/anvia.png +0 -0
- package/dist/ui/assets/index-DArq3ZFi.js +85 -0
- package/dist/ui/assets/index-DArq3ZFi.js.map +1 -0
- package/dist/ui/assets/index-VMCHaVgC.css +1 -0
- package/dist/ui/assets/logo.png +0 -0
- package/dist/ui/index.html +22 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2657 @@
|
|
|
1
|
+
// src/runtime/studio.ts
|
|
2
|
+
import {
|
|
3
|
+
Agent,
|
|
4
|
+
createHook as createHook3
|
|
5
|
+
} from "@anvia/core";
|
|
6
|
+
import { serve } from "@hono/node-server";
|
|
7
|
+
import { Hono as HonoApp } from "hono";
|
|
8
|
+
|
|
9
|
+
// src/traces/trace-observer.ts
|
|
10
|
+
var StudioTraceObserver = class {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
options;
|
|
15
|
+
startRun(args) {
|
|
16
|
+
const traceId = args.trace?.traceId ?? globalThis.crypto.randomUUID().replaceAll("-", "");
|
|
17
|
+
const observationId = globalThis.crypto.randomUUID().replaceAll("-", "").slice(0, 16);
|
|
18
|
+
return new StudioRunTraceObserver({
|
|
19
|
+
id: traceId,
|
|
20
|
+
observationId,
|
|
21
|
+
args,
|
|
22
|
+
store: this.store()
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
store() {
|
|
26
|
+
return typeof this.options.store === "function" ? this.options.store() : this.options.store;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var StudioRunTraceObserver = class {
|
|
30
|
+
constructor(props) {
|
|
31
|
+
this.props = props;
|
|
32
|
+
this.trace = { traceId: props.id, observationId: props.observationId };
|
|
33
|
+
}
|
|
34
|
+
props;
|
|
35
|
+
trace;
|
|
36
|
+
startedAt = /* @__PURE__ */ new Date();
|
|
37
|
+
observations = [];
|
|
38
|
+
startGeneration(args) {
|
|
39
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
40
|
+
return {
|
|
41
|
+
end: (endArgs) => {
|
|
42
|
+
this.observations.push(
|
|
43
|
+
traceObservation({
|
|
44
|
+
kind: "generation",
|
|
45
|
+
name: `model.turn.${args.turn}`,
|
|
46
|
+
status: "success",
|
|
47
|
+
turn: args.turn,
|
|
48
|
+
startedAt,
|
|
49
|
+
input: toJsonValue(args.request),
|
|
50
|
+
output: toJsonValue(endArgs.response),
|
|
51
|
+
metadata: {
|
|
52
|
+
model: args.request.model ?? "default",
|
|
53
|
+
toolCount: args.request.tools.length,
|
|
54
|
+
...endArgs.firstDeltaMs === void 0 ? {} : { firstDeltaMs: endArgs.firstDeltaMs }
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
error: (errorArgs) => {
|
|
60
|
+
this.observations.push(
|
|
61
|
+
traceObservation({
|
|
62
|
+
kind: "generation",
|
|
63
|
+
name: `model.turn.${args.turn}`,
|
|
64
|
+
status: "error",
|
|
65
|
+
turn: args.turn,
|
|
66
|
+
startedAt,
|
|
67
|
+
input: toJsonValue(args.request),
|
|
68
|
+
error: serializeError(errorArgs.error),
|
|
69
|
+
metadata: {
|
|
70
|
+
model: args.request.model ?? "default",
|
|
71
|
+
toolCount: args.request.tools.length
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
startTool(args) {
|
|
79
|
+
const startedAt = /* @__PURE__ */ new Date();
|
|
80
|
+
return {
|
|
81
|
+
end: (endArgs) => {
|
|
82
|
+
this.observations.push(
|
|
83
|
+
traceObservation({
|
|
84
|
+
kind: "tool",
|
|
85
|
+
name: args.toolName,
|
|
86
|
+
status: "success",
|
|
87
|
+
turn: args.turn,
|
|
88
|
+
startedAt,
|
|
89
|
+
input: parseOrString(args.args),
|
|
90
|
+
output: parseOrString(endArgs.result),
|
|
91
|
+
metadata: toolMetadata(args, endArgs.skipped)
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
error: (errorArgs) => {
|
|
96
|
+
this.observations.push(
|
|
97
|
+
traceObservation({
|
|
98
|
+
kind: "tool",
|
|
99
|
+
name: args.toolName,
|
|
100
|
+
status: "error",
|
|
101
|
+
turn: args.turn,
|
|
102
|
+
startedAt,
|
|
103
|
+
input: parseOrString(args.args),
|
|
104
|
+
error: serializeError(errorArgs.error),
|
|
105
|
+
metadata: toolMetadata(args, false)
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async end(args) {
|
|
112
|
+
await this.save("success", {
|
|
113
|
+
endedAt: /* @__PURE__ */ new Date(),
|
|
114
|
+
output: args.output,
|
|
115
|
+
usage: args.usage,
|
|
116
|
+
messages: toJsonValue(args.messages)
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async error(args) {
|
|
120
|
+
await this.save("error", {
|
|
121
|
+
endedAt: /* @__PURE__ */ new Date(),
|
|
122
|
+
error: serializeError(args.error),
|
|
123
|
+
usage: args.usage,
|
|
124
|
+
messages: toJsonValue(args.messages)
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async save(status, result) {
|
|
128
|
+
const sessionId = this.props.args.trace?.sessionId;
|
|
129
|
+
const store = this.props.store;
|
|
130
|
+
if (sessionId === void 0 || store === void 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const metadata = traceMetadata(this.props.args, result.messages);
|
|
134
|
+
const trace = {
|
|
135
|
+
id: this.props.id,
|
|
136
|
+
sessionId,
|
|
137
|
+
...this.props.args.trace?.name === void 0 ? {} : { name: this.props.args.trace.name },
|
|
138
|
+
status,
|
|
139
|
+
trace: this.trace,
|
|
140
|
+
startedAt: this.startedAt.toISOString(),
|
|
141
|
+
endedAt: result.endedAt.toISOString(),
|
|
142
|
+
durationMs: durationMs(this.startedAt, result.endedAt),
|
|
143
|
+
input: toJsonValue({
|
|
144
|
+
instructions: this.props.args.instructions,
|
|
145
|
+
prompt: this.props.args.prompt,
|
|
146
|
+
history: this.props.args.history
|
|
147
|
+
}),
|
|
148
|
+
...result.output === void 0 ? {} : { output: result.output },
|
|
149
|
+
...result.error === void 0 ? {} : { error: result.error },
|
|
150
|
+
...result.usage === void 0 ? {} : { usage: result.usage },
|
|
151
|
+
metadata,
|
|
152
|
+
observations: this.observations,
|
|
153
|
+
observationCount: this.observations.length
|
|
154
|
+
};
|
|
155
|
+
await store.saveTrace(trace);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
function traceObservation(props) {
|
|
159
|
+
const endedAt = /* @__PURE__ */ new Date();
|
|
160
|
+
return {
|
|
161
|
+
id: globalThis.crypto.randomUUID(),
|
|
162
|
+
kind: props.kind,
|
|
163
|
+
name: props.name,
|
|
164
|
+
status: props.status,
|
|
165
|
+
turn: props.turn,
|
|
166
|
+
startedAt: props.startedAt.toISOString(),
|
|
167
|
+
endedAt: endedAt.toISOString(),
|
|
168
|
+
durationMs: durationMs(props.startedAt, endedAt),
|
|
169
|
+
...props.input === void 0 ? {} : { input: props.input },
|
|
170
|
+
...props.output === void 0 ? {} : { output: props.output },
|
|
171
|
+
...props.error === void 0 ? {} : { error: props.error },
|
|
172
|
+
...props.metadata === void 0 ? {} : { metadata: props.metadata }
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function traceMetadata(args, messages) {
|
|
176
|
+
return compactJsonObject({
|
|
177
|
+
agentName: args.agentName,
|
|
178
|
+
agentDescription: args.agentDescription,
|
|
179
|
+
maxTurns: args.maxTurns,
|
|
180
|
+
userId: args.trace?.userId,
|
|
181
|
+
tags: args.trace?.tags,
|
|
182
|
+
version: args.trace?.version,
|
|
183
|
+
metadata: toJsonValue(args.trace?.metadata ?? {}),
|
|
184
|
+
messages
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function toolMetadata(args, skipped) {
|
|
188
|
+
return compactJsonObject({
|
|
189
|
+
internalCallId: args.internalCallId,
|
|
190
|
+
toolCallId: args.toolCallId,
|
|
191
|
+
skipped
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function compactJsonObject(values) {
|
|
195
|
+
const entries = Object.entries(values).flatMap(
|
|
196
|
+
([key, value]) => value === void 0 ? [] : [[key, toJsonValue(value)]]
|
|
197
|
+
);
|
|
198
|
+
return Object.fromEntries(entries);
|
|
199
|
+
}
|
|
200
|
+
function durationMs(startedAt, endedAt) {
|
|
201
|
+
return Math.max(0, endedAt.getTime() - startedAt.getTime());
|
|
202
|
+
}
|
|
203
|
+
function parseOrString(value) {
|
|
204
|
+
try {
|
|
205
|
+
return toJsonValue(JSON.parse(value));
|
|
206
|
+
} catch {
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function serializeError(error) {
|
|
211
|
+
if (error instanceof Error) {
|
|
212
|
+
return compactJsonObject({
|
|
213
|
+
name: error.name,
|
|
214
|
+
message: error.message,
|
|
215
|
+
stack: error.stack
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return toJsonValue(error);
|
|
219
|
+
}
|
|
220
|
+
function toJsonValue(value) {
|
|
221
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
222
|
+
return value;
|
|
223
|
+
}
|
|
224
|
+
if (value === void 0) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(value)) {
|
|
228
|
+
return value.map((item) => toJsonValue(item));
|
|
229
|
+
}
|
|
230
|
+
if (typeof value === "object") {
|
|
231
|
+
return compactJsonObject(value);
|
|
232
|
+
}
|
|
233
|
+
return String(value);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/ui/routes.tsx
|
|
237
|
+
import { readFile } from "fs/promises";
|
|
238
|
+
import { fileURLToPath } from "url";
|
|
239
|
+
function resolveStudioUiOptions(ui) {
|
|
240
|
+
const options = typeof ui === "object" ? ui : {};
|
|
241
|
+
return {
|
|
242
|
+
path: normalizeUiPath(options.path ?? "/ui"),
|
|
243
|
+
title: options.title ?? "Anvia Studio",
|
|
244
|
+
rootRoutes: options.rootRoutes ?? true,
|
|
245
|
+
redirectRoot: options.redirectRoot ?? true,
|
|
246
|
+
...options.clientScript === void 0 ? {} : { clientScript: options.clientScript },
|
|
247
|
+
protectShell: options.protectShell ?? false
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function isStudioUiEnabled(ui) {
|
|
251
|
+
return ui !== false;
|
|
252
|
+
}
|
|
253
|
+
function registerStudioUi(app, options) {
|
|
254
|
+
const scriptPath = `${options.path}/assets/client.js`;
|
|
255
|
+
const stylePath = `${options.path}/assets/styles.css`;
|
|
256
|
+
const renderShell = () => renderStudioUi({
|
|
257
|
+
options,
|
|
258
|
+
routePath: options.path,
|
|
259
|
+
clientScript: scriptPath,
|
|
260
|
+
stylesheet: stylePath
|
|
261
|
+
});
|
|
262
|
+
const renderRootShell = () => renderStudioUi({
|
|
263
|
+
options,
|
|
264
|
+
routePath: "",
|
|
265
|
+
clientScript: scriptPath,
|
|
266
|
+
stylesheet: stylePath
|
|
267
|
+
});
|
|
268
|
+
if (options.redirectRoot) {
|
|
269
|
+
app.get("/", (c) => c.redirect(studioUiEntryPath(options)));
|
|
270
|
+
}
|
|
271
|
+
app.get(options.path, async (c) => c.html(await renderShell()));
|
|
272
|
+
app.get(`${options.path}/:sessionId`, async (c) => c.html(await renderShell()));
|
|
273
|
+
app.get(`${options.path}/playground`, async (c) => c.html(await renderShell()));
|
|
274
|
+
app.get(`${options.path}/playground/:sessionId`, async (c) => c.html(await renderShell()));
|
|
275
|
+
app.get(`${options.path}/tracing`, async (c) => c.html(await renderShell()));
|
|
276
|
+
app.get(`${options.path}/tracing/:traceId`, async (c) => c.html(await renderShell()));
|
|
277
|
+
app.get(`${options.path}/tracing/sessions/:sessionId`, async (c) => c.html(await renderShell()));
|
|
278
|
+
app.get(`${options.path}/tracing/*`, async (c) => c.html(await renderShell()));
|
|
279
|
+
app.get(`${options.path}/sessions`, async (c) => c.html(await renderShell()));
|
|
280
|
+
app.get(`${options.path}/agents`, async (c) => c.html(await renderShell()));
|
|
281
|
+
app.get(`${options.path}/knowledge`, async (c) => c.html(await renderShell()));
|
|
282
|
+
if (options.rootRoutes) {
|
|
283
|
+
app.get("/playground", async (c) => c.html(await renderRootShell()));
|
|
284
|
+
app.get("/playground/:sessionId", async (c) => c.html(await renderRootShell()));
|
|
285
|
+
app.get("/tracing", async (c) => c.html(await renderRootShell()));
|
|
286
|
+
app.get("/tracing/:traceId", async (c) => c.html(await renderRootShell()));
|
|
287
|
+
app.get("/tracing/sessions/:sessionId", async (c) => c.html(await renderRootShell()));
|
|
288
|
+
app.get("/tracing/*", async (c) => c.html(await renderRootShell()));
|
|
289
|
+
}
|
|
290
|
+
app.get(scriptPath, async () => {
|
|
291
|
+
if (options.clientScript === void 0) {
|
|
292
|
+
return new Response(null, { status: 404 });
|
|
293
|
+
}
|
|
294
|
+
return new Response(options.clientScript, {
|
|
295
|
+
headers: {
|
|
296
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
297
|
+
"cache-control": "no-cache"
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
app.get(stylePath, async () => {
|
|
302
|
+
const source = await readBundledLegacyStylesheet();
|
|
303
|
+
return new Response(source, {
|
|
304
|
+
headers: {
|
|
305
|
+
"content-type": "text/css; charset=utf-8",
|
|
306
|
+
"cache-control": "no-cache"
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
app.get(`${options.path}/assets/:asset`, async (c) => {
|
|
311
|
+
const asset = c.req.param("asset");
|
|
312
|
+
if (asset.includes("/") || asset.includes("..")) {
|
|
313
|
+
return new Response(null, { status: 404 });
|
|
314
|
+
}
|
|
315
|
+
const source = await readBundledUiAsset(asset);
|
|
316
|
+
if (source === void 0) {
|
|
317
|
+
return new Response(null, { status: 404 });
|
|
318
|
+
}
|
|
319
|
+
const body = new ArrayBuffer(source.byteLength);
|
|
320
|
+
new Uint8Array(body).set(source);
|
|
321
|
+
return new Response(body, {
|
|
322
|
+
headers: {
|
|
323
|
+
"content-type": contentTypeForAsset(asset),
|
|
324
|
+
"cache-control": "no-cache"
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
async function renderStudioUi(props) {
|
|
330
|
+
const { options } = props;
|
|
331
|
+
if (options.clientScript !== void 0) {
|
|
332
|
+
return renderLegacyStudioUiShell({
|
|
333
|
+
title: options.title,
|
|
334
|
+
uiPath: props.routePath,
|
|
335
|
+
compatUiPath: options.path,
|
|
336
|
+
clientScript: props.clientScript,
|
|
337
|
+
stylesheet: props.stylesheet
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
const index = await readBundledUiIndex();
|
|
341
|
+
return index.replace(/<title>.*?<\/title>/, `<title>${escapeHtml(options.title)}</title>`).replace(
|
|
342
|
+
'data-ui-path="/ui"',
|
|
343
|
+
`data-ui-path="${escapeHtml(props.routePath)}" data-ui-compat-path="${escapeHtml(options.path)}"`
|
|
344
|
+
).replaceAll('"/ui/', `"${escapeHtml(options.path)}/`);
|
|
345
|
+
}
|
|
346
|
+
function renderLegacyStudioUiShell(props) {
|
|
347
|
+
const title = escapeHtml(props.title);
|
|
348
|
+
return [
|
|
349
|
+
"<!doctype html>",
|
|
350
|
+
'<html lang="en" class="dark">',
|
|
351
|
+
"<head>",
|
|
352
|
+
'<meta charset="utf-8">',
|
|
353
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
354
|
+
`<title>${title}</title>`,
|
|
355
|
+
'<script>(()=>{try{if((localStorage.getItem("anvia-studio-theme")??localStorage.getItem("aion-studio-theme"))==="light"){document.documentElement.classList.remove("dark")}}catch{}})();</script>',
|
|
356
|
+
`<link rel="icon" type="image/png" href="${escapeHtml(props.compatUiPath)}/assets/logo.png">`,
|
|
357
|
+
`<link rel="stylesheet" href="${escapeHtml(props.stylesheet)}">`,
|
|
358
|
+
"</head>",
|
|
359
|
+
"<body>",
|
|
360
|
+
`<div id="anvia-ui" data-ui-path="${escapeHtml(props.uiPath)}" data-ui-compat-path="${escapeHtml(props.compatUiPath)}">`,
|
|
361
|
+
'<main class="shell-loading">',
|
|
362
|
+
"<div>",
|
|
363
|
+
`<img src="${escapeHtml(props.compatUiPath)}/assets/logo.png" alt="" width="32" height="32">`,
|
|
364
|
+
'<p class="eyebrow">Anvia</p>',
|
|
365
|
+
`<h1>${title}</h1>`,
|
|
366
|
+
"</div>",
|
|
367
|
+
"</main>",
|
|
368
|
+
"</div>",
|
|
369
|
+
`<script type="module" src="${escapeHtml(props.clientScript)}"></script>`,
|
|
370
|
+
"</body>",
|
|
371
|
+
"</html>"
|
|
372
|
+
].join("");
|
|
373
|
+
}
|
|
374
|
+
function normalizeUiPath(path) {
|
|
375
|
+
const trimmed = path.trim();
|
|
376
|
+
const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
377
|
+
const withoutTrailingSlash = withSlash.length > 1 ? withSlash.replace(/\/+$/, "") : withSlash;
|
|
378
|
+
if (withoutTrailingSlash.length === 0 || withoutTrailingSlash === "/") {
|
|
379
|
+
return "/ui";
|
|
380
|
+
}
|
|
381
|
+
return withoutTrailingSlash;
|
|
382
|
+
}
|
|
383
|
+
function studioUiEntryPath(options) {
|
|
384
|
+
return options.rootRoutes ? "/playground" : options.path;
|
|
385
|
+
}
|
|
386
|
+
async function readBundledUiIndex() {
|
|
387
|
+
try {
|
|
388
|
+
return await readFile(fileURLToPath(new URL("./ui/index.html", import.meta.url)), "utf8");
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (!isNotFoundError(error)) {
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
return await readFile(
|
|
396
|
+
fileURLToPath(new URL("../../dist/ui/index.html", import.meta.url)),
|
|
397
|
+
"utf8"
|
|
398
|
+
);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
if (!isNotFoundError(error)) {
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
return readFile(fileURLToPath(new URL("./app/index.html", import.meta.url)), "utf8");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function readBundledUiAsset(asset) {
|
|
407
|
+
try {
|
|
408
|
+
return await readFile(fileURLToPath(new URL(`./assets/${asset}`, import.meta.url)));
|
|
409
|
+
} catch (sourceError) {
|
|
410
|
+
if (!isNotFoundError(sourceError)) {
|
|
411
|
+
throw sourceError;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
return await readFile(fileURLToPath(new URL(`./ui/assets/${asset}`, import.meta.url)));
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (!isNotFoundError(error)) {
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
return await readFile(
|
|
422
|
+
fileURLToPath(new URL(`../../dist/ui/assets/${asset}`, import.meta.url))
|
|
423
|
+
);
|
|
424
|
+
} catch (fallbackError) {
|
|
425
|
+
if (!isNotFoundError(fallbackError)) {
|
|
426
|
+
throw fallbackError;
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
async function readBundledLegacyStylesheet() {
|
|
433
|
+
try {
|
|
434
|
+
return await readFile(fileURLToPath(new URL("./ui/styles.css", import.meta.url)), "utf8");
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if (!isNotFoundError(error)) {
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
return await readFile(
|
|
441
|
+
fileURLToPath(new URL("../../dist/ui/styles.css", import.meta.url)),
|
|
442
|
+
"utf8"
|
|
443
|
+
);
|
|
444
|
+
} catch (fallbackError) {
|
|
445
|
+
if (!isNotFoundError(fallbackError)) {
|
|
446
|
+
throw fallbackError;
|
|
447
|
+
}
|
|
448
|
+
return "";
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function isNotFoundError(error) {
|
|
453
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
454
|
+
}
|
|
455
|
+
function contentTypeForAsset(asset) {
|
|
456
|
+
if (asset.endsWith(".js")) {
|
|
457
|
+
return "text/javascript; charset=utf-8";
|
|
458
|
+
}
|
|
459
|
+
if (asset.endsWith(".css")) {
|
|
460
|
+
return "text/css; charset=utf-8";
|
|
461
|
+
}
|
|
462
|
+
if (asset.endsWith(".svg")) {
|
|
463
|
+
return "image/svg+xml";
|
|
464
|
+
}
|
|
465
|
+
if (asset.endsWith(".png")) {
|
|
466
|
+
return "image/png";
|
|
467
|
+
}
|
|
468
|
+
if (asset.endsWith(".woff2")) {
|
|
469
|
+
return "font/woff2";
|
|
470
|
+
}
|
|
471
|
+
return "application/octet-stream";
|
|
472
|
+
}
|
|
473
|
+
function escapeHtml(value) {
|
|
474
|
+
return value.replaceAll("&", "&").replaceAll('"', """).replaceAll("<", "<").replaceAll(">", ">");
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/runtime/approvals.ts
|
|
478
|
+
import { createHook, parseToolArgs } from "@anvia/core";
|
|
479
|
+
|
|
480
|
+
// src/runtime/shared.ts
|
|
481
|
+
import { join } from "path";
|
|
482
|
+
|
|
483
|
+
// src/storage/sqlite-store.ts
|
|
484
|
+
import { mkdirSync } from "fs";
|
|
485
|
+
import { createRequire } from "module";
|
|
486
|
+
import { dirname, resolve } from "path";
|
|
487
|
+
var { DatabaseSync } = createRequire(import.meta.url)(
|
|
488
|
+
"node:sqlite"
|
|
489
|
+
);
|
|
490
|
+
function createSqliteSessionStore(options = {}) {
|
|
491
|
+
return new SqliteSessionStore(options.path ?? ":memory:");
|
|
492
|
+
}
|
|
493
|
+
var SqliteSessionStore = class {
|
|
494
|
+
constructor(path) {
|
|
495
|
+
this.path = path;
|
|
496
|
+
}
|
|
497
|
+
path;
|
|
498
|
+
kind = "sqlite";
|
|
499
|
+
db;
|
|
500
|
+
listSessions(options) {
|
|
501
|
+
const db = this.database();
|
|
502
|
+
const agentClause = options.agentId === void 0 ? "" : "WHERE agent_id = $agentId";
|
|
503
|
+
const rows = db.prepare(
|
|
504
|
+
`SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
|
|
505
|
+
FROM runner_sessions
|
|
506
|
+
${agentClause}
|
|
507
|
+
ORDER BY updated_at DESC
|
|
508
|
+
LIMIT $limit`
|
|
509
|
+
).all({
|
|
510
|
+
$agentId: options.agentId ?? null,
|
|
511
|
+
$limit: options.limit
|
|
512
|
+
});
|
|
513
|
+
return rows.map(toSessionSummary);
|
|
514
|
+
}
|
|
515
|
+
createSession(input) {
|
|
516
|
+
const db = this.database();
|
|
517
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
518
|
+
db.prepare(
|
|
519
|
+
`INSERT INTO runner_sessions (
|
|
520
|
+
id,
|
|
521
|
+
agent_id,
|
|
522
|
+
title,
|
|
523
|
+
metadata_json,
|
|
524
|
+
messages_json,
|
|
525
|
+
transcript_json,
|
|
526
|
+
created_at,
|
|
527
|
+
updated_at
|
|
528
|
+
) VALUES ($id, $agentId, $title, $metadata, '[]', '[]', $now, $now)`
|
|
529
|
+
).run({
|
|
530
|
+
$id: input.id,
|
|
531
|
+
$agentId: input.agentId,
|
|
532
|
+
$title: input.title ?? null,
|
|
533
|
+
$metadata: input.metadata === void 0 ? null : JSON.stringify(input.metadata),
|
|
534
|
+
$now: now
|
|
535
|
+
});
|
|
536
|
+
return {
|
|
537
|
+
id: input.id,
|
|
538
|
+
agentId: input.agentId,
|
|
539
|
+
...input.title === void 0 ? {} : { title: input.title },
|
|
540
|
+
createdAt: now,
|
|
541
|
+
updatedAt: now,
|
|
542
|
+
messageCount: 0,
|
|
543
|
+
...input.metadata === void 0 ? {} : { metadata: input.metadata }
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
getSession(id) {
|
|
547
|
+
const db = this.database();
|
|
548
|
+
const row = db.prepare(
|
|
549
|
+
`SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
|
|
550
|
+
FROM runner_sessions
|
|
551
|
+
WHERE id = $id`
|
|
552
|
+
).get({ $id: id });
|
|
553
|
+
return row === void 0 ? void 0 : toSession(row);
|
|
554
|
+
}
|
|
555
|
+
appendSessionRun(input) {
|
|
556
|
+
const db = this.database();
|
|
557
|
+
try {
|
|
558
|
+
db.exec("BEGIN IMMEDIATE");
|
|
559
|
+
const row = db.prepare(
|
|
560
|
+
`SELECT id, agent_id, title, metadata_json, messages_json, transcript_json, created_at, updated_at
|
|
561
|
+
FROM runner_sessions
|
|
562
|
+
WHERE id = $id`
|
|
563
|
+
).get({ $id: input.id });
|
|
564
|
+
if (row === void 0) {
|
|
565
|
+
db.exec("ROLLBACK");
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
const current = toSession(row);
|
|
569
|
+
const messages = [...current.messages, ...input.messages];
|
|
570
|
+
const transcript = renumberTranscript([...current.transcript, ...input.transcript]);
|
|
571
|
+
const title = current.title ?? input.title;
|
|
572
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
573
|
+
db.prepare(
|
|
574
|
+
`UPDATE runner_sessions
|
|
575
|
+
SET title = $title,
|
|
576
|
+
messages_json = $messages,
|
|
577
|
+
transcript_json = $transcript,
|
|
578
|
+
updated_at = $updatedAt
|
|
579
|
+
WHERE id = $id`
|
|
580
|
+
).run({
|
|
581
|
+
$id: input.id,
|
|
582
|
+
$title: title ?? null,
|
|
583
|
+
$messages: JSON.stringify(messages),
|
|
584
|
+
$transcript: JSON.stringify(transcript),
|
|
585
|
+
$updatedAt: updatedAt
|
|
586
|
+
});
|
|
587
|
+
db.exec("COMMIT");
|
|
588
|
+
return {
|
|
589
|
+
...current,
|
|
590
|
+
...title === void 0 ? {} : { title },
|
|
591
|
+
updatedAt,
|
|
592
|
+
messageCount: messages.length,
|
|
593
|
+
messages,
|
|
594
|
+
transcript
|
|
595
|
+
};
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (db.isTransaction) {
|
|
598
|
+
db.exec("ROLLBACK");
|
|
599
|
+
}
|
|
600
|
+
throw error;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
deleteSession(id) {
|
|
604
|
+
const db = this.database();
|
|
605
|
+
try {
|
|
606
|
+
db.exec("BEGIN IMMEDIATE");
|
|
607
|
+
db.prepare("DELETE FROM runner_traces WHERE session_id = $id").run({ $id: id });
|
|
608
|
+
const result = db.prepare("DELETE FROM runner_sessions WHERE id = $id").run({ $id: id });
|
|
609
|
+
db.exec("COMMIT");
|
|
610
|
+
return Number(result.changes) > 0;
|
|
611
|
+
} catch (error) {
|
|
612
|
+
if (db.isTransaction) {
|
|
613
|
+
db.exec("ROLLBACK");
|
|
614
|
+
}
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
listTraces(options) {
|
|
619
|
+
const db = this.database();
|
|
620
|
+
const filters = [];
|
|
621
|
+
const values = {
|
|
622
|
+
$limit: options.limit
|
|
623
|
+
};
|
|
624
|
+
if (options.agentId !== void 0) {
|
|
625
|
+
filters.push(
|
|
626
|
+
"(s.agent_id = $agentId OR json_extract(t.metadata_json, '$.metadata.agentId') = $agentId)"
|
|
627
|
+
);
|
|
628
|
+
values.$agentId = options.agentId;
|
|
629
|
+
}
|
|
630
|
+
if (options.sessionId !== void 0) {
|
|
631
|
+
filters.push("t.session_id = $sessionId");
|
|
632
|
+
values.$sessionId = options.sessionId;
|
|
633
|
+
}
|
|
634
|
+
if (options.status !== void 0) {
|
|
635
|
+
filters.push("t.status = $status");
|
|
636
|
+
values.$status = options.status;
|
|
637
|
+
}
|
|
638
|
+
const whereClause = filters.length === 0 ? "" : `WHERE ${filters.join(" AND ")}`;
|
|
639
|
+
const rows = db.prepare(
|
|
640
|
+
`SELECT t.id, t.session_id, t.name, t.status, t.trace_json, t.input_json, t.output,
|
|
641
|
+
t.error_json, t.usage_json, t.metadata_json, t.observations_json,
|
|
642
|
+
t.started_at, t.ended_at, t.duration_ms
|
|
643
|
+
FROM runner_traces t
|
|
644
|
+
LEFT JOIN runner_sessions s ON s.id = t.session_id
|
|
645
|
+
${whereClause}
|
|
646
|
+
ORDER BY t.started_at DESC
|
|
647
|
+
LIMIT $limit`
|
|
648
|
+
).all(values);
|
|
649
|
+
return rows.map(toTraceSummary);
|
|
650
|
+
}
|
|
651
|
+
listSessionTraces(options) {
|
|
652
|
+
const db = this.database();
|
|
653
|
+
const rows = db.prepare(
|
|
654
|
+
`SELECT id, session_id, name, status, trace_json, input_json, output, error_json,
|
|
655
|
+
usage_json, metadata_json, observations_json, started_at, ended_at, duration_ms
|
|
656
|
+
FROM runner_traces
|
|
657
|
+
WHERE session_id = $sessionId
|
|
658
|
+
ORDER BY started_at DESC
|
|
659
|
+
LIMIT $limit`
|
|
660
|
+
).all({
|
|
661
|
+
$sessionId: options.sessionId,
|
|
662
|
+
$limit: options.limit
|
|
663
|
+
});
|
|
664
|
+
return rows.map(toTraceSummary);
|
|
665
|
+
}
|
|
666
|
+
getTrace(id) {
|
|
667
|
+
const db = this.database();
|
|
668
|
+
const row = db.prepare(
|
|
669
|
+
`SELECT id, session_id, name, status, trace_json, input_json, output, error_json,
|
|
670
|
+
usage_json, metadata_json, observations_json, started_at, ended_at, duration_ms
|
|
671
|
+
FROM runner_traces
|
|
672
|
+
WHERE id = $id`
|
|
673
|
+
).get({ $id: id });
|
|
674
|
+
return row === void 0 ? void 0 : toTrace(row);
|
|
675
|
+
}
|
|
676
|
+
saveTrace(trace) {
|
|
677
|
+
const db = this.database();
|
|
678
|
+
db.prepare(
|
|
679
|
+
`INSERT INTO runner_traces (
|
|
680
|
+
id,
|
|
681
|
+
session_id,
|
|
682
|
+
name,
|
|
683
|
+
status,
|
|
684
|
+
trace_json,
|
|
685
|
+
input_json,
|
|
686
|
+
output,
|
|
687
|
+
error_json,
|
|
688
|
+
usage_json,
|
|
689
|
+
metadata_json,
|
|
690
|
+
observations_json,
|
|
691
|
+
started_at,
|
|
692
|
+
ended_at,
|
|
693
|
+
duration_ms
|
|
694
|
+
) VALUES (
|
|
695
|
+
$id,
|
|
696
|
+
$sessionId,
|
|
697
|
+
$name,
|
|
698
|
+
$status,
|
|
699
|
+
$trace,
|
|
700
|
+
$input,
|
|
701
|
+
$output,
|
|
702
|
+
$error,
|
|
703
|
+
$usage,
|
|
704
|
+
$metadata,
|
|
705
|
+
$observations,
|
|
706
|
+
$startedAt,
|
|
707
|
+
$endedAt,
|
|
708
|
+
$durationMs
|
|
709
|
+
)
|
|
710
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
711
|
+
session_id = excluded.session_id,
|
|
712
|
+
name = excluded.name,
|
|
713
|
+
status = excluded.status,
|
|
714
|
+
trace_json = excluded.trace_json,
|
|
715
|
+
input_json = excluded.input_json,
|
|
716
|
+
output = excluded.output,
|
|
717
|
+
error_json = excluded.error_json,
|
|
718
|
+
usage_json = excluded.usage_json,
|
|
719
|
+
metadata_json = excluded.metadata_json,
|
|
720
|
+
observations_json = excluded.observations_json,
|
|
721
|
+
started_at = excluded.started_at,
|
|
722
|
+
ended_at = excluded.ended_at,
|
|
723
|
+
duration_ms = excluded.duration_ms`
|
|
724
|
+
).run({
|
|
725
|
+
$id: trace.id,
|
|
726
|
+
$sessionId: trace.sessionId,
|
|
727
|
+
$name: trace.name ?? null,
|
|
728
|
+
$status: trace.status,
|
|
729
|
+
$trace: trace.trace === void 0 ? null : JSON.stringify(trace.trace),
|
|
730
|
+
$input: trace.input === void 0 ? null : JSON.stringify(trace.input),
|
|
731
|
+
$output: trace.output ?? null,
|
|
732
|
+
$error: trace.error === void 0 ? null : JSON.stringify(trace.error),
|
|
733
|
+
$usage: trace.usage === void 0 ? null : JSON.stringify(trace.usage),
|
|
734
|
+
$metadata: trace.metadata === void 0 ? null : JSON.stringify(trace.metadata),
|
|
735
|
+
$observations: JSON.stringify(trace.observations),
|
|
736
|
+
$startedAt: trace.startedAt,
|
|
737
|
+
$endedAt: trace.endedAt ?? null,
|
|
738
|
+
$durationMs: trace.durationMs ?? null
|
|
739
|
+
});
|
|
740
|
+
return trace;
|
|
741
|
+
}
|
|
742
|
+
database() {
|
|
743
|
+
if (this.db !== void 0) {
|
|
744
|
+
return this.db;
|
|
745
|
+
}
|
|
746
|
+
if (this.path !== ":memory:") {
|
|
747
|
+
mkdirSync(dirname(resolve(this.path)), { recursive: true });
|
|
748
|
+
}
|
|
749
|
+
const db = new DatabaseSync(this.path, {
|
|
750
|
+
allowUnknownNamedParameters: true,
|
|
751
|
+
timeout: 5e3
|
|
752
|
+
});
|
|
753
|
+
db.exec(`
|
|
754
|
+
PRAGMA journal_mode = WAL;
|
|
755
|
+
PRAGMA foreign_keys = ON;
|
|
756
|
+
CREATE TABLE IF NOT EXISTS runner_sessions (
|
|
757
|
+
id TEXT PRIMARY KEY,
|
|
758
|
+
agent_id TEXT NOT NULL,
|
|
759
|
+
title TEXT,
|
|
760
|
+
metadata_json TEXT,
|
|
761
|
+
messages_json TEXT NOT NULL,
|
|
762
|
+
transcript_json TEXT NOT NULL,
|
|
763
|
+
created_at TEXT NOT NULL,
|
|
764
|
+
updated_at TEXT NOT NULL
|
|
765
|
+
) STRICT;
|
|
766
|
+
CREATE INDEX IF NOT EXISTS runner_sessions_agent_updated_idx
|
|
767
|
+
ON runner_sessions(agent_id, updated_at DESC);
|
|
768
|
+
CREATE TABLE IF NOT EXISTS runner_traces (
|
|
769
|
+
id TEXT PRIMARY KEY,
|
|
770
|
+
session_id TEXT NOT NULL,
|
|
771
|
+
name TEXT,
|
|
772
|
+
status TEXT NOT NULL,
|
|
773
|
+
trace_json TEXT,
|
|
774
|
+
input_json TEXT,
|
|
775
|
+
output TEXT,
|
|
776
|
+
error_json TEXT,
|
|
777
|
+
usage_json TEXT,
|
|
778
|
+
metadata_json TEXT,
|
|
779
|
+
observations_json TEXT NOT NULL,
|
|
780
|
+
started_at TEXT NOT NULL,
|
|
781
|
+
ended_at TEXT,
|
|
782
|
+
duration_ms INTEGER
|
|
783
|
+
) STRICT;
|
|
784
|
+
CREATE INDEX IF NOT EXISTS runner_traces_session_started_idx
|
|
785
|
+
ON runner_traces(session_id, started_at DESC);
|
|
786
|
+
`);
|
|
787
|
+
this.db = db;
|
|
788
|
+
return db;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
function toSession(row) {
|
|
792
|
+
const summary = toSessionSummary(row);
|
|
793
|
+
return {
|
|
794
|
+
...summary,
|
|
795
|
+
messages: parseJsonArray(row.messages_json),
|
|
796
|
+
transcript: renumberTranscript(parseJsonArray(row.transcript_json))
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function toSessionSummary(row) {
|
|
800
|
+
const messages = parseJsonArray(row.messages_json);
|
|
801
|
+
const metadata = parseJsonValue(row.metadata_json);
|
|
802
|
+
return {
|
|
803
|
+
id: row.id,
|
|
804
|
+
agentId: row.agent_id,
|
|
805
|
+
...row.title === null ? {} : { title: row.title },
|
|
806
|
+
createdAt: row.created_at,
|
|
807
|
+
updatedAt: row.updated_at,
|
|
808
|
+
messageCount: messages.length,
|
|
809
|
+
...metadata === void 0 ? {} : { metadata }
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function toTrace(row) {
|
|
813
|
+
const trace = parseJsonValue(row.trace_json);
|
|
814
|
+
const input = parseJsonValue(row.input_json);
|
|
815
|
+
return {
|
|
816
|
+
...toTraceSummary(row),
|
|
817
|
+
...trace === void 0 ? {} : { trace },
|
|
818
|
+
...input === void 0 ? {} : { input },
|
|
819
|
+
observations: parseJsonArray(row.observations_json)
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function toTraceSummary(row) {
|
|
823
|
+
const observations = parseJsonArray(row.observations_json);
|
|
824
|
+
const error = parseJsonValue(row.error_json);
|
|
825
|
+
const usage = parseJsonValue(row.usage_json);
|
|
826
|
+
const metadata = parseJsonValue(row.metadata_json);
|
|
827
|
+
return {
|
|
828
|
+
id: row.id,
|
|
829
|
+
sessionId: row.session_id,
|
|
830
|
+
...row.name === null ? {} : { name: row.name },
|
|
831
|
+
status: row.status,
|
|
832
|
+
startedAt: row.started_at,
|
|
833
|
+
...row.ended_at === null ? {} : { endedAt: row.ended_at },
|
|
834
|
+
...row.duration_ms === null ? {} : { durationMs: row.duration_ms },
|
|
835
|
+
...row.output === null ? {} : { output: row.output },
|
|
836
|
+
...error === void 0 ? {} : { error },
|
|
837
|
+
...usage === void 0 ? {} : { usage },
|
|
838
|
+
...metadata === void 0 ? {} : { metadata },
|
|
839
|
+
observationCount: observations.length
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function parseJsonArray(value) {
|
|
843
|
+
const parsed = JSON.parse(value);
|
|
844
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
845
|
+
}
|
|
846
|
+
function parseJsonValue(value) {
|
|
847
|
+
if (value === null) {
|
|
848
|
+
return void 0;
|
|
849
|
+
}
|
|
850
|
+
return JSON.parse(value);
|
|
851
|
+
}
|
|
852
|
+
function renumberTranscript(entries) {
|
|
853
|
+
return entries.map((entry, entryId) => ({ ...entry, entryId }));
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/runtime/shared.ts
|
|
857
|
+
function resolveStores(options) {
|
|
858
|
+
const defaultPath = process.env.ANVIA_STUDIO_DB ?? process.env.AION_STUDIO_DB ?? join(process.cwd(), ".anvia-studio", `${safeFileName(runnerId(options))}.sqlite`);
|
|
859
|
+
const defaultStore = createSqliteSessionStore({ path: defaultPath });
|
|
860
|
+
const sessions = resolveSessionStore(options, defaultStore);
|
|
861
|
+
const traces = resolveTraceStore(options, sessions, defaultStore);
|
|
862
|
+
return {
|
|
863
|
+
...sessions === void 0 ? {} : { sessions },
|
|
864
|
+
...traces === void 0 ? {} : { traces }
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
function resolveSessionStore(options, defaultStore) {
|
|
868
|
+
if (options.stores?.sessions === false) {
|
|
869
|
+
return void 0;
|
|
870
|
+
}
|
|
871
|
+
if (options.stores?.sessions !== void 0) {
|
|
872
|
+
return options.stores.sessions;
|
|
873
|
+
}
|
|
874
|
+
return defaultStore;
|
|
875
|
+
}
|
|
876
|
+
function resolveTraceStore(options, sessionStore, defaultStore) {
|
|
877
|
+
if (options.stores?.traces !== void 0) {
|
|
878
|
+
return options.stores.traces;
|
|
879
|
+
}
|
|
880
|
+
if (sessionStore === void 0) {
|
|
881
|
+
return void 0;
|
|
882
|
+
}
|
|
883
|
+
if (isTraceStore(sessionStore)) {
|
|
884
|
+
return sessionStore;
|
|
885
|
+
}
|
|
886
|
+
return defaultStore;
|
|
887
|
+
}
|
|
888
|
+
function isTraceStore(store) {
|
|
889
|
+
const candidate = store;
|
|
890
|
+
return typeof candidate.listSessionTraces === "function" && typeof candidate.getTrace === "function" && typeof candidate.saveTrace === "function";
|
|
891
|
+
}
|
|
892
|
+
function unsupportedCapabilities(stores) {
|
|
893
|
+
return [
|
|
894
|
+
...stores.sessions === void 0 ? ["sessions"] : [],
|
|
895
|
+
...stores.traces === void 0 ? ["traces"] : []
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
function normalizeAgents(agents) {
|
|
899
|
+
const ids = /* @__PURE__ */ new Set();
|
|
900
|
+
return agents.map((agent) => {
|
|
901
|
+
const id = agent.id.trim();
|
|
902
|
+
if (id.length === 0) {
|
|
903
|
+
throw new Error("Studio agent id cannot be empty");
|
|
904
|
+
}
|
|
905
|
+
if (ids.has(id)) {
|
|
906
|
+
throw new Error(`Duplicate runner agent id: ${id}`);
|
|
907
|
+
}
|
|
908
|
+
ids.add(id);
|
|
909
|
+
return { ...agent, id };
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
function buildConfig(options, agents, stores) {
|
|
913
|
+
return {
|
|
914
|
+
id: runnerId(options),
|
|
915
|
+
...options.name === void 0 ? {} : { name: options.name },
|
|
916
|
+
...options.description === void 0 ? {} : { description: options.description },
|
|
917
|
+
...options.version === void 0 ? {} : { version: options.version },
|
|
918
|
+
agents: agents.map(agentConfig),
|
|
919
|
+
chat: {
|
|
920
|
+
quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
|
|
921
|
+
},
|
|
922
|
+
capabilities: capabilityConfig(options, agents, stores),
|
|
923
|
+
unsupportedCapabilities: unsupportedCapabilities(stores)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function runnerId(options) {
|
|
927
|
+
return options.id ?? "anvia-studio";
|
|
928
|
+
}
|
|
929
|
+
function agentConfig(agent) {
|
|
930
|
+
const name = agent.name ?? agent.agent.name;
|
|
931
|
+
const description = agent.description ?? agent.agent.description;
|
|
932
|
+
return {
|
|
933
|
+
id: agent.id,
|
|
934
|
+
...name === void 0 ? {} : { name },
|
|
935
|
+
...description === void 0 ? {} : { description },
|
|
936
|
+
quickPrompts: agent.quickPrompts ?? [],
|
|
937
|
+
...agent.metadata === void 0 ? {} : { metadata: agent.metadata }
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function capabilityConfig(_options, agents, stores) {
|
|
941
|
+
const capabilities = {
|
|
942
|
+
agents: { enabled: true }
|
|
943
|
+
};
|
|
944
|
+
if (stores.sessions !== void 0) {
|
|
945
|
+
capabilities.sessions = { enabled: true };
|
|
946
|
+
}
|
|
947
|
+
if (stores.traces !== void 0) {
|
|
948
|
+
capabilities.traces = { enabled: true };
|
|
949
|
+
}
|
|
950
|
+
if (agents.some((agent) => agent.agent.observers.length > 0)) {
|
|
951
|
+
capabilities.observability = { enabled: true };
|
|
952
|
+
}
|
|
953
|
+
if (agents.some((agent) => agent.agent.toolSet.values().some((tool) => tool.approval))) {
|
|
954
|
+
capabilities.approvals = { enabled: true };
|
|
955
|
+
}
|
|
956
|
+
if (agents.some(
|
|
957
|
+
(agent) => agent.agent.staticContext.length > 0 || agent.agent.dynamicContexts.length > 0 || agent.agent.dynamicTools.length > 0
|
|
958
|
+
)) {
|
|
959
|
+
capabilities.knowledge = { enabled: true };
|
|
960
|
+
}
|
|
961
|
+
return capabilities;
|
|
962
|
+
}
|
|
963
|
+
function optionalQueryString(value) {
|
|
964
|
+
const trimmed = value?.trim();
|
|
965
|
+
return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
|
|
966
|
+
}
|
|
967
|
+
function parseLimit(value) {
|
|
968
|
+
if (value === void 0 || value.trim().length === 0) {
|
|
969
|
+
return 50;
|
|
970
|
+
}
|
|
971
|
+
const limit = Number(value);
|
|
972
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
973
|
+
return void 0;
|
|
974
|
+
}
|
|
975
|
+
return Math.min(limit, 100);
|
|
976
|
+
}
|
|
977
|
+
function parseTraceStatus(value) {
|
|
978
|
+
const status = optionalQueryString(value);
|
|
979
|
+
if (status === void 0) {
|
|
980
|
+
return void 0;
|
|
981
|
+
}
|
|
982
|
+
return status === "running" || status === "success" || status === "error" ? status : false;
|
|
983
|
+
}
|
|
984
|
+
function safeFileName(value) {
|
|
985
|
+
return value.replace(/[^a-zA-Z0-9._-]+/g, "_") || "anvia-studio";
|
|
986
|
+
}
|
|
987
|
+
function isMessageInput(value) {
|
|
988
|
+
return typeof value === "string" || isMessage(value);
|
|
989
|
+
}
|
|
990
|
+
function isMessage(value) {
|
|
991
|
+
if (!isObject(value) || typeof value.role !== "string") {
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
if (value.role === "system") {
|
|
995
|
+
return typeof value.content === "string";
|
|
996
|
+
}
|
|
997
|
+
if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
|
|
998
|
+
return Array.isArray(value.content);
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
function isObject(value) {
|
|
1003
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1004
|
+
}
|
|
1005
|
+
function isJsonObject(value) {
|
|
1006
|
+
return isObject(value) && Object.values(value).every(isJsonValue);
|
|
1007
|
+
}
|
|
1008
|
+
function isJsonValue(value) {
|
|
1009
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1010
|
+
return true;
|
|
1011
|
+
}
|
|
1012
|
+
if (Array.isArray(value)) {
|
|
1013
|
+
return value.every(isJsonValue);
|
|
1014
|
+
}
|
|
1015
|
+
return isJsonObject(value);
|
|
1016
|
+
}
|
|
1017
|
+
function isAgentTraceOptions(value) {
|
|
1018
|
+
if (!isObject(value)) {
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
return optionalString(value.name) && optionalString(value.userId) && optionalString(value.sessionId) && optionalString(value.version) && optionalString(value.traceId) && optionalBoolean(value.failOnObserverError) && optionalStringArray(value.tags) && optionalObject(value.metadata);
|
|
1022
|
+
}
|
|
1023
|
+
function optionalString(value) {
|
|
1024
|
+
return value === void 0 || typeof value === "string";
|
|
1025
|
+
}
|
|
1026
|
+
function optionalBoolean(value) {
|
|
1027
|
+
return value === void 0 || typeof value === "boolean";
|
|
1028
|
+
}
|
|
1029
|
+
function optionalStringArray(value) {
|
|
1030
|
+
return value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
1031
|
+
}
|
|
1032
|
+
function optionalObject(value) {
|
|
1033
|
+
return value === void 0 || isObject(value);
|
|
1034
|
+
}
|
|
1035
|
+
function isNonNegativeInteger(value) {
|
|
1036
|
+
return Number.isInteger(value) && typeof value === "number" && value >= 0;
|
|
1037
|
+
}
|
|
1038
|
+
function isPositiveInteger(value) {
|
|
1039
|
+
return Number.isInteger(value) && typeof value === "number" && value > 0;
|
|
1040
|
+
}
|
|
1041
|
+
function unsupportedCapability(c, capability) {
|
|
1042
|
+
return errorResponse(
|
|
1043
|
+
c,
|
|
1044
|
+
501,
|
|
1045
|
+
"unsupported_capability",
|
|
1046
|
+
`Capability "${capability}" is not implemented by this runner`,
|
|
1047
|
+
{ capability }
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
function errorResponse(c, status, code, message, details) {
|
|
1051
|
+
const body = {
|
|
1052
|
+
error: {
|
|
1053
|
+
code,
|
|
1054
|
+
message
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
if (details !== void 0) {
|
|
1058
|
+
body.error.details = details;
|
|
1059
|
+
}
|
|
1060
|
+
return c.json(body, status);
|
|
1061
|
+
}
|
|
1062
|
+
function serializeError2(error) {
|
|
1063
|
+
if (error instanceof Error) {
|
|
1064
|
+
return {
|
|
1065
|
+
name: error.name,
|
|
1066
|
+
message: error.message,
|
|
1067
|
+
...error.stack === void 0 ? {} : { stack: error.stack }
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
return isJsonValue(error) ? error : String(error);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// src/runtime/approvals.ts
|
|
1074
|
+
function registerApprovalRoutes(app, approvals) {
|
|
1075
|
+
app.get("/approvals", (c) => {
|
|
1076
|
+
const status = parseApprovalStatus(c.req.query("status"));
|
|
1077
|
+
if (status === false) {
|
|
1078
|
+
return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
|
|
1079
|
+
}
|
|
1080
|
+
const options = {};
|
|
1081
|
+
const runId = optionalQueryString(c.req.query("runId"));
|
|
1082
|
+
const agentId = optionalQueryString(c.req.query("agentId"));
|
|
1083
|
+
const sessionId = optionalQueryString(c.req.query("sessionId"));
|
|
1084
|
+
if (status !== void 0) {
|
|
1085
|
+
options.status = status;
|
|
1086
|
+
}
|
|
1087
|
+
if (runId !== void 0) {
|
|
1088
|
+
options.runId = runId;
|
|
1089
|
+
}
|
|
1090
|
+
if (agentId !== void 0) {
|
|
1091
|
+
options.agentId = agentId;
|
|
1092
|
+
}
|
|
1093
|
+
if (sessionId !== void 0) {
|
|
1094
|
+
options.sessionId = sessionId;
|
|
1095
|
+
}
|
|
1096
|
+
return c.json({
|
|
1097
|
+
approvals: approvals.list(options)
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
app.post("/approvals/:approvalId/decision", async (c) => {
|
|
1101
|
+
const body = await parseApprovalDecisionRequest(c);
|
|
1102
|
+
if ("error" in body) {
|
|
1103
|
+
return body.error;
|
|
1104
|
+
}
|
|
1105
|
+
const result = approvals.decide(c.req.param("approvalId"), body);
|
|
1106
|
+
if (result === "missing") {
|
|
1107
|
+
return errorResponse(c, 404, "not_found", "Approval not found");
|
|
1108
|
+
}
|
|
1109
|
+
if (result === "resolved") {
|
|
1110
|
+
return errorResponse(c, 409, "conflict", "Approval is already resolved");
|
|
1111
|
+
}
|
|
1112
|
+
return c.json(result);
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
function parseApprovalStatus(value) {
|
|
1116
|
+
const status = optionalQueryString(value);
|
|
1117
|
+
if (status === void 0) {
|
|
1118
|
+
return void 0;
|
|
1119
|
+
}
|
|
1120
|
+
return status === "pending" || status === "resolved" ? status : false;
|
|
1121
|
+
}
|
|
1122
|
+
async function parseApprovalDecisionRequest(c) {
|
|
1123
|
+
let body;
|
|
1124
|
+
try {
|
|
1125
|
+
body = await c.req.json();
|
|
1126
|
+
} catch {
|
|
1127
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
|
|
1128
|
+
}
|
|
1129
|
+
if (!isObject(body)) {
|
|
1130
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
|
|
1131
|
+
}
|
|
1132
|
+
if (typeof body.approved !== "boolean") {
|
|
1133
|
+
return { error: errorResponse(c, 400, "bad_request", "approved must be a boolean") };
|
|
1134
|
+
}
|
|
1135
|
+
if ("reason" in body && typeof body.reason !== "string") {
|
|
1136
|
+
return { error: errorResponse(c, 400, "bad_request", "reason must be a string") };
|
|
1137
|
+
}
|
|
1138
|
+
return {
|
|
1139
|
+
approved: body.approved,
|
|
1140
|
+
...typeof body.reason === "string" && body.reason.trim().length > 0 ? { reason: body.reason.trim() } : {}
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function createApprovalRuntime() {
|
|
1144
|
+
const approvals = /* @__PURE__ */ new Map();
|
|
1145
|
+
return {
|
|
1146
|
+
approvals,
|
|
1147
|
+
createHook(context) {
|
|
1148
|
+
return createHook({
|
|
1149
|
+
async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
|
|
1150
|
+
const registeredTool = context.getTool(toolName);
|
|
1151
|
+
if (registeredTool?.approval === void 0) {
|
|
1152
|
+
return control.run();
|
|
1153
|
+
}
|
|
1154
|
+
const approval = registeredTool.approval;
|
|
1155
|
+
const rawParsedArgs = parseToolArgs(args);
|
|
1156
|
+
const parsedArgs = registeredTool.parseApprovalArgs?.(rawParsedArgs) ?? rawParsedArgs;
|
|
1157
|
+
const approvalContext = {
|
|
1158
|
+
toolName,
|
|
1159
|
+
args: parsedArgs,
|
|
1160
|
+
rawArgs: args,
|
|
1161
|
+
...toolCallId === void 0 ? {} : { toolCallId },
|
|
1162
|
+
internalCallId,
|
|
1163
|
+
run: {
|
|
1164
|
+
agentId: context.agentId,
|
|
1165
|
+
runId: context.runId,
|
|
1166
|
+
...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
|
|
1167
|
+
...context.metadata === void 0 ? {} : { metadata: context.metadata }
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
const required = await approval.when(approvalContext);
|
|
1171
|
+
if (!required) {
|
|
1172
|
+
return control.run();
|
|
1173
|
+
}
|
|
1174
|
+
const reason = await resolveApprovalText(approval.reason, approvalContext);
|
|
1175
|
+
const rejectMessage = await resolveApprovalText(approval.rejectMessage, approvalContext);
|
|
1176
|
+
const decision = await requestApproval(approvals, context, {
|
|
1177
|
+
toolName,
|
|
1178
|
+
...toolCallId === void 0 ? {} : { toolCallId },
|
|
1179
|
+
internalCallId,
|
|
1180
|
+
args,
|
|
1181
|
+
...reason === void 0 ? {} : { reason },
|
|
1182
|
+
...rejectMessage === void 0 ? {} : { rejectMessage }
|
|
1183
|
+
});
|
|
1184
|
+
return decision.approved ? control.run() : control.skip(decision.reason ?? rejectMessage ?? "Rejected in Anvia Studio.");
|
|
1185
|
+
}
|
|
1186
|
+
});
|
|
1187
|
+
},
|
|
1188
|
+
list(options) {
|
|
1189
|
+
return [...approvals.values()].filter((approval) => {
|
|
1190
|
+
if (options.status === "pending" && approval.status !== "pending") {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
if (options.status === "resolved" && approval.status === "pending") {
|
|
1194
|
+
return false;
|
|
1195
|
+
}
|
|
1196
|
+
if (options.runId !== void 0 && approval.runId !== options.runId) {
|
|
1197
|
+
return false;
|
|
1198
|
+
}
|
|
1199
|
+
if (options.agentId !== void 0 && approval.agentId !== options.agentId) {
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
if (options.sessionId !== void 0 && approval.sessionId !== options.sessionId) {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
return true;
|
|
1206
|
+
}).map(publicApproval);
|
|
1207
|
+
},
|
|
1208
|
+
decide(id, decision) {
|
|
1209
|
+
const approval = approvals.get(id);
|
|
1210
|
+
if (approval === void 0) {
|
|
1211
|
+
return "missing";
|
|
1212
|
+
}
|
|
1213
|
+
if (!isPendingApproval(approval)) {
|
|
1214
|
+
return "resolved";
|
|
1215
|
+
}
|
|
1216
|
+
const reason = decision.approved ? decision.reason : decision.reason ?? approval.rejectMessage ?? "Rejected in Anvia Studio.";
|
|
1217
|
+
const resolved = resolveApproval(approval, decision.approved ? "approved" : "rejected", {
|
|
1218
|
+
...reason === void 0 ? {} : { reason }
|
|
1219
|
+
});
|
|
1220
|
+
approvals.set(id, resolved);
|
|
1221
|
+
approval.emit?.({ type: "tool_approval_result", approval: resolved });
|
|
1222
|
+
approval.resolve({
|
|
1223
|
+
approved: decision.approved,
|
|
1224
|
+
...reason === void 0 ? {} : { reason }
|
|
1225
|
+
});
|
|
1226
|
+
return publicApproval(resolved);
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
async function requestApproval(approvals, context, request) {
|
|
1231
|
+
const id = globalThis.crypto.randomUUID();
|
|
1232
|
+
const approval = {
|
|
1233
|
+
id,
|
|
1234
|
+
runId: context.runId,
|
|
1235
|
+
agentId: context.agentId,
|
|
1236
|
+
...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
|
|
1237
|
+
toolName: request.toolName,
|
|
1238
|
+
...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
|
|
1239
|
+
internalCallId: request.internalCallId,
|
|
1240
|
+
args: request.args,
|
|
1241
|
+
status: "pending",
|
|
1242
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1243
|
+
...request.reason === void 0 ? {} : { reason: request.reason },
|
|
1244
|
+
...request.rejectMessage === void 0 ? {} : { rejectMessage: request.rejectMessage },
|
|
1245
|
+
...context.emit === void 0 ? {} : { emit: context.emit },
|
|
1246
|
+
resolve: () => {
|
|
1247
|
+
}
|
|
1248
|
+
};
|
|
1249
|
+
const decision = new Promise((resolve2) => {
|
|
1250
|
+
approval.resolve = (decision2) => {
|
|
1251
|
+
const current = approvals.get(id);
|
|
1252
|
+
if (!isPendingApproval(current)) {
|
|
1253
|
+
if (current !== void 0) {
|
|
1254
|
+
resolve2({
|
|
1255
|
+
approved: current.status === "approved",
|
|
1256
|
+
...current.reason === void 0 ? {} : { reason: current.reason }
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const reason = decision2.approved ? decision2.reason : decision2.reason ?? request.rejectMessage ?? "Rejected in Anvia Studio.";
|
|
1262
|
+
const resolved = resolveApproval(current, decision2.approved ? "approved" : "rejected", {
|
|
1263
|
+
...reason === void 0 ? {} : { reason }
|
|
1264
|
+
});
|
|
1265
|
+
approvals.set(id, resolved);
|
|
1266
|
+
context.emit?.({ type: "tool_approval_result", approval: resolved });
|
|
1267
|
+
resolve2({
|
|
1268
|
+
approved: decision2.approved,
|
|
1269
|
+
...reason === void 0 ? {} : { reason }
|
|
1270
|
+
});
|
|
1271
|
+
};
|
|
1272
|
+
});
|
|
1273
|
+
approvals.set(id, approval);
|
|
1274
|
+
context.emit?.({ type: "tool_approval_request", approval: publicApproval(approval) });
|
|
1275
|
+
return decision;
|
|
1276
|
+
}
|
|
1277
|
+
async function resolveApprovalText(value, context) {
|
|
1278
|
+
return typeof value === "function" ? value(context) : value;
|
|
1279
|
+
}
|
|
1280
|
+
function isPendingApproval(approval) {
|
|
1281
|
+
return approval !== void 0 && approval.status === "pending" && "resolve" in approval;
|
|
1282
|
+
}
|
|
1283
|
+
function resolveApproval(approval, status, options = {}) {
|
|
1284
|
+
return publicApproval({
|
|
1285
|
+
...approval,
|
|
1286
|
+
status,
|
|
1287
|
+
resolvedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1288
|
+
...options.reason === void 0 ? {} : { reason: options.reason }
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
function publicApproval(approval) {
|
|
1292
|
+
const { emit, rejectMessage, resolve: resolve2, ...rest } = approval;
|
|
1293
|
+
void emit;
|
|
1294
|
+
void rejectMessage;
|
|
1295
|
+
void resolve2;
|
|
1296
|
+
return rest;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// src/runtime/json.ts
|
|
1300
|
+
function toJsonValue2(value) {
|
|
1301
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
1302
|
+
return value;
|
|
1303
|
+
}
|
|
1304
|
+
if (value === void 0) {
|
|
1305
|
+
return null;
|
|
1306
|
+
}
|
|
1307
|
+
if (Array.isArray(value)) {
|
|
1308
|
+
return value.map((item) => toJsonValue2(item));
|
|
1309
|
+
}
|
|
1310
|
+
if (typeof value === "object") {
|
|
1311
|
+
return compactJsonObject2(value);
|
|
1312
|
+
}
|
|
1313
|
+
return String(value);
|
|
1314
|
+
}
|
|
1315
|
+
function compactJsonObject2(values) {
|
|
1316
|
+
const entries = Object.entries(values).flatMap(
|
|
1317
|
+
([key, value]) => value === void 0 ? [] : [[key, toJsonValue2(value)]]
|
|
1318
|
+
);
|
|
1319
|
+
return Object.fromEntries(entries);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/runtime/knowledge.ts
|
|
1323
|
+
function registerKnowledgeRoutes(app, props) {
|
|
1324
|
+
app.get("/knowledge", async (c) => {
|
|
1325
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
1326
|
+
if (limit === void 0) {
|
|
1327
|
+
return errorResponse(c, 400, "bad_request", "Invalid limit");
|
|
1328
|
+
}
|
|
1329
|
+
const summary = {
|
|
1330
|
+
agents: props.agents.map(agentKnowledgeConfig),
|
|
1331
|
+
evidence: await recentKnowledgeEvidence(props.traceStore, limit)
|
|
1332
|
+
};
|
|
1333
|
+
return c.json(summary);
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
function agentKnowledgeConfig(agent) {
|
|
1337
|
+
const agentName = agent.name ?? agent.agent.name;
|
|
1338
|
+
return {
|
|
1339
|
+
agentId: agent.id,
|
|
1340
|
+
...agentName === void 0 ? {} : { agentName },
|
|
1341
|
+
sources: [
|
|
1342
|
+
{ kind: "static_context", count: agent.agent.staticContext.length },
|
|
1343
|
+
{ kind: "dynamic_context", count: agent.agent.dynamicContexts.length },
|
|
1344
|
+
{ kind: "dynamic_tools", count: agent.agent.dynamicTools.length }
|
|
1345
|
+
],
|
|
1346
|
+
staticContext: agent.agent.staticContext.map((document) => ({
|
|
1347
|
+
id: document.id,
|
|
1348
|
+
text: document.text,
|
|
1349
|
+
...document.additionalProps === void 0 ? {} : { additionalProps: jsonObjectFromRecord(document.additionalProps) }
|
|
1350
|
+
}))
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
async function recentKnowledgeEvidence(traceStore, limit) {
|
|
1354
|
+
if (traceStore?.listTraces === void 0) {
|
|
1355
|
+
return [];
|
|
1356
|
+
}
|
|
1357
|
+
const store = traceStore;
|
|
1358
|
+
const listTraces = store.listTraces;
|
|
1359
|
+
if (listTraces === void 0) {
|
|
1360
|
+
return [];
|
|
1361
|
+
}
|
|
1362
|
+
const summaries = await listTraces.call(store, { limit });
|
|
1363
|
+
const traces = await Promise.all(
|
|
1364
|
+
summaries.map((summary) => Promise.resolve(store.getTrace(summary.id)).catch(() => void 0))
|
|
1365
|
+
);
|
|
1366
|
+
return traces.flatMap(
|
|
1367
|
+
(trace) => trace === void 0 ? [] : evidenceFromTrace(trace)
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
function evidenceFromTrace(trace) {
|
|
1371
|
+
return trace.observations.flatMap((observation) => {
|
|
1372
|
+
if (observation.kind !== "generation" || !isRecord(observation.input)) {
|
|
1373
|
+
return [];
|
|
1374
|
+
}
|
|
1375
|
+
const documents = Array.isArray(observation.input.documents) ? observation.input.documents.flatMap((document) => evidenceDocument(document)) : [];
|
|
1376
|
+
const tools = Array.isArray(observation.input.tools) ? observation.input.tools.flatMap((tool) => evidenceToolName(tool)) : [];
|
|
1377
|
+
if (documents.length === 0 && tools.length === 0) {
|
|
1378
|
+
return [];
|
|
1379
|
+
}
|
|
1380
|
+
const query = queryFromGenerationInput(observation.input);
|
|
1381
|
+
return [
|
|
1382
|
+
{
|
|
1383
|
+
traceId: trace.id,
|
|
1384
|
+
sessionId: trace.sessionId,
|
|
1385
|
+
observationId: observation.id,
|
|
1386
|
+
observationName: observation.name,
|
|
1387
|
+
turn: observation.turn,
|
|
1388
|
+
startedAt: observation.startedAt,
|
|
1389
|
+
...query === void 0 ? {} : { query },
|
|
1390
|
+
documentCount: documents.length,
|
|
1391
|
+
toolCount: tools.length,
|
|
1392
|
+
documents,
|
|
1393
|
+
tools
|
|
1394
|
+
}
|
|
1395
|
+
];
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
function queryFromGenerationInput(value) {
|
|
1399
|
+
const promptText = messageText(value.prompt);
|
|
1400
|
+
if (promptText.length > 0) {
|
|
1401
|
+
return promptText;
|
|
1402
|
+
}
|
|
1403
|
+
if (Array.isArray(value.chatHistory)) {
|
|
1404
|
+
for (let index = value.chatHistory.length - 1; index >= 0; index -= 1) {
|
|
1405
|
+
const text = messageText(value.chatHistory[index]);
|
|
1406
|
+
if (text.length > 0) {
|
|
1407
|
+
return text;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (Array.isArray(value.history)) {
|
|
1412
|
+
for (let index = value.history.length - 1; index >= 0; index -= 1) {
|
|
1413
|
+
const text = messageText(value.history[index]);
|
|
1414
|
+
if (text.length > 0) {
|
|
1415
|
+
return text;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
return void 0;
|
|
1420
|
+
}
|
|
1421
|
+
function messageText(value) {
|
|
1422
|
+
if (typeof value === "string") {
|
|
1423
|
+
return value.trim();
|
|
1424
|
+
}
|
|
1425
|
+
if (!isRecord(value)) {
|
|
1426
|
+
return "";
|
|
1427
|
+
}
|
|
1428
|
+
if (typeof value.text === "string") {
|
|
1429
|
+
return value.text.trim();
|
|
1430
|
+
}
|
|
1431
|
+
if (typeof value.content === "string") {
|
|
1432
|
+
return value.content.trim();
|
|
1433
|
+
}
|
|
1434
|
+
if (Array.isArray(value.content)) {
|
|
1435
|
+
return value.content.map(contentText).filter(Boolean).join("\n").trim();
|
|
1436
|
+
}
|
|
1437
|
+
return "";
|
|
1438
|
+
}
|
|
1439
|
+
function contentText(value) {
|
|
1440
|
+
if (typeof value === "string") {
|
|
1441
|
+
return value.trim();
|
|
1442
|
+
}
|
|
1443
|
+
if (!isRecord(value)) {
|
|
1444
|
+
return "";
|
|
1445
|
+
}
|
|
1446
|
+
if (typeof value.text === "string") {
|
|
1447
|
+
return value.text.trim();
|
|
1448
|
+
}
|
|
1449
|
+
return "";
|
|
1450
|
+
}
|
|
1451
|
+
function evidenceDocument(value) {
|
|
1452
|
+
if (!isRecord(value)) {
|
|
1453
|
+
return [];
|
|
1454
|
+
}
|
|
1455
|
+
const id = typeof value.id === "string" ? value.id : void 0;
|
|
1456
|
+
const text = typeof value.text === "string" ? value.text : void 0;
|
|
1457
|
+
const additionalProps = isRecord(value.additionalProps) ? jsonObjectFromRecord(value.additionalProps) : void 0;
|
|
1458
|
+
if (id === void 0 && text === void 0 && additionalProps === void 0) {
|
|
1459
|
+
return [];
|
|
1460
|
+
}
|
|
1461
|
+
return [
|
|
1462
|
+
{
|
|
1463
|
+
...id === void 0 ? {} : { id },
|
|
1464
|
+
...text === void 0 ? {} : { text },
|
|
1465
|
+
...additionalProps === void 0 ? {} : { additionalProps }
|
|
1466
|
+
}
|
|
1467
|
+
];
|
|
1468
|
+
}
|
|
1469
|
+
function evidenceToolName(value) {
|
|
1470
|
+
if (!isRecord(value) || typeof value.name !== "string") {
|
|
1471
|
+
return [];
|
|
1472
|
+
}
|
|
1473
|
+
return [value.name];
|
|
1474
|
+
}
|
|
1475
|
+
function jsonObjectFromRecord(value) {
|
|
1476
|
+
return compactJsonObject2(value);
|
|
1477
|
+
}
|
|
1478
|
+
function isRecord(value) {
|
|
1479
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
// src/runtime/questions.ts
|
|
1483
|
+
import { createHook as createHook2, parseToolArgs as parseToolArgs2 } from "@anvia/core";
|
|
1484
|
+
function registerQuestionRoutes(app, questions) {
|
|
1485
|
+
app.get("/questions", (c) => {
|
|
1486
|
+
const status = parseQuestionStatus(c.req.query("status"));
|
|
1487
|
+
if (status === false) {
|
|
1488
|
+
return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
|
|
1489
|
+
}
|
|
1490
|
+
const options = {};
|
|
1491
|
+
const runId = optionalQueryString(c.req.query("runId"));
|
|
1492
|
+
const agentId = optionalQueryString(c.req.query("agentId"));
|
|
1493
|
+
const sessionId = optionalQueryString(c.req.query("sessionId"));
|
|
1494
|
+
if (status !== void 0) {
|
|
1495
|
+
options.status = status;
|
|
1496
|
+
}
|
|
1497
|
+
if (runId !== void 0) {
|
|
1498
|
+
options.runId = runId;
|
|
1499
|
+
}
|
|
1500
|
+
if (agentId !== void 0) {
|
|
1501
|
+
options.agentId = agentId;
|
|
1502
|
+
}
|
|
1503
|
+
if (sessionId !== void 0) {
|
|
1504
|
+
options.sessionId = sessionId;
|
|
1505
|
+
}
|
|
1506
|
+
return c.json({ questions: questions.list(options) });
|
|
1507
|
+
});
|
|
1508
|
+
app.post("/questions/:questionId/answer", async (c) => {
|
|
1509
|
+
const body = await parseQuestionAnswerRequest(c);
|
|
1510
|
+
if ("error" in body) {
|
|
1511
|
+
return body.error;
|
|
1512
|
+
}
|
|
1513
|
+
const result = questions.answer(c.req.param("questionId"), body.answers);
|
|
1514
|
+
if (result === "missing") {
|
|
1515
|
+
return errorResponse(c, 404, "not_found", "Question not found");
|
|
1516
|
+
}
|
|
1517
|
+
if (result === "resolved") {
|
|
1518
|
+
return errorResponse(c, 409, "conflict", "Question is already answered");
|
|
1519
|
+
}
|
|
1520
|
+
return c.json(result);
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
function parseQuestionStatus(value) {
|
|
1524
|
+
const status = optionalQueryString(value);
|
|
1525
|
+
if (status === void 0) {
|
|
1526
|
+
return void 0;
|
|
1527
|
+
}
|
|
1528
|
+
return status === "pending" || status === "resolved" ? status : false;
|
|
1529
|
+
}
|
|
1530
|
+
async function parseQuestionAnswerRequest(c) {
|
|
1531
|
+
let body;
|
|
1532
|
+
try {
|
|
1533
|
+
body = await c.req.json();
|
|
1534
|
+
} catch {
|
|
1535
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
|
|
1536
|
+
}
|
|
1537
|
+
if (!isObject(body)) {
|
|
1538
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
|
|
1539
|
+
}
|
|
1540
|
+
if (!Array.isArray(body.answers)) {
|
|
1541
|
+
return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
|
|
1542
|
+
}
|
|
1543
|
+
const answers = [];
|
|
1544
|
+
for (const answer of body.answers) {
|
|
1545
|
+
if (!isObject(answer)) {
|
|
1546
|
+
return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
|
|
1547
|
+
}
|
|
1548
|
+
if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
|
|
1549
|
+
return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
|
|
1550
|
+
}
|
|
1551
|
+
if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
|
|
1552
|
+
return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
|
|
1553
|
+
}
|
|
1554
|
+
if ("choice" in answer && typeof answer.choice !== "string") {
|
|
1555
|
+
return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
|
|
1556
|
+
}
|
|
1557
|
+
if ("custom" in answer && typeof answer.custom !== "boolean") {
|
|
1558
|
+
return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
|
|
1559
|
+
}
|
|
1560
|
+
answers.push({
|
|
1561
|
+
questionId: answer.questionId.trim(),
|
|
1562
|
+
answer: answer.answer.trim(),
|
|
1563
|
+
...typeof answer.choice === "string" ? { choice: answer.choice } : {},
|
|
1564
|
+
...typeof answer.custom === "boolean" ? { custom: answer.custom } : {}
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
return { answers };
|
|
1568
|
+
}
|
|
1569
|
+
function createQuestionRuntime() {
|
|
1570
|
+
const questions = /* @__PURE__ */ new Map();
|
|
1571
|
+
return {
|
|
1572
|
+
questions,
|
|
1573
|
+
createHook(context) {
|
|
1574
|
+
return createHook2({
|
|
1575
|
+
async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
|
|
1576
|
+
if (toolName !== "ask_question") {
|
|
1577
|
+
return control.run();
|
|
1578
|
+
}
|
|
1579
|
+
const prompts = normalizeQuestionPrompts(parseToolArgs2(args));
|
|
1580
|
+
if ("error" in prompts) {
|
|
1581
|
+
return control.skip(prompts.error);
|
|
1582
|
+
}
|
|
1583
|
+
const answers = await requestQuestion(questions, context, {
|
|
1584
|
+
toolName,
|
|
1585
|
+
...toolCallId === void 0 ? {} : { toolCallId },
|
|
1586
|
+
internalCallId,
|
|
1587
|
+
args,
|
|
1588
|
+
questions: prompts.questions
|
|
1589
|
+
});
|
|
1590
|
+
return control.skip(JSON.stringify({ answers }));
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
},
|
|
1594
|
+
list(options) {
|
|
1595
|
+
return [...questions.values()].filter((question) => {
|
|
1596
|
+
if (options.status === "pending" && question.status !== "pending") {
|
|
1597
|
+
return false;
|
|
1598
|
+
}
|
|
1599
|
+
if (options.status === "resolved" && question.status === "pending") {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
if (options.runId !== void 0 && question.runId !== options.runId) {
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
if (options.agentId !== void 0 && question.agentId !== options.agentId) {
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
|
|
1609
|
+
return false;
|
|
1610
|
+
}
|
|
1611
|
+
return true;
|
|
1612
|
+
}).map(publicQuestion);
|
|
1613
|
+
},
|
|
1614
|
+
answer(id, answers) {
|
|
1615
|
+
const question = questions.get(id);
|
|
1616
|
+
if (question === void 0) {
|
|
1617
|
+
return "missing";
|
|
1618
|
+
}
|
|
1619
|
+
if (!isPendingQuestion(question)) {
|
|
1620
|
+
return "resolved";
|
|
1621
|
+
}
|
|
1622
|
+
const resolved = resolveQuestion(question, answers);
|
|
1623
|
+
questions.set(id, resolved);
|
|
1624
|
+
question.emit?.({ type: "tool_question_result", question: resolved });
|
|
1625
|
+
question.resolve(answers);
|
|
1626
|
+
return publicQuestion(resolved);
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
async function requestQuestion(questions, context, request) {
|
|
1631
|
+
const id = globalThis.crypto.randomUUID();
|
|
1632
|
+
const question = {
|
|
1633
|
+
id,
|
|
1634
|
+
runId: context.runId,
|
|
1635
|
+
agentId: context.agentId,
|
|
1636
|
+
...context.sessionId === void 0 ? {} : { sessionId: context.sessionId },
|
|
1637
|
+
toolName: request.toolName,
|
|
1638
|
+
...request.toolCallId === void 0 ? {} : { callId: request.toolCallId },
|
|
1639
|
+
internalCallId: request.internalCallId,
|
|
1640
|
+
args: request.args,
|
|
1641
|
+
questions: request.questions,
|
|
1642
|
+
status: "pending",
|
|
1643
|
+
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1644
|
+
...context.emit === void 0 ? {} : { emit: context.emit },
|
|
1645
|
+
resolve: () => {
|
|
1646
|
+
}
|
|
1647
|
+
};
|
|
1648
|
+
const answer = new Promise((resolve2) => {
|
|
1649
|
+
question.resolve = (answers) => {
|
|
1650
|
+
resolve2(answers);
|
|
1651
|
+
};
|
|
1652
|
+
});
|
|
1653
|
+
questions.set(id, question);
|
|
1654
|
+
context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
|
|
1655
|
+
return answer;
|
|
1656
|
+
}
|
|
1657
|
+
function normalizeQuestionPrompts(args) {
|
|
1658
|
+
if (!isObject(args)) {
|
|
1659
|
+
return { error: "ask_question requires a JSON object with questions." };
|
|
1660
|
+
}
|
|
1661
|
+
const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
|
|
1662
|
+
if (rawQuestions.length === 0) {
|
|
1663
|
+
return { error: "ask_question requires at least one question." };
|
|
1664
|
+
}
|
|
1665
|
+
const questions = [];
|
|
1666
|
+
for (const [index, question] of rawQuestions.entries()) {
|
|
1667
|
+
const normalized = normalizeQuestionPrompt(question, index);
|
|
1668
|
+
if (normalized === void 0) {
|
|
1669
|
+
return {
|
|
1670
|
+
error: "ask_question requires every question to include text and at least one choice."
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
questions.push(normalized);
|
|
1674
|
+
}
|
|
1675
|
+
return { questions };
|
|
1676
|
+
}
|
|
1677
|
+
function normalizeQuestionPrompt(value, index) {
|
|
1678
|
+
if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
|
|
1679
|
+
return void 0;
|
|
1680
|
+
}
|
|
1681
|
+
const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
|
|
1682
|
+
if (choices.length === 0) {
|
|
1683
|
+
return void 0;
|
|
1684
|
+
}
|
|
1685
|
+
return {
|
|
1686
|
+
id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
|
|
1687
|
+
question: value.question.trim(),
|
|
1688
|
+
choices
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function normalizeQuestionChoice(value) {
|
|
1692
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1693
|
+
return { label: value.trim(), value: value.trim() };
|
|
1694
|
+
}
|
|
1695
|
+
if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
|
|
1696
|
+
return void 0;
|
|
1697
|
+
}
|
|
1698
|
+
return {
|
|
1699
|
+
label: value.label.trim(),
|
|
1700
|
+
value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
|
|
1701
|
+
};
|
|
1702
|
+
}
|
|
1703
|
+
function isPendingQuestion(question) {
|
|
1704
|
+
return question !== void 0 && question.status === "pending" && "resolve" in question;
|
|
1705
|
+
}
|
|
1706
|
+
function resolveQuestion(question, answers) {
|
|
1707
|
+
return publicQuestion({
|
|
1708
|
+
...question,
|
|
1709
|
+
status: "answered",
|
|
1710
|
+
answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1711
|
+
answers
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
function publicQuestion(question) {
|
|
1715
|
+
const { emit, resolve: resolve2, ...rest } = question;
|
|
1716
|
+
void emit;
|
|
1717
|
+
void resolve2;
|
|
1718
|
+
return rest;
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/runtime/runs.ts
|
|
1722
|
+
import { stream as streamResponse } from "hono/streaming";
|
|
1723
|
+
var AsyncEventQueue = class {
|
|
1724
|
+
values = [];
|
|
1725
|
+
resolvers = [];
|
|
1726
|
+
closed = false;
|
|
1727
|
+
push(value) {
|
|
1728
|
+
if (this.closed) {
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
const resolver = this.resolvers.shift();
|
|
1732
|
+
if (resolver !== void 0) {
|
|
1733
|
+
resolver({ done: false, value });
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
this.values.push(value);
|
|
1737
|
+
}
|
|
1738
|
+
close() {
|
|
1739
|
+
this.closed = true;
|
|
1740
|
+
for (const resolver of this.resolvers.splice(0)) {
|
|
1741
|
+
resolver({ done: true, value: void 0 });
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
next() {
|
|
1745
|
+
const value = this.values.shift();
|
|
1746
|
+
if (value !== void 0) {
|
|
1747
|
+
return Promise.resolve({ done: false, value });
|
|
1748
|
+
}
|
|
1749
|
+
if (this.closed) {
|
|
1750
|
+
return Promise.resolve({ done: true, value: void 0 });
|
|
1751
|
+
}
|
|
1752
|
+
return new Promise((resolve2) => this.resolvers.push(resolve2));
|
|
1753
|
+
}
|
|
1754
|
+
};
|
|
1755
|
+
async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
|
|
1756
|
+
const runIterator = runEvents[Symbol.asyncIterator]();
|
|
1757
|
+
let runDone = false;
|
|
1758
|
+
let runNext = runIterator.next();
|
|
1759
|
+
let approvalNext = approvalEvents.next();
|
|
1760
|
+
try {
|
|
1761
|
+
while (runNext !== void 0 || approvalNext !== void 0) {
|
|
1762
|
+
const pending = [];
|
|
1763
|
+
if (runNext !== void 0) {
|
|
1764
|
+
pending.push(runNext.then((value) => ({ source: "run", value })));
|
|
1765
|
+
}
|
|
1766
|
+
if (approvalNext !== void 0) {
|
|
1767
|
+
pending.push(approvalNext.then((value) => ({ source: "approval", value })));
|
|
1768
|
+
}
|
|
1769
|
+
const result = await Promise.race(pending);
|
|
1770
|
+
if (result.source === "run") {
|
|
1771
|
+
if (result.value.done === true) {
|
|
1772
|
+
runDone = true;
|
|
1773
|
+
runNext = void 0;
|
|
1774
|
+
approvalEvents.close();
|
|
1775
|
+
} else {
|
|
1776
|
+
runNext = runIterator.next();
|
|
1777
|
+
yield result.value.value;
|
|
1778
|
+
}
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
if (result.value.done === true) {
|
|
1782
|
+
approvalNext = void 0;
|
|
1783
|
+
} else {
|
|
1784
|
+
approvalNext = approvalEvents.next();
|
|
1785
|
+
yield result.value.value;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
} finally {
|
|
1789
|
+
if (!runDone && runIterator.return !== void 0) {
|
|
1790
|
+
await runIterator.return();
|
|
1791
|
+
}
|
|
1792
|
+
approvalEvents.close();
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function streamAgentRunEvents(c, events) {
|
|
1796
|
+
c.header("content-type", "application/x-ndjson; charset=utf-8");
|
|
1797
|
+
c.header("cache-control", "no-cache, no-transform");
|
|
1798
|
+
c.header("connection", "keep-alive");
|
|
1799
|
+
c.header("transfer-encoding", "chunked");
|
|
1800
|
+
c.header("x-accel-buffering", "no");
|
|
1801
|
+
return streamResponse(
|
|
1802
|
+
c,
|
|
1803
|
+
async (stream) => {
|
|
1804
|
+
for await (const event of events) {
|
|
1805
|
+
await stream.write(`${JSON.stringify(event)}
|
|
1806
|
+
`);
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
async (error, stream) => {
|
|
1810
|
+
await stream.write(`${JSON.stringify({ type: "error", error: serializeError2(error) })}
|
|
1811
|
+
`);
|
|
1812
|
+
}
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
function traceForRun(trace, agentId, session) {
|
|
1816
|
+
const metadata = {
|
|
1817
|
+
...trace?.metadata ?? {},
|
|
1818
|
+
agentId
|
|
1819
|
+
};
|
|
1820
|
+
return {
|
|
1821
|
+
...trace ?? {},
|
|
1822
|
+
metadata,
|
|
1823
|
+
...trace?.sessionId !== void 0 ? { sessionId: trace.sessionId } : session === void 0 ? {} : { sessionId: session.id }
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
async function* persistStreamingSessionRun(props) {
|
|
1827
|
+
const transcript = [messageToTranscriptEntry(props.message, 0)];
|
|
1828
|
+
for await (const event of props.stream) {
|
|
1829
|
+
acceptTranscriptStreamEvent(transcript, event);
|
|
1830
|
+
if (event.type === "final") {
|
|
1831
|
+
const nextSession = await props.store.appendSessionRun({
|
|
1832
|
+
id: props.session.id,
|
|
1833
|
+
...optionalTitle(props.message),
|
|
1834
|
+
messages: event.messages,
|
|
1835
|
+
transcript
|
|
1836
|
+
});
|
|
1837
|
+
if (nextSession === void 0) {
|
|
1838
|
+
throw new Error("Session not found");
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
yield event;
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
function acceptTranscriptStreamEvent(transcript, event) {
|
|
1845
|
+
if (event.type === "text_delta") {
|
|
1846
|
+
appendTranscriptAssistantText(transcript, event.delta);
|
|
1847
|
+
}
|
|
1848
|
+
if (event.type === "reasoning_delta") {
|
|
1849
|
+
appendTranscriptReasoningText(transcript, event.delta, event.id);
|
|
1850
|
+
}
|
|
1851
|
+
if (event.type === "tool_call") {
|
|
1852
|
+
transcript.push({
|
|
1853
|
+
entryId: transcript.length,
|
|
1854
|
+
kind: "tool",
|
|
1855
|
+
toolName: event.toolCall.function.name,
|
|
1856
|
+
callId: event.toolCall.callId ?? event.toolCall.id,
|
|
1857
|
+
args: formatJson(event.toolCall.function.arguments)
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
if (event.type === "tool_result") {
|
|
1861
|
+
const matched = findTranscriptToolEntry(transcript, event.toolName, event.toolCallId);
|
|
1862
|
+
if (matched === void 0) {
|
|
1863
|
+
transcript.push({
|
|
1864
|
+
entryId: transcript.length,
|
|
1865
|
+
kind: "tool",
|
|
1866
|
+
toolName: event.toolName,
|
|
1867
|
+
...event.toolCallId === void 0 ? {} : { callId: event.toolCallId },
|
|
1868
|
+
args: event.args,
|
|
1869
|
+
result: event.result
|
|
1870
|
+
});
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
matched.args = matched.args ?? event.args;
|
|
1874
|
+
matched.result = event.result;
|
|
1875
|
+
}
|
|
1876
|
+
if (event.type === "tool_approval_request") {
|
|
1877
|
+
const matched = findTranscriptToolEntry(
|
|
1878
|
+
transcript,
|
|
1879
|
+
event.approval.toolName,
|
|
1880
|
+
approvalCallId(event.approval)
|
|
1881
|
+
);
|
|
1882
|
+
if (matched !== void 0) {
|
|
1883
|
+
matched.approval = {
|
|
1884
|
+
id: event.approval.id,
|
|
1885
|
+
status: event.approval.status,
|
|
1886
|
+
requestedAt: event.approval.requestedAt
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (event.type === "tool_approval_result") {
|
|
1891
|
+
const matched = findTranscriptToolEntry(
|
|
1892
|
+
transcript,
|
|
1893
|
+
event.approval.toolName,
|
|
1894
|
+
approvalCallId(event.approval)
|
|
1895
|
+
);
|
|
1896
|
+
if (matched !== void 0) {
|
|
1897
|
+
matched.approval = {
|
|
1898
|
+
id: event.approval.id,
|
|
1899
|
+
status: event.approval.status,
|
|
1900
|
+
requestedAt: event.approval.requestedAt,
|
|
1901
|
+
...event.approval.resolvedAt === void 0 ? {} : { resolvedAt: event.approval.resolvedAt },
|
|
1902
|
+
...event.approval.reason === void 0 ? {} : { reason: event.approval.reason }
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
if (event.type === "tool_question_request") {
|
|
1907
|
+
const matched = findTranscriptToolEntry(
|
|
1908
|
+
transcript,
|
|
1909
|
+
event.question.toolName,
|
|
1910
|
+
questionCallId(event.question)
|
|
1911
|
+
);
|
|
1912
|
+
if (matched !== void 0) {
|
|
1913
|
+
matched.question = {
|
|
1914
|
+
id: event.question.id,
|
|
1915
|
+
status: event.question.status,
|
|
1916
|
+
requestedAt: event.question.requestedAt,
|
|
1917
|
+
questions: event.question.questions
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
if (event.type === "tool_question_result") {
|
|
1922
|
+
const matched = findTranscriptToolEntry(
|
|
1923
|
+
transcript,
|
|
1924
|
+
event.question.toolName,
|
|
1925
|
+
questionCallId(event.question)
|
|
1926
|
+
);
|
|
1927
|
+
if (matched !== void 0) {
|
|
1928
|
+
matched.question = {
|
|
1929
|
+
id: event.question.id,
|
|
1930
|
+
status: event.question.status,
|
|
1931
|
+
requestedAt: event.question.requestedAt,
|
|
1932
|
+
...event.question.answeredAt === void 0 ? {} : { answeredAt: event.question.answeredAt },
|
|
1933
|
+
questions: event.question.questions,
|
|
1934
|
+
...event.question.answers === void 0 ? {} : { answers: event.question.answers }
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
if (event.type === "final" && event.trace?.traceId !== void 0) {
|
|
1939
|
+
assignTranscriptTraceId(transcript, event.trace.traceId);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
function approvalCallId(approval) {
|
|
1943
|
+
return approval.callId ?? approval.toolCallId;
|
|
1944
|
+
}
|
|
1945
|
+
function questionCallId(question) {
|
|
1946
|
+
return question.callId ?? question.toolCallId;
|
|
1947
|
+
}
|
|
1948
|
+
function transcriptFromMessages(messages) {
|
|
1949
|
+
const transcript = [];
|
|
1950
|
+
for (const message of messages) {
|
|
1951
|
+
if (message.role === "system") {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
if (message.role === "user") {
|
|
1955
|
+
for (const content of message.content) {
|
|
1956
|
+
if (content.type === "text") {
|
|
1957
|
+
transcript.push({
|
|
1958
|
+
entryId: transcript.length,
|
|
1959
|
+
kind: "message",
|
|
1960
|
+
role: "user",
|
|
1961
|
+
text: content.text
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
if (message.role === "tool") {
|
|
1968
|
+
for (const content of message.content) {
|
|
1969
|
+
transcript.push({
|
|
1970
|
+
entryId: transcript.length,
|
|
1971
|
+
kind: "tool",
|
|
1972
|
+
toolName: "tool_result",
|
|
1973
|
+
callId: content.callId ?? content.id,
|
|
1974
|
+
result: content.content.map((item) => "text" in item ? item.text : "[image]").join("\n")
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1977
|
+
continue;
|
|
1978
|
+
}
|
|
1979
|
+
for (const content of message.content) {
|
|
1980
|
+
if (content.type === "text") {
|
|
1981
|
+
appendTranscriptAssistantText(transcript, content.text);
|
|
1982
|
+
} else if (content.type === "reasoning") {
|
|
1983
|
+
appendTranscriptReasoningText(transcript, content.text, content.id);
|
|
1984
|
+
} else if (content.type === "tool_call") {
|
|
1985
|
+
transcript.push({
|
|
1986
|
+
entryId: transcript.length,
|
|
1987
|
+
kind: "tool",
|
|
1988
|
+
toolName: content.function.name,
|
|
1989
|
+
callId: content.callId ?? content.id,
|
|
1990
|
+
args: formatJson(content.function.arguments)
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return transcript;
|
|
1996
|
+
}
|
|
1997
|
+
function messageToTranscriptEntry(message, entryId) {
|
|
1998
|
+
const role = typeof message === "string" || message.role !== "assistant" ? "user" : "assistant";
|
|
1999
|
+
return {
|
|
2000
|
+
entryId,
|
|
2001
|
+
kind: "message",
|
|
2002
|
+
role,
|
|
2003
|
+
text: extractMessageText(message)
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
function appendTranscriptAssistantText(transcript, delta) {
|
|
2007
|
+
const last = transcript.at(-1);
|
|
2008
|
+
if (last?.kind === "message" && last.role === "assistant") {
|
|
2009
|
+
last.text = `${last.text}${delta}`;
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
transcript.push({
|
|
2013
|
+
entryId: transcript.length,
|
|
2014
|
+
kind: "message",
|
|
2015
|
+
role: "assistant",
|
|
2016
|
+
text: delta
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
2019
|
+
function assignTranscriptTraceId(transcript, traceId) {
|
|
2020
|
+
for (let index = transcript.length - 1; index >= 0; index -= 1) {
|
|
2021
|
+
const entry = transcript[index];
|
|
2022
|
+
if (entry?.kind === "message" && entry.role === "assistant") {
|
|
2023
|
+
transcript[index] = { ...entry, traceId };
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
function appendTranscriptReasoningText(transcript, delta, reasoningId) {
|
|
2029
|
+
const last = transcript.at(-1);
|
|
2030
|
+
if (last?.kind === "reasoning" && (last.reasoningId ?? "") === (reasoningId ?? "")) {
|
|
2031
|
+
last.text = `${last.text}${delta}`;
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
transcript.push({
|
|
2035
|
+
entryId: transcript.length,
|
|
2036
|
+
kind: "reasoning",
|
|
2037
|
+
...reasoningId === void 0 ? {} : { reasoningId },
|
|
2038
|
+
text: delta
|
|
2039
|
+
});
|
|
2040
|
+
}
|
|
2041
|
+
function findTranscriptToolEntry(transcript, toolName, callId) {
|
|
2042
|
+
for (let index = transcript.length - 1; index >= 0; index -= 1) {
|
|
2043
|
+
const entry = transcript[index];
|
|
2044
|
+
if (entry?.kind !== "tool" || entry.toolName !== toolName || entry.result !== void 0) {
|
|
2045
|
+
continue;
|
|
2046
|
+
}
|
|
2047
|
+
if (callId === void 0 || entry.callId === callId) {
|
|
2048
|
+
return entry;
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
return void 0;
|
|
2052
|
+
}
|
|
2053
|
+
function titleFromMessage(message) {
|
|
2054
|
+
const text = extractMessageText(message).replace(/\s+/g, " ").trim();
|
|
2055
|
+
if (text.length === 0) {
|
|
2056
|
+
return void 0;
|
|
2057
|
+
}
|
|
2058
|
+
return text.length > 72 ? `${text.slice(0, 69)}...` : text;
|
|
2059
|
+
}
|
|
2060
|
+
function optionalTitle(message) {
|
|
2061
|
+
const title = titleFromMessage(message);
|
|
2062
|
+
return title === void 0 ? {} : { title };
|
|
2063
|
+
}
|
|
2064
|
+
function extractMessageText(message) {
|
|
2065
|
+
if (typeof message === "string") {
|
|
2066
|
+
return message;
|
|
2067
|
+
}
|
|
2068
|
+
if (message.role === "system") {
|
|
2069
|
+
return message.content;
|
|
2070
|
+
}
|
|
2071
|
+
return message.content.flatMap((item) => {
|
|
2072
|
+
if (item.type === "text" || item.type === "reasoning") {
|
|
2073
|
+
return [item.text];
|
|
2074
|
+
}
|
|
2075
|
+
if (item.type === "tool_call") {
|
|
2076
|
+
return [`${item.function.name}(${formatJson(item.function.arguments)})`];
|
|
2077
|
+
}
|
|
2078
|
+
if (item.type === "tool_result") {
|
|
2079
|
+
return item.content.map((result) => "text" in result ? result.text : "[image]");
|
|
2080
|
+
}
|
|
2081
|
+
return [];
|
|
2082
|
+
}).join("\n");
|
|
2083
|
+
}
|
|
2084
|
+
function formatJson(value) {
|
|
2085
|
+
try {
|
|
2086
|
+
return JSON.stringify(value, null, 2);
|
|
2087
|
+
} catch {
|
|
2088
|
+
return String(value);
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
async function parseRunRequest(c) {
|
|
2092
|
+
let body;
|
|
2093
|
+
try {
|
|
2094
|
+
body = await c.req.json();
|
|
2095
|
+
} catch {
|
|
2096
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
|
|
2097
|
+
}
|
|
2098
|
+
if (!isObject(body)) {
|
|
2099
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
|
|
2100
|
+
}
|
|
2101
|
+
if (!("message" in body) || !isMessageInput(body.message)) {
|
|
2102
|
+
return {
|
|
2103
|
+
error: errorResponse(c, 400, "bad_request", "Request body requires a string or Message")
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
const request = {
|
|
2107
|
+
message: typeof body.message === "string" ? body.message : body.message
|
|
2108
|
+
};
|
|
2109
|
+
if ("history" in body) {
|
|
2110
|
+
if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
|
|
2111
|
+
return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
|
|
2112
|
+
}
|
|
2113
|
+
request.history = body.history;
|
|
2114
|
+
}
|
|
2115
|
+
if ("sessionId" in body) {
|
|
2116
|
+
if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
|
|
2117
|
+
return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
|
|
2118
|
+
}
|
|
2119
|
+
if (request.history !== void 0) {
|
|
2120
|
+
return {
|
|
2121
|
+
error: errorResponse(c, 400, "bad_request", "sessionId cannot be combined with history")
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
request.sessionId = body.sessionId;
|
|
2125
|
+
}
|
|
2126
|
+
if ("stream" in body) {
|
|
2127
|
+
if (typeof body.stream !== "boolean") {
|
|
2128
|
+
return { error: errorResponse(c, 400, "bad_request", "stream must be a boolean") };
|
|
2129
|
+
}
|
|
2130
|
+
request.stream = body.stream;
|
|
2131
|
+
}
|
|
2132
|
+
if ("maxTurns" in body) {
|
|
2133
|
+
if (!isNonNegativeInteger(body.maxTurns)) {
|
|
2134
|
+
return {
|
|
2135
|
+
error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
request.maxTurns = body.maxTurns;
|
|
2139
|
+
}
|
|
2140
|
+
if ("toolConcurrency" in body) {
|
|
2141
|
+
if (!isPositiveInteger(body.toolConcurrency)) {
|
|
2142
|
+
return {
|
|
2143
|
+
error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
request.toolConcurrency = body.toolConcurrency;
|
|
2147
|
+
}
|
|
2148
|
+
if ("metadata" in body) {
|
|
2149
|
+
if (!isJsonObject(body.metadata)) {
|
|
2150
|
+
return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
|
|
2151
|
+
}
|
|
2152
|
+
request.metadata = body.metadata;
|
|
2153
|
+
}
|
|
2154
|
+
if ("trace" in body) {
|
|
2155
|
+
if (!isAgentTraceOptions(body.trace)) {
|
|
2156
|
+
return {
|
|
2157
|
+
error: errorResponse(c, 400, "bad_request", "trace must be an AgentTraceOptions object")
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
request.trace = body.trace;
|
|
2161
|
+
}
|
|
2162
|
+
return request;
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
// src/runtime/sessions.ts
|
|
2166
|
+
function registerSessionRoutes(app, props) {
|
|
2167
|
+
app.get("/sessions", async (c) => {
|
|
2168
|
+
const agentId = optionalQueryString(c.req.query("agentId"));
|
|
2169
|
+
if (agentId !== void 0 && !props.agentMap.has(agentId)) {
|
|
2170
|
+
return errorResponse(c, 404, "not_found", "Agent not found");
|
|
2171
|
+
}
|
|
2172
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
2173
|
+
if (limit === void 0) {
|
|
2174
|
+
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
2175
|
+
}
|
|
2176
|
+
const sessions = await props.sessionStore.listSessions({
|
|
2177
|
+
...agentId === void 0 ? {} : { agentId },
|
|
2178
|
+
limit
|
|
2179
|
+
});
|
|
2180
|
+
return c.json({ sessions });
|
|
2181
|
+
});
|
|
2182
|
+
app.post("/sessions", async (c) => {
|
|
2183
|
+
const body = await parseCreateSessionRequest(c);
|
|
2184
|
+
if ("error" in body) {
|
|
2185
|
+
return body.error;
|
|
2186
|
+
}
|
|
2187
|
+
if (!props.agentMap.has(body.agentId)) {
|
|
2188
|
+
return errorResponse(c, 404, "not_found", "Agent not found");
|
|
2189
|
+
}
|
|
2190
|
+
const session = await props.sessionStore.createSession({
|
|
2191
|
+
id: globalThis.crypto.randomUUID(),
|
|
2192
|
+
agentId: body.agentId,
|
|
2193
|
+
...body.title === void 0 ? {} : { title: body.title },
|
|
2194
|
+
...body.metadata === void 0 ? {} : { metadata: body.metadata }
|
|
2195
|
+
});
|
|
2196
|
+
return c.json(session, 201);
|
|
2197
|
+
});
|
|
2198
|
+
app.get("/sessions/:sessionId", async (c) => {
|
|
2199
|
+
const session = await props.sessionStore.getSession(c.req.param("sessionId"));
|
|
2200
|
+
if (session === void 0) {
|
|
2201
|
+
return errorResponse(c, 404, "not_found", "Session not found");
|
|
2202
|
+
}
|
|
2203
|
+
return c.json(session);
|
|
2204
|
+
});
|
|
2205
|
+
app.delete("/sessions/:sessionId", async (c) => {
|
|
2206
|
+
if (props.sessionStore.deleteSession === void 0) {
|
|
2207
|
+
return errorResponse(
|
|
2208
|
+
c,
|
|
2209
|
+
501,
|
|
2210
|
+
"unsupported_capability",
|
|
2211
|
+
'Capability "sessions.delete" is not implemented by this runner',
|
|
2212
|
+
{ capability: "sessions", operation: "delete" }
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
const deleted = await props.sessionStore.deleteSession(c.req.param("sessionId"));
|
|
2216
|
+
if (!deleted) {
|
|
2217
|
+
return errorResponse(c, 404, "not_found", "Session not found");
|
|
2218
|
+
}
|
|
2219
|
+
return c.body(null, 204);
|
|
2220
|
+
});
|
|
2221
|
+
app.get("/sessions/:sessionId/traces", async (c) => {
|
|
2222
|
+
if (props.traceStore === void 0) {
|
|
2223
|
+
return unsupportedCapability(c, "traces");
|
|
2224
|
+
}
|
|
2225
|
+
const sessionId = c.req.param("sessionId");
|
|
2226
|
+
const session = await props.sessionStore.getSession(sessionId);
|
|
2227
|
+
if (session === void 0) {
|
|
2228
|
+
return errorResponse(c, 404, "not_found", "Session not found");
|
|
2229
|
+
}
|
|
2230
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
2231
|
+
if (limit === void 0) {
|
|
2232
|
+
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
2233
|
+
}
|
|
2234
|
+
const traces = await props.traceStore.listSessionTraces({ sessionId, limit });
|
|
2235
|
+
return c.json({ traces });
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
async function parseCreateSessionRequest(c) {
|
|
2239
|
+
let body;
|
|
2240
|
+
try {
|
|
2241
|
+
body = await c.req.json();
|
|
2242
|
+
} catch {
|
|
2243
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
|
|
2244
|
+
}
|
|
2245
|
+
if (!isObject(body)) {
|
|
2246
|
+
return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
|
|
2247
|
+
}
|
|
2248
|
+
if (typeof body.agentId !== "string" || body.agentId.trim().length === 0) {
|
|
2249
|
+
return { error: errorResponse(c, 400, "bad_request", "agentId must be a string") };
|
|
2250
|
+
}
|
|
2251
|
+
const request = {
|
|
2252
|
+
agentId: body.agentId.trim()
|
|
2253
|
+
};
|
|
2254
|
+
if ("title" in body) {
|
|
2255
|
+
if (typeof body.title !== "string") {
|
|
2256
|
+
return { error: errorResponse(c, 400, "bad_request", "title must be a string") };
|
|
2257
|
+
}
|
|
2258
|
+
const title = body.title.trim();
|
|
2259
|
+
if (title.length > 0) {
|
|
2260
|
+
request.title = title;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
if ("metadata" in body) {
|
|
2264
|
+
if (!isJsonObject(body.metadata)) {
|
|
2265
|
+
return { error: errorResponse(c, 400, "bad_request", "metadata must be an object") };
|
|
2266
|
+
}
|
|
2267
|
+
request.metadata = body.metadata;
|
|
2268
|
+
}
|
|
2269
|
+
return request;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// src/runtime/trace-routes.ts
|
|
2273
|
+
function registerTraceRoutes(app, traceStore) {
|
|
2274
|
+
app.get("/traces", async (c) => {
|
|
2275
|
+
if (traceStore.listTraces === void 0) {
|
|
2276
|
+
return errorResponse(
|
|
2277
|
+
c,
|
|
2278
|
+
501,
|
|
2279
|
+
"unsupported_capability",
|
|
2280
|
+
'Capability "traces.list" is not implemented by this runner',
|
|
2281
|
+
{ capability: "traces", operation: "list" }
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
2285
|
+
if (limit === void 0) {
|
|
2286
|
+
return errorResponse(c, 400, "bad_request", "limit must be a positive integer");
|
|
2287
|
+
}
|
|
2288
|
+
const status = parseTraceStatus(c.req.query("status"));
|
|
2289
|
+
if (status === false) {
|
|
2290
|
+
return errorResponse(c, 400, "bad_request", "status must be running, success, or error");
|
|
2291
|
+
}
|
|
2292
|
+
const agentId = optionalQueryString(c.req.query("agentId"));
|
|
2293
|
+
const sessionId = optionalQueryString(c.req.query("sessionId"));
|
|
2294
|
+
const traces = await traceStore.listTraces({
|
|
2295
|
+
limit,
|
|
2296
|
+
...agentId === void 0 ? {} : { agentId },
|
|
2297
|
+
...sessionId === void 0 ? {} : { sessionId },
|
|
2298
|
+
...status === void 0 ? {} : { status }
|
|
2299
|
+
});
|
|
2300
|
+
return c.json({ traces });
|
|
2301
|
+
});
|
|
2302
|
+
app.get("/traces/:traceId", async (c) => {
|
|
2303
|
+
const trace = await traceStore.getTrace(c.req.param("traceId"));
|
|
2304
|
+
if (trace === void 0) {
|
|
2305
|
+
return errorResponse(c, 404, "not_found", "Trace not found");
|
|
2306
|
+
}
|
|
2307
|
+
return c.json(trace);
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
// src/runtime/studio.ts
|
|
2312
|
+
var Studio = class {
|
|
2313
|
+
options;
|
|
2314
|
+
studio;
|
|
2315
|
+
server;
|
|
2316
|
+
sigintHandler;
|
|
2317
|
+
constructor(agents = [], options = {}) {
|
|
2318
|
+
this.options = studioOptionsFromAgents(agents, options);
|
|
2319
|
+
this.studio = createStudioApp(this.options);
|
|
2320
|
+
}
|
|
2321
|
+
get app() {
|
|
2322
|
+
return this.studio.app;
|
|
2323
|
+
}
|
|
2324
|
+
fetch(request) {
|
|
2325
|
+
return this.studio.fetch(request);
|
|
2326
|
+
}
|
|
2327
|
+
config() {
|
|
2328
|
+
return this.studio.config();
|
|
2329
|
+
}
|
|
2330
|
+
traceObserver() {
|
|
2331
|
+
return new StudioTraceObserver({
|
|
2332
|
+
store: () => this.studio.traceStore
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
start(serveOptions = {}) {
|
|
2336
|
+
this.close();
|
|
2337
|
+
this.studio = createStudioApp(this.options);
|
|
2338
|
+
const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
|
|
2339
|
+
this.server = serve({
|
|
2340
|
+
fetch: (request) => this.fetch(request),
|
|
2341
|
+
...serveOptions.hostname === void 0 ? {} : { hostname: serveOptions.hostname },
|
|
2342
|
+
port
|
|
2343
|
+
});
|
|
2344
|
+
const log = serveOptions.log ?? true;
|
|
2345
|
+
if (log) {
|
|
2346
|
+
const host = serveOptions.hostname ?? "localhost";
|
|
2347
|
+
if (isStudioUiEnabled(this.options.ui)) {
|
|
2348
|
+
const uiPath = studioUiEntryPath(resolveStudioUiOptions(this.options.ui));
|
|
2349
|
+
console.log(`Studio UI: http://${host}:${port}${uiPath}`);
|
|
2350
|
+
} else {
|
|
2351
|
+
console.log(`Studio API: http://${host}:${port}`);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
this.sigintHandler = () => {
|
|
2355
|
+
this.close();
|
|
2356
|
+
process.exit(0);
|
|
2357
|
+
};
|
|
2358
|
+
process.once("SIGINT", this.sigintHandler);
|
|
2359
|
+
return this;
|
|
2360
|
+
}
|
|
2361
|
+
close() {
|
|
2362
|
+
if (this.sigintHandler !== void 0) {
|
|
2363
|
+
process.off("SIGINT", this.sigintHandler);
|
|
2364
|
+
this.sigintHandler = void 0;
|
|
2365
|
+
}
|
|
2366
|
+
this.server?.close();
|
|
2367
|
+
this.server = void 0;
|
|
2368
|
+
this.studio.close();
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
function studioOptionsFromAgents(agents, options) {
|
|
2372
|
+
return {
|
|
2373
|
+
agents: inferStudioAgents(agents, options.quickPrompts ?? {})
|
|
2374
|
+
};
|
|
2375
|
+
}
|
|
2376
|
+
function inferStudioAgents(agents, quickPrompts) {
|
|
2377
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2378
|
+
return agents.map((agent) => {
|
|
2379
|
+
const id = uniqueAgentId(agent.id, ids);
|
|
2380
|
+
return {
|
|
2381
|
+
id,
|
|
2382
|
+
agent,
|
|
2383
|
+
quickPrompts: quickPrompts[id] ?? [],
|
|
2384
|
+
metadata: agentMetadata(agent)
|
|
2385
|
+
};
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
function uniqueAgentId(baseId, ids) {
|
|
2389
|
+
let id = baseId;
|
|
2390
|
+
let suffix = 2;
|
|
2391
|
+
while (ids.has(id)) {
|
|
2392
|
+
id = `${baseId}-${suffix}`;
|
|
2393
|
+
suffix += 1;
|
|
2394
|
+
}
|
|
2395
|
+
ids.add(id);
|
|
2396
|
+
return id;
|
|
2397
|
+
}
|
|
2398
|
+
function agentMetadata(agent) {
|
|
2399
|
+
return {
|
|
2400
|
+
...agent.defaultMaxTurns === void 0 ? {} : { defaultMaxTurns: agent.defaultMaxTurns },
|
|
2401
|
+
staticContextCount: agent.staticContext.length,
|
|
2402
|
+
dynamicContextCount: agent.dynamicContexts.length,
|
|
2403
|
+
dynamicToolCount: agent.dynamicTools.length,
|
|
2404
|
+
hasOutputSchema: agent.outputSchema !== void 0,
|
|
2405
|
+
observerCount: agent.observers.length,
|
|
2406
|
+
approvalToolCount: agent.toolSet.values().filter((tool) => tool.approval !== void 0).length
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
function createStudioApp(options) {
|
|
2410
|
+
const stores = resolveStores(options);
|
|
2411
|
+
const agents = normalizeAgents(options.agents).map(
|
|
2412
|
+
(agent) => withStudioTraceObserver(agent, stores.traces)
|
|
2413
|
+
);
|
|
2414
|
+
const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
|
|
2415
|
+
const approvalRuntime = createApprovalRuntime();
|
|
2416
|
+
const questionRuntime = createQuestionRuntime();
|
|
2417
|
+
const app = new HonoApp();
|
|
2418
|
+
const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
|
|
2419
|
+
if (uiOptions !== void 0 && !uiOptions.protectShell) {
|
|
2420
|
+
registerStudioUi(app, uiOptions);
|
|
2421
|
+
}
|
|
2422
|
+
if (uiOptions?.protectShell) {
|
|
2423
|
+
registerStudioUi(app, uiOptions);
|
|
2424
|
+
}
|
|
2425
|
+
app.get(
|
|
2426
|
+
"/health",
|
|
2427
|
+
(c) => c.json({
|
|
2428
|
+
status: "ok",
|
|
2429
|
+
runner: {
|
|
2430
|
+
id: runnerId(options),
|
|
2431
|
+
name: options.name,
|
|
2432
|
+
version: options.version
|
|
2433
|
+
}
|
|
2434
|
+
})
|
|
2435
|
+
);
|
|
2436
|
+
app.get("/config", (c) => c.json(buildConfig(options, agents, stores)));
|
|
2437
|
+
app.get("/agents", (c) => c.json({ agents: agents.map(agentConfig) }));
|
|
2438
|
+
app.get("/agents/:agentId", (c) => {
|
|
2439
|
+
const agent = agentMap.get(c.req.param("agentId"));
|
|
2440
|
+
if (agent === void 0) {
|
|
2441
|
+
return errorResponse(c, 404, "not_found", "Agent not found");
|
|
2442
|
+
}
|
|
2443
|
+
return c.json(agentConfig(agent));
|
|
2444
|
+
});
|
|
2445
|
+
registerApprovalRoutes(app, approvalRuntime);
|
|
2446
|
+
registerQuestionRoutes(app, questionRuntime);
|
|
2447
|
+
registerKnowledgeRoutes(app, {
|
|
2448
|
+
agents,
|
|
2449
|
+
...stores.traces === void 0 ? {} : { traceStore: stores.traces }
|
|
2450
|
+
});
|
|
2451
|
+
app.post("/agents/:agentId/runs", async (c) => {
|
|
2452
|
+
const agentId = c.req.param("agentId");
|
|
2453
|
+
const agent = agentMap.get(agentId);
|
|
2454
|
+
if (agent === void 0) {
|
|
2455
|
+
return errorResponse(c, 404, "not_found", "Agent not found");
|
|
2456
|
+
}
|
|
2457
|
+
const body = await parseRunRequest(c);
|
|
2458
|
+
if ("error" in body) {
|
|
2459
|
+
return body.error;
|
|
2460
|
+
}
|
|
2461
|
+
if (body.sessionId !== void 0 && stores.sessions === void 0) {
|
|
2462
|
+
return unsupportedCapability(c, "sessions");
|
|
2463
|
+
}
|
|
2464
|
+
const session = body.sessionId === void 0 ? void 0 : await stores.sessions?.getSession(body.sessionId);
|
|
2465
|
+
if (body.sessionId !== void 0 && session === void 0) {
|
|
2466
|
+
return errorResponse(c, 404, "not_found", "Session not found");
|
|
2467
|
+
}
|
|
2468
|
+
if (session !== void 0 && session.agentId !== agentId) {
|
|
2469
|
+
return errorResponse(c, 400, "bad_request", "Session belongs to another agent");
|
|
2470
|
+
}
|
|
2471
|
+
const runId = globalThis.crypto.randomUUID();
|
|
2472
|
+
const request = agent.agent.prompt(body.message);
|
|
2473
|
+
if (session !== void 0) {
|
|
2474
|
+
request.withHistory(session.messages);
|
|
2475
|
+
} else if (body.history !== void 0) {
|
|
2476
|
+
request.withHistory(body.history);
|
|
2477
|
+
}
|
|
2478
|
+
if (body.maxTurns !== void 0) {
|
|
2479
|
+
request.maxTurns(body.maxTurns);
|
|
2480
|
+
}
|
|
2481
|
+
if (body.toolConcurrency !== void 0) {
|
|
2482
|
+
request.withToolConcurrency(body.toolConcurrency);
|
|
2483
|
+
}
|
|
2484
|
+
if (body.trace !== void 0) {
|
|
2485
|
+
request.withTrace(traceForRun(body.trace, agentId, session));
|
|
2486
|
+
} else if (session !== void 0) {
|
|
2487
|
+
request.withTrace(traceForRun(void 0, agentId, session));
|
|
2488
|
+
}
|
|
2489
|
+
if (body.stream === true) {
|
|
2490
|
+
const runtimeEvents = new AsyncEventQueue();
|
|
2491
|
+
const effectiveHook = composeHooks(
|
|
2492
|
+
composeHooks(
|
|
2493
|
+
agent.agent.hook,
|
|
2494
|
+
approvalRuntime.createHook({
|
|
2495
|
+
runId,
|
|
2496
|
+
agentId,
|
|
2497
|
+
...session?.id === void 0 ? {} : { sessionId: session.id },
|
|
2498
|
+
...body.metadata === void 0 ? {} : { metadata: body.metadata },
|
|
2499
|
+
getTool: (toolName) => agent.agent.getTool(toolName),
|
|
2500
|
+
emit: (event) => runtimeEvents.push(event)
|
|
2501
|
+
})
|
|
2502
|
+
),
|
|
2503
|
+
questionRuntime.createHook({
|
|
2504
|
+
runId,
|
|
2505
|
+
agentId,
|
|
2506
|
+
...session?.id === void 0 ? {} : { sessionId: session.id },
|
|
2507
|
+
...body.metadata === void 0 ? {} : { metadata: body.metadata },
|
|
2508
|
+
emit: (event) => runtimeEvents.push(event)
|
|
2509
|
+
})
|
|
2510
|
+
);
|
|
2511
|
+
if (effectiveHook !== void 0) {
|
|
2512
|
+
request.requestHook(effectiveHook);
|
|
2513
|
+
}
|
|
2514
|
+
const runStream = mergeRunAndApprovalEvents(request.stream(), runtimeEvents);
|
|
2515
|
+
const stream = session === void 0 || stores.sessions === void 0 ? runStream : persistStreamingSessionRun({
|
|
2516
|
+
stream: runStream,
|
|
2517
|
+
store: stores.sessions,
|
|
2518
|
+
session,
|
|
2519
|
+
message: body.message
|
|
2520
|
+
});
|
|
2521
|
+
return streamAgentRunEvents(c, stream);
|
|
2522
|
+
}
|
|
2523
|
+
try {
|
|
2524
|
+
const effectiveHook = composeHooks(
|
|
2525
|
+
composeHooks(
|
|
2526
|
+
agent.agent.hook,
|
|
2527
|
+
approvalRuntime.createHook({
|
|
2528
|
+
runId,
|
|
2529
|
+
agentId,
|
|
2530
|
+
...session?.id === void 0 ? {} : { sessionId: session.id },
|
|
2531
|
+
...body.metadata === void 0 ? {} : { metadata: body.metadata },
|
|
2532
|
+
getTool: (toolName) => agent.agent.getTool(toolName)
|
|
2533
|
+
})
|
|
2534
|
+
),
|
|
2535
|
+
questionRuntime.createHook({
|
|
2536
|
+
runId,
|
|
2537
|
+
agentId,
|
|
2538
|
+
...session?.id === void 0 ? {} : { sessionId: session.id },
|
|
2539
|
+
...body.metadata === void 0 ? {} : { metadata: body.metadata }
|
|
2540
|
+
})
|
|
2541
|
+
);
|
|
2542
|
+
if (effectiveHook !== void 0) {
|
|
2543
|
+
request.requestHook(effectiveHook);
|
|
2544
|
+
}
|
|
2545
|
+
const response = await request.send();
|
|
2546
|
+
if (session !== void 0 && stores.sessions !== void 0) {
|
|
2547
|
+
await stores.sessions.appendSessionRun({
|
|
2548
|
+
id: session.id,
|
|
2549
|
+
...optionalTitle(body.message),
|
|
2550
|
+
messages: response.messages,
|
|
2551
|
+
transcript: transcriptFromMessages(response.messages)
|
|
2552
|
+
});
|
|
2553
|
+
}
|
|
2554
|
+
return c.json(response);
|
|
2555
|
+
} catch (error) {
|
|
2556
|
+
return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError2(error));
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
if (stores.sessions !== void 0) {
|
|
2560
|
+
registerSessionRoutes(app, {
|
|
2561
|
+
agentMap,
|
|
2562
|
+
sessionStore: stores.sessions,
|
|
2563
|
+
...stores.traces === void 0 ? {} : { traceStore: stores.traces }
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2566
|
+
if (stores.traces !== void 0) {
|
|
2567
|
+
registerTraceRoutes(app, stores.traces);
|
|
2568
|
+
}
|
|
2569
|
+
for (const capability of unsupportedCapabilities(stores)) {
|
|
2570
|
+
app.all(`/${capability}`, (c) => unsupportedCapability(c, capability));
|
|
2571
|
+
app.all(`/${capability}/*`, (c) => unsupportedCapability(c, capability));
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
app,
|
|
2575
|
+
fetch(request) {
|
|
2576
|
+
return app.fetch(request);
|
|
2577
|
+
},
|
|
2578
|
+
config() {
|
|
2579
|
+
return buildConfig(options, agents, stores);
|
|
2580
|
+
},
|
|
2581
|
+
close() {
|
|
2582
|
+
},
|
|
2583
|
+
...stores.sessions === void 0 ? {} : { sessionStore: stores.sessions },
|
|
2584
|
+
...stores.traces === void 0 ? {} : { traceStore: stores.traces }
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
function withStudioTraceObserver(studioAgent, traceStore) {
|
|
2588
|
+
if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
|
|
2589
|
+
return studioAgent;
|
|
2590
|
+
}
|
|
2591
|
+
return {
|
|
2592
|
+
...studioAgent,
|
|
2593
|
+
agent: new Agent({
|
|
2594
|
+
id: studioAgent.agent.id,
|
|
2595
|
+
name: studioAgent.agent.name,
|
|
2596
|
+
description: studioAgent.agent.description,
|
|
2597
|
+
model: studioAgent.agent.model,
|
|
2598
|
+
instructions: studioAgent.agent.instructions,
|
|
2599
|
+
staticContext: studioAgent.agent.staticContext,
|
|
2600
|
+
temperature: studioAgent.agent.temperature,
|
|
2601
|
+
maxTokens: studioAgent.agent.maxTokens,
|
|
2602
|
+
additionalParams: studioAgent.agent.additionalParams,
|
|
2603
|
+
toolSet: studioAgent.agent.toolSet,
|
|
2604
|
+
toolChoice: studioAgent.agent.toolChoice,
|
|
2605
|
+
defaultMaxTurns: studioAgent.agent.defaultMaxTurns,
|
|
2606
|
+
hook: studioAgent.agent.hook,
|
|
2607
|
+
outputSchema: studioAgent.agent.outputSchema,
|
|
2608
|
+
observers: [
|
|
2609
|
+
...studioAgent.agent.observers,
|
|
2610
|
+
{ observer: new StudioTraceObserver({ store: traceStore }) }
|
|
2611
|
+
],
|
|
2612
|
+
dynamicContexts: studioAgent.agent.dynamicContexts,
|
|
2613
|
+
dynamicTools: studioAgent.agent.dynamicTools
|
|
2614
|
+
})
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
function hasStudioTraceObserver(agent) {
|
|
2618
|
+
return agent.observers.some(
|
|
2619
|
+
(registration) => registration.observer instanceof StudioTraceObserver
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
function composeHooks(first, second) {
|
|
2623
|
+
if (first === void 0) {
|
|
2624
|
+
return second;
|
|
2625
|
+
}
|
|
2626
|
+
if (second === void 0) {
|
|
2627
|
+
return first;
|
|
2628
|
+
}
|
|
2629
|
+
return createHook3({
|
|
2630
|
+
async onCompletionCall(args) {
|
|
2631
|
+
const firstAction = await first.onCompletionCall?.(args);
|
|
2632
|
+
return firstAction?.type === "terminate" ? firstAction : await second.onCompletionCall?.(args) ?? void 0;
|
|
2633
|
+
},
|
|
2634
|
+
async onCompletionResponse(args) {
|
|
2635
|
+
const firstAction = await first.onCompletionResponse?.(args);
|
|
2636
|
+
return firstAction?.type === "terminate" ? firstAction : await second.onCompletionResponse?.(args) ?? void 0;
|
|
2637
|
+
},
|
|
2638
|
+
async onToolCall(args) {
|
|
2639
|
+
const firstAction = await first.onToolCall?.(args);
|
|
2640
|
+
if (firstAction?.type === "skip" || firstAction?.type === "terminate") {
|
|
2641
|
+
return firstAction;
|
|
2642
|
+
}
|
|
2643
|
+
const secondAction = await second.onToolCall?.(args);
|
|
2644
|
+
return secondAction ?? firstAction ?? void 0;
|
|
2645
|
+
},
|
|
2646
|
+
async onToolResult(args) {
|
|
2647
|
+
const firstAction = await first.onToolResult?.(args);
|
|
2648
|
+
return firstAction?.type === "terminate" ? firstAction : await second.onToolResult?.(args) ?? void 0;
|
|
2649
|
+
}
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
export {
|
|
2653
|
+
Studio,
|
|
2654
|
+
StudioTraceObserver,
|
|
2655
|
+
createSqliteSessionStore
|
|
2656
|
+
};
|
|
2657
|
+
//# sourceMappingURL=index.js.map
|