@anvia/studio 1.0.1 → 1.0.4

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.
Files changed (107) hide show
  1. package/README.md +45 -3
  2. package/dist/index.d.ts +56 -5
  3. package/dist/index.js +349 -41
  4. package/dist/index.js.map +1 -1
  5. package/dist/ui/assets/Check.es-DgvuCllR.js +2 -0
  6. package/dist/ui/assets/Check.es-DgvuCllR.js.map +1 -0
  7. package/dist/ui/assets/Copy.es-DV7tb_eC.js +2 -0
  8. package/dist/ui/assets/Copy.es-DV7tb_eC.js.map +1 -0
  9. package/dist/ui/assets/MagnifyingGlass.es-yzEp9c89.js +2 -0
  10. package/dist/ui/assets/MagnifyingGlass.es-yzEp9c89.js.map +1 -0
  11. package/dist/ui/assets/Wrench.es-rRuP3Mrh.js +2 -0
  12. package/dist/ui/assets/Wrench.es-rRuP3Mrh.js.map +1 -0
  13. package/dist/ui/assets/agents-page-ByhvVYOx.js +2 -0
  14. package/dist/ui/assets/agents-page-ByhvVYOx.js.map +1 -0
  15. package/dist/ui/assets/badge-C3fkxijh.js +2 -0
  16. package/dist/ui/assets/badge-C3fkxijh.js.map +1 -0
  17. package/dist/ui/assets/button-B_AoswCu.js +2 -0
  18. package/dist/ui/assets/button-B_AoswCu.js.map +1 -0
  19. package/dist/ui/assets/{dialog-DN8F1Aaq.js → dialog-BfKpnby4.js} +2 -2
  20. package/dist/ui/assets/{dialog-DN8F1Aaq.js.map → dialog-BfKpnby4.js.map} +1 -1
  21. package/dist/ui/assets/{dist-PrGeYMiK.js → dist-Dr6EWOhl.js} +2 -2
  22. package/dist/ui/assets/{dist-PrGeYMiK.js.map → dist-Dr6EWOhl.js.map} +1 -1
  23. package/dist/ui/assets/{es2015-Ck1KeeEx.js → es2015-DGcxtKLK.js} +2 -2
  24. package/dist/ui/assets/{es2015-Ck1KeeEx.js.map → es2015-DGcxtKLK.js.map} +1 -1
  25. package/dist/ui/assets/favicon-dark.svg +7 -0
  26. package/dist/ui/assets/favicon-light.svg +7 -0
  27. package/dist/ui/assets/favicon.svg +1 -1
  28. package/dist/ui/assets/graphs-page-ne3dJIrx.js +2 -0
  29. package/dist/ui/assets/graphs-page-ne3dJIrx.js.map +1 -0
  30. package/dist/ui/assets/index-BGl7eOVR.css +2 -0
  31. package/dist/ui/assets/index-DF9Ysm8q.js +75 -0
  32. package/dist/ui/assets/index-DF9Ysm8q.js.map +1 -0
  33. package/dist/ui/assets/{input-DHvac9Uq.js → input-DuERdYTl.js} +2 -2
  34. package/dist/ui/assets/{input-DHvac9Uq.js.map → input-DuERdYTl.js.map} +1 -1
  35. package/dist/ui/assets/knowledge-page-1WgvGV4f.js +2 -0
  36. package/dist/ui/assets/knowledge-page-1WgvGV4f.js.map +1 -0
  37. package/dist/ui/assets/{mcps-page-MSz67ifn.js → mcps-page-BV96jSfG.js} +2 -2
  38. package/dist/ui/assets/mcps-page-BV96jSfG.js.map +1 -0
  39. package/dist/ui/assets/memory-page-DxClkjRl.js +4 -0
  40. package/dist/ui/assets/{memory-page-2qr2eMrI.js.map → memory-page-DxClkjRl.js.map} +1 -1
  41. package/dist/ui/assets/pipelines-page-ClO_uXn1.js +2 -0
  42. package/dist/ui/assets/pipelines-page-ClO_uXn1.js.map +1 -0
  43. package/dist/ui/assets/{renderers-BIw5yJkg.js → renderers-Cf55u6J4.js} +3 -3
  44. package/dist/ui/assets/{renderers-BIw5yJkg.js.map → renderers-Cf55u6J4.js.map} +1 -1
  45. package/dist/ui/assets/{request-CnFL8L6p.js → request-DbgMKweq.js} +2 -2
  46. package/dist/ui/assets/{request-CnFL8L6p.js.map → request-DbgMKweq.js.map} +1 -1
  47. package/dist/ui/assets/{sandboxes-page-DWMtrKYJ.js → sandboxes-page-Bo4S4ILL.js} +2 -2
  48. package/dist/ui/assets/sandboxes-page-Bo4S4ILL.js.map +1 -0
  49. package/dist/ui/assets/select-BdCBnmXx.js +2 -0
  50. package/dist/ui/assets/{select-C1_9fsRu.js.map → select-BdCBnmXx.js.map} +1 -1
  51. package/dist/ui/assets/sessions-page-BMnR-r_F.js +2 -0
  52. package/dist/ui/assets/sessions-page-BMnR-r_F.js.map +1 -0
  53. package/dist/ui/assets/status-page-Ys5dQyMG.js +2 -0
  54. package/dist/ui/assets/status-page-Ys5dQyMG.js.map +1 -0
  55. package/dist/ui/assets/studio-DzEdCFsX.js +2 -0
  56. package/dist/ui/assets/studio-DzEdCFsX.js.map +1 -0
  57. package/dist/ui/assets/style-BstB0PNS.js +8 -0
  58. package/dist/ui/assets/style-BstB0PNS.js.map +1 -0
  59. package/dist/ui/assets/{textarea-CZvhTJxB.js → textarea-fAtCF2cf.js} +2 -2
  60. package/dist/ui/assets/{textarea-CZvhTJxB.js.map → textarea-fAtCF2cf.js.map} +1 -1
  61. package/dist/ui/assets/tool-runner-Cwfc3yY8.js +2 -0
  62. package/dist/ui/assets/tool-runner-Cwfc3yY8.js.map +1 -0
  63. package/dist/ui/assets/{tools-page-D1ewvG0D.js → tools-page-BSgFqVi2.js} +2 -2
  64. package/dist/ui/assets/tools-page-BSgFqVi2.js.map +1 -0
  65. package/dist/ui/assets/trace-browser-Co9iS9HR.js +4 -0
  66. package/dist/ui/assets/trace-browser-Co9iS9HR.js.map +1 -0
  67. package/dist/ui/assets/transcript-item-ChVGempt.js +3 -0
  68. package/dist/ui/assets/transcript-item-ChVGempt.js.map +1 -0
  69. package/dist/ui/index.html +28 -20
  70. package/package.json +12 -8
  71. package/dist/ui/assets/CaretDown.es-CXu4Fvbf.js +0 -2
  72. package/dist/ui/assets/CaretDown.es-CXu4Fvbf.js.map +0 -1
  73. package/dist/ui/assets/Check.es-DSXK9-8c.js +0 -2
  74. package/dist/ui/assets/Check.es-DSXK9-8c.js.map +0 -1
  75. package/dist/ui/assets/Wrench.es-Cl38Pi3C.js +0 -4
  76. package/dist/ui/assets/Wrench.es-Cl38Pi3C.js.map +0 -1
  77. package/dist/ui/assets/agents-page-h-83w9EN.js +0 -2
  78. package/dist/ui/assets/agents-page-h-83w9EN.js.map +0 -1
  79. package/dist/ui/assets/badge-O3YyUN3G.js +0 -2
  80. package/dist/ui/assets/badge-O3YyUN3G.js.map +0 -1
  81. package/dist/ui/assets/button-Dsqwey2_.js +0 -2
  82. package/dist/ui/assets/button-Dsqwey2_.js.map +0 -1
  83. package/dist/ui/assets/index-Dggzi3lZ.js +0 -75
  84. package/dist/ui/assets/index-Dggzi3lZ.js.map +0 -1
  85. package/dist/ui/assets/index-VCdJ2Rb4.css +0 -2
  86. package/dist/ui/assets/knowledge-page-DKnPrmhK.js +0 -2
  87. package/dist/ui/assets/knowledge-page-DKnPrmhK.js.map +0 -1
  88. package/dist/ui/assets/mcps-page-MSz67ifn.js.map +0 -1
  89. package/dist/ui/assets/memory-page-2qr2eMrI.js +0 -4
  90. package/dist/ui/assets/pipelines-page-D1IB1tw-.js +0 -8
  91. package/dist/ui/assets/pipelines-page-D1IB1tw-.js.map +0 -1
  92. package/dist/ui/assets/sandboxes-page-DWMtrKYJ.js.map +0 -1
  93. package/dist/ui/assets/select-C1_9fsRu.js +0 -2
  94. package/dist/ui/assets/sessions-page-BIk4q-L8.js +0 -2
  95. package/dist/ui/assets/sessions-page-BIk4q-L8.js.map +0 -1
  96. package/dist/ui/assets/status-page-jKTghmnz.js +0 -2
  97. package/dist/ui/assets/status-page-jKTghmnz.js.map +0 -1
  98. package/dist/ui/assets/studio-Bh00wIF1.js +0 -2
  99. package/dist/ui/assets/studio-Bh00wIF1.js.map +0 -1
  100. package/dist/ui/assets/tool-runner-BtaZqE3R.js +0 -2
  101. package/dist/ui/assets/tool-runner-BtaZqE3R.js.map +0 -1
  102. package/dist/ui/assets/tools-page-D1ewvG0D.js.map +0 -1
  103. package/dist/ui/assets/trace-browser-CwRUyCO6.js +0 -10
  104. package/dist/ui/assets/trace-browser-CwRUyCO6.js.map +0 -1
  105. package/dist/ui/assets/transcript-item-ChtaJvPe.js +0 -3
  106. package/dist/ui/assets/transcript-item-ChtaJvPe.js.map +0 -1
  107. /package/dist/ui/assets/{pipelines-page-DLioOiRN.css → style-DLioOiRN.css} +0 -0
package/dist/index.js CHANGED
@@ -5,6 +5,9 @@ import { serve } from "@hono/node-server";
5
5
  import { Hono as HonoApp } from "hono";
6
6
  import { WebSocketServer } from "ws";
7
7
 
8
+ // src/traces/trace-observer.ts
9
+ import { AgentRunCancelledError } from "@anvia/core/agent";
10
+
8
11
  // src/runtime/json.ts
9
12
  function toJsonValue(value) {
10
13
  return toJsonValueInternal(value, /* @__PURE__ */ new WeakSet());
@@ -213,7 +216,7 @@ var StudioRunTraceObserver = class {
213
216
  });
214
217
  }
215
218
  async error(args) {
216
- await this.save("error", {
219
+ await this.save(args.status === "cancelled" ? "cancelled" : "error", {
217
220
  endedAt: /* @__PURE__ */ new Date(),
218
221
  error: serializeUnknown(args.error),
219
222
  usage: args.usage,
@@ -355,11 +358,12 @@ var ChildAgentToolTraceAccumulator = class {
355
358
  if (child.type === "error") {
356
359
  const metadata = this.childMetadata(agentId, agentName, childTurn);
357
360
  if (isRecord(child.usage)) metadata.usage = toJsonValue(child.usage);
361
+ const status = child.error instanceof AgentRunCancelledError ? "cancelled" : "error";
358
362
  this.completedObservations.push(
359
363
  traceObservation({
360
364
  kind: "tool",
361
365
  name: `${agentLabel(agentId, agentName)}.error`,
362
- status: "error",
366
+ status,
363
367
  turn: this.parent.turn,
364
368
  startedAt: /* @__PURE__ */ new Date(),
365
369
  error: serializeUnknown(child.error),
@@ -385,7 +389,7 @@ var ChildAgentToolTraceAccumulator = class {
385
389
  parentObservationId,
386
390
  kind: "agent",
387
391
  name: `${agentLabel(agentStart.agentId, agentStart.agentName)}.run`,
388
- status: agentChildren.some((observation) => observation.status === "error") ? "error" : "success",
392
+ status: agentChildren.some((observation) => observation.status === "error") ? "error" : agentChildren.some((observation) => observation.status === "cancelled") ? "cancelled" : "success",
389
393
  turn: this.parent.turn,
390
394
  startedAt,
391
395
  endedAt,
@@ -670,6 +674,7 @@ function registerStudioUi(app, options) {
670
674
  app.get(`${options.path}/tools`, (c) => redirectWithQuery(c, "/tools"));
671
675
  app.get(`${options.path}/mcps`, (c) => redirectWithQuery(c, "/mcps"));
672
676
  app.get(`${options.path}/pipelines`, (c) => redirectWithQuery(c, "/pipelines"));
677
+ app.get(`${options.path}/graphs`, (c) => redirectWithQuery(c, "/graphs"));
673
678
  app.get(`${options.path}/sandboxes`, (c) => redirectWithQuery(c, "/sandboxes"));
674
679
  app.get(
675
680
  `${options.path}/sandboxes/:sandboxRef`,
@@ -694,6 +699,7 @@ function registerStudioUi(app, options) {
694
699
  app.get("/tools", async (c) => c.html(await renderShell()));
695
700
  app.get("/mcps", async (c) => c.html(await renderShell()));
696
701
  app.get("/pipelines", shellWhenHtml(renderShell));
702
+ app.get("/graphs", shellWhenHtml(renderShell));
697
703
  app.get("/sandboxes", shellWhenHtml(renderShell));
698
704
  app.get("/sandboxes/:sandboxRef", shellWhenHtml(renderShell));
699
705
  app.get("/memory", async (c) => c.html(await renderShell()));
@@ -787,8 +793,9 @@ function renderLegacyStudioUiShell(props) {
787
793
  '<meta charset="utf-8">',
788
794
  '<meta name="viewport" content="width=device-width, initial-scale=1">',
789
795
  `<title>${title}</title>`,
790
- '<script>(()=>{try{const t=localStorage.getItem("anvia-studio-theme");const d=t==="dark"||(t!=="light"&&matchMedia("(prefers-color-scheme: dark)").matches);document.documentElement.classList.toggle("dark",d);document.documentElement.style.colorScheme=d?"dark":"light"}catch{}})();</script>',
791
- `<link rel="icon" type="image/svg+xml" href="${escapeHtml(props.compatUiPath)}/assets/favicon.svg">`,
796
+ '<meta name="theme-color" content="#09090b">',
797
+ `<link data-anvia-favicon rel="icon" type="image/svg+xml" href="${escapeHtml(props.compatUiPath)}/assets/favicon-dark.svg">`,
798
+ '<script>(()=>{let m="dark";try{const t=localStorage.getItem("anvia-theme");const l=localStorage.getItem("anvia-studio-theme");if(t==="light"||(t!=="dark"&&l==="light"))m="light"}catch{}const r=document.documentElement;r.classList.toggle("dark",m==="dark");r.dataset.theme=m;r.style.colorScheme=m;document.querySelector(\'meta[name="theme-color"]\')?.setAttribute("content",m==="dark"?"#09090b":"#f4f4f5");const f=document.querySelector("link[data-anvia-favicon]");if(f)f.href=f.href.replace("favicon-dark.svg",`favicon-${m}.svg`)})();</script>',
792
799
  `<link rel="stylesheet" href="${escapeHtml(props.stylesheet)}">`,
793
800
  "</head>",
794
801
  "<body>",
@@ -1005,6 +1012,18 @@ function unsupportedCapability(c, capability) {
1005
1012
  { capability }
1006
1013
  );
1007
1014
  }
1015
+ async function parseJsonBody(c, validate) {
1016
+ let body;
1017
+ try {
1018
+ body = await c.req.json();
1019
+ } catch {
1020
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
1021
+ }
1022
+ if (!isObject(body)) {
1023
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
1024
+ }
1025
+ return validate(body);
1026
+ }
1008
1027
 
1009
1028
  // src/runtime/models.ts
1010
1029
  var STUDIO_MODEL_METADATA_KEY = "studioModel";
@@ -3089,16 +3108,30 @@ function registerAgentRunRoute(app, props) {
3089
3108
  app.post("/agents/:agentId/runs", (c) => handleAgentRun(c, props));
3090
3109
  }
3091
3110
  async function handleAgentRun(c, props) {
3092
- const prepared = await prepareAgentRun(c, props);
3093
- if (prepared instanceof Response) {
3094
- return prepared;
3111
+ const lease = props.runLifecycle.start(c.req.raw.signal);
3112
+ if (lease === void 0) {
3113
+ return errorResponse(c, 503, "service_unavailable", "Anvia Studio is shutting down");
3095
3114
  }
3096
- if (prepared.body.stream === true) {
3097
- return handleStreamingAgentRun(c, prepared, props);
3115
+ try {
3116
+ const prepared = await prepareAgentRun(c, props, lease.abortSignal);
3117
+ if (prepared instanceof Response) {
3118
+ lease.finish();
3119
+ return prepared;
3120
+ }
3121
+ if (prepared.body.stream === true) {
3122
+ return handleStreamingAgentRun(c, prepared, props, lease);
3123
+ }
3124
+ try {
3125
+ return await handleBufferedAgentRun(c, prepared, props);
3126
+ } finally {
3127
+ lease.finish();
3128
+ }
3129
+ } catch (error) {
3130
+ lease.finish();
3131
+ throw error;
3098
3132
  }
3099
- return handleBufferedAgentRun(c, prepared, props);
3100
3133
  }
3101
- async function prepareAgentRun(c, props) {
3134
+ async function prepareAgentRun(c, props, abortSignal) {
3102
3135
  const agentId = c.req.param("agentId");
3103
3136
  const agent = props.agentMap.get(agentId);
3104
3137
  if (agent === void 0) {
@@ -3132,7 +3165,7 @@ async function prepareAgentRun(c, props) {
3132
3165
  if (body.stream !== void 0) resumedBody = { ...resumedBody, stream: body.stream };
3133
3166
  if (body.metadata !== void 0) resumedBody = { ...resumedBody, metadata: body.metadata };
3134
3167
  if (body.trace !== void 0) resumedBody = { ...resumedBody, trace: body.trace };
3135
- const runOptions = createRunOptions(resumedBody, agentId, source.session, c.req.raw.signal);
3168
+ const runOptions = createRunOptions(resumedBody, agentId, source.session, abortSignal);
3136
3169
  const execution2 = {
3137
3170
  generate: (options) => source.runAgent.generate({
3138
3171
  continuation: claimed.continuation,
@@ -3211,7 +3244,7 @@ async function prepareAgentRun(c, props) {
3211
3244
  execution,
3212
3245
  failureMessages: void 0,
3213
3246
  memoryCompactionLogged: false,
3214
- options: createRunOptions(body, agentId, session, c.req.raw.signal),
3247
+ options: createRunOptions(body, agentId, session, abortSignal),
3215
3248
  runAgent,
3216
3249
  runId,
3217
3250
  runStartedAt,
@@ -3339,7 +3372,7 @@ function createRunOptions(body, agentId, session, abortSignal) {
3339
3372
  }
3340
3373
  return options;
3341
3374
  }
3342
- function handleStreamingAgentRun(c, run, props) {
3375
+ function handleStreamingAgentRun(c, run, props, lease) {
3343
3376
  const streamOptions = withInternalAgentRunOptions({ ...run.options }, { runId: run.runId });
3344
3377
  const runStream = registerStreamingContinuation(
3345
3378
  run.execution.stream(streamOptions),
@@ -3367,7 +3400,7 @@ function handleStreamingAgentRun(c, run, props) {
3367
3400
  });
3368
3401
  stream = persistedRun.events;
3369
3402
  }
3370
- return streamAgentRunEvents(c, stream, {
3403
+ return streamAgentRunEvents(c, finishRunLease(stream, lease), {
3371
3404
  runId: run.runId,
3372
3405
  onCancel: async () => {
3373
3406
  const persistence = [];
@@ -3386,6 +3419,13 @@ function handleStreamingAgentRun(c, run, props) {
3386
3419
  }
3387
3420
  });
3388
3421
  }
3422
+ async function* finishRunLease(events, lease) {
3423
+ try {
3424
+ yield* events;
3425
+ } finally {
3426
+ lease.finish();
3427
+ }
3428
+ }
3389
3429
  async function handleBufferedAgentRun(c, run, props) {
3390
3430
  try {
3391
3431
  const runtimeOptions = await startBufferedSessionRun(run);
@@ -3680,7 +3720,7 @@ function parseTraceStatus(value) {
3680
3720
  if (status === void 0) {
3681
3721
  return void 0;
3682
3722
  }
3683
- return status === "running" || status === "success" || status === "suspended" || status === "error" ? status : false;
3723
+ return status === "running" || status === "success" || status === "suspended" || status === "error" || status === "cancelled" ? status : false;
3684
3724
  }
3685
3725
  function parseAfter(value) {
3686
3726
  if (value === void 0 || value.trim().length === 0) {
@@ -4193,12 +4233,33 @@ function pipelineConfig(pipeline) {
4193
4233
  if (pipeline.metadata !== void 0) config.metadata = pipeline.metadata;
4194
4234
  return config;
4195
4235
  }
4236
+ function graphConfig(registration) {
4237
+ const config = {
4238
+ id: registration.id,
4239
+ nodeTypes: Object.entries(registration.graph.schema.nodes).map(([name, definition]) => ({
4240
+ name,
4241
+ description: definition.description
4242
+ })),
4243
+ relationshipTypes: Object.entries(registration.graph.schema.relationships).map(
4244
+ ([name, definition]) => ({
4245
+ name,
4246
+ description: definition.description,
4247
+ from: definition.from,
4248
+ to: definition.to
4249
+ })
4250
+ )
4251
+ };
4252
+ if (registration.name !== void 0) config.name = registration.name;
4253
+ if (registration.description !== void 0) config.description = registration.description;
4254
+ return config;
4255
+ }
4196
4256
  function buildConfig(options, agents, pipelines, stores, sandboxCount = 0) {
4197
4257
  const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
4198
4258
  const config = {
4199
4259
  id: runnerId(options),
4200
4260
  agents: agents.map(agentConfig),
4201
4261
  pipelines: pipelines.map(pipelineConfig),
4262
+ graphs: options.graphs.map(graphConfig),
4202
4263
  evals: options.evals.map(evalConfig),
4203
4264
  chat: {
4204
4265
  quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
@@ -4230,6 +4291,9 @@ function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0)
4230
4291
  if (pipelines.length > 0) {
4231
4292
  capabilities.pipelines = { enabled: true };
4232
4293
  }
4294
+ if (_options.graphs.length > 0) {
4295
+ capabilities.graphs = { enabled: true };
4296
+ }
4233
4297
  if (_options.evals.length > 0) {
4234
4298
  capabilities.evals = { enabled: true };
4235
4299
  }
@@ -4327,6 +4391,110 @@ async function parseEvalRunRequest(c) {
4327
4391
  return request;
4328
4392
  }
4329
4393
 
4394
+ // src/runtime/graphs.ts
4395
+ import { resolveGraphExploreOptions } from "@anvia/graph/explore";
4396
+ function registerGraphRoutes(app, registrations) {
4397
+ const graphs = graphRegistry(registrations);
4398
+ app.get("/graphs", (c) => c.json({ graphs: [...graphs.values()].map(graphConfig) }));
4399
+ app.post("/graphs/:graphId/explore", async (c) => {
4400
+ const registration = graphs.get(c.req.param("graphId"));
4401
+ if (registration === void 0) {
4402
+ return errorResponse(c, 404, "not_found", "Graph not found");
4403
+ }
4404
+ const body = await parseJsonBody(c, (value) => parseExploreRequest(c, value));
4405
+ if ("error" in body) return body.error;
4406
+ const input = exploreOptionsWithSignal(body, c.req.raw.signal);
4407
+ let options;
4408
+ try {
4409
+ options = resolveGraphExploreOptions(registration.graph.schema, input);
4410
+ } catch (error) {
4411
+ return errorResponse(
4412
+ c,
4413
+ 400,
4414
+ "bad_request",
4415
+ error instanceof Error ? error.message : "Invalid graph exploration request"
4416
+ );
4417
+ }
4418
+ return c.json(await registration.graph.explore(options));
4419
+ });
4420
+ }
4421
+ function graphRegistry(registrations) {
4422
+ const graphs = /* @__PURE__ */ new Map();
4423
+ for (const registration of registrations) {
4424
+ if (typeof registration.id !== "string" || registration.id.length === 0) {
4425
+ throw new TypeError("Studio graph id must be a non-empty string.");
4426
+ }
4427
+ if (graphs.has(registration.id)) {
4428
+ throw new TypeError(`Duplicate Studio graph id: ${registration.id}.`);
4429
+ }
4430
+ graphs.set(registration.id, registration);
4431
+ }
4432
+ return graphs;
4433
+ }
4434
+ function parseExploreRequest(c, value) {
4435
+ if (!isObject(value) || value.mode !== "overview" && value.mode !== "expand") {
4436
+ return { error: errorResponse(c, 400, "bad_request", "mode must be overview or expand") };
4437
+ }
4438
+ const common = parseCommonOptions(c, value);
4439
+ if ("error" in common) return common;
4440
+ if (value.mode === "overview") return { ...common, mode: "overview" };
4441
+ const nodeIds = stringList(value.nodeIds);
4442
+ if (nodeIds === void 0) {
4443
+ return { error: errorResponse(c, 400, "bad_request", "nodeIds must be a string array") };
4444
+ }
4445
+ const request = {
4446
+ ...common,
4447
+ mode: "expand",
4448
+ nodeIds
4449
+ };
4450
+ if (value.direction === "outgoing" || value.direction === "incoming" || value.direction === "both") {
4451
+ request.direction = value.direction;
4452
+ } else if (value.direction !== void 0) {
4453
+ return {
4454
+ error: errorResponse(c, 400, "bad_request", "direction must be outgoing, incoming, or both")
4455
+ };
4456
+ }
4457
+ if (typeof value.maxDepth === "number") request.maxDepth = value.maxDepth;
4458
+ else if (value.maxDepth !== void 0) {
4459
+ return { error: errorResponse(c, 400, "bad_request", "maxDepth must be a number") };
4460
+ }
4461
+ return request;
4462
+ }
4463
+ function parseCommonOptions(c, value) {
4464
+ const request = {};
4465
+ for (const [key, label] of [
4466
+ ["nodeTypes", "nodeTypes"],
4467
+ ["relationships", "relationships"]
4468
+ ]) {
4469
+ const selected = value[key];
4470
+ if (selected === void 0) continue;
4471
+ const values = stringList(selected);
4472
+ if (values === void 0) {
4473
+ return { error: errorResponse(c, 400, "bad_request", `${label} must be a string array`) };
4474
+ }
4475
+ request[key] = values;
4476
+ }
4477
+ for (const [key, label] of [
4478
+ ["maxNodes", "maxNodes"],
4479
+ ["maxRelationships", "maxRelationships"]
4480
+ ]) {
4481
+ const selected = value[key];
4482
+ if (selected === void 0) continue;
4483
+ if (typeof selected !== "number") {
4484
+ return { error: errorResponse(c, 400, "bad_request", `${label} must be a number`) };
4485
+ }
4486
+ request[key] = selected;
4487
+ }
4488
+ return request;
4489
+ }
4490
+ function exploreOptionsWithSignal(request, abortSignal) {
4491
+ return { ...request, abortSignal };
4492
+ }
4493
+ function stringList(value) {
4494
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) return void 0;
4495
+ return value;
4496
+ }
4497
+
4330
4498
  // src/runtime/interactions.ts
4331
4499
  import {
4332
4500
  assertAgentInteractionResponse
@@ -4997,7 +5165,7 @@ function registerPipelineRoutes(app, props) {
4997
5165
  if ("error" in body) {
4998
5166
  return body.error;
4999
5167
  }
5000
- return executePipelineRun(c, props, pipeline, body);
5168
+ return executeTrackedPipelineRun(c, props, pipeline, body);
5001
5169
  });
5002
5170
  app.post("/pipelines/:pipelineId/runs/:runId/replay", async (c) => {
5003
5171
  const pipeline = props.pipelineMap.get(c.req.param("pipelineId"));
@@ -5033,10 +5201,22 @@ function registerPipelineRoutes(app, props) {
5033
5201
  metadata: replayMetadata(sourceRun.metadata, body.metadata, sourceRun.runId)
5034
5202
  };
5035
5203
  if (body.stream !== void 0) replayRequest.stream = body.stream;
5036
- return executePipelineRun(c, props, pipeline, replayRequest);
5204
+ return executeTrackedPipelineRun(c, props, pipeline, replayRequest);
5037
5205
  });
5038
5206
  }
5039
- async function executePipelineRun(c, props, pipeline, body) {
5207
+ async function executeTrackedPipelineRun(c, props, pipeline, body) {
5208
+ const lease = props.runLifecycle.start(c.req.raw.signal);
5209
+ if (lease === void 0) {
5210
+ return errorResponse(c, 503, "service_unavailable", "Anvia Studio is shutting down");
5211
+ }
5212
+ try {
5213
+ return await executePipelineRun(c, props, pipeline, body, lease);
5214
+ } catch (error) {
5215
+ lease.finish();
5216
+ throw error;
5217
+ }
5218
+ }
5219
+ async function executePipelineRun(c, props, pipeline, body, lease) {
5040
5220
  const runId = globalThis.crypto.randomUUID();
5041
5221
  const startedAt = Date.now();
5042
5222
  const startedAtIso = new Date(startedAt).toISOString();
@@ -5063,12 +5243,13 @@ async function executePipelineRun(c, props, pipeline, body) {
5063
5243
  runId,
5064
5244
  input: body.input,
5065
5245
  startedAt,
5066
- startedAtIso
5246
+ startedAtIso,
5247
+ onFinish: lease.finish
5067
5248
  };
5068
5249
  if (body.metadata !== void 0) streamOptions.metadata = body.metadata;
5069
5250
  if (props.logStore !== void 0) streamOptions.logStore = props.logStore;
5070
5251
  if (props.runStore !== void 0) streamOptions.runStore = props.runStore;
5071
- streamOptions.abortSignal = c.req.raw.signal;
5252
+ streamOptions.abortSignal = lease.abortSignal;
5072
5253
  return streamPipelineRun(c, streamOptions);
5073
5254
  }
5074
5255
  try {
@@ -5077,7 +5258,7 @@ async function executePipelineRun(c, props, pipeline, body) {
5077
5258
  input: body.input,
5078
5259
  runId,
5079
5260
  metadata: body.metadata,
5080
- abortSignal: c.req.raw.signal,
5261
+ abortSignal: lease.abortSignal,
5081
5262
  observer: {
5082
5263
  async onEvent(event) {
5083
5264
  await appendPipelineLog(props.logStore, pipelineStageLog(event, pipeline.id));
@@ -5134,6 +5315,8 @@ async function executePipelineRun(c, props, pipeline, body) {
5134
5315
  pipelineRunFailedLog(pipeline.id, runId, error, startedAt)
5135
5316
  );
5136
5317
  return errorResponse(c, 500, "internal_error", "Pipeline run failed", serializeError(error));
5318
+ } finally {
5319
+ lease.finish();
5137
5320
  }
5138
5321
  }
5139
5322
  function pipelineDetail(pipeline) {
@@ -5232,7 +5415,11 @@ async function* pipelineRunEvents(props) {
5232
5415
  yield next.value;
5233
5416
  }
5234
5417
  } finally {
5235
- await run;
5418
+ try {
5419
+ await run;
5420
+ } finally {
5421
+ props.onFinish();
5422
+ }
5236
5423
  }
5237
5424
  }
5238
5425
  async function savePipelineRun(store, input) {
@@ -5320,6 +5507,61 @@ function parsePipelineLogAfter(value) {
5320
5507
  return after;
5321
5508
  }
5322
5509
 
5510
+ // src/runtime/run-lifecycle.ts
5511
+ var StudioRunLifecycle = class {
5512
+ shutdownController = new AbortController();
5513
+ active = /* @__PURE__ */ new Set();
5514
+ drainPromise;
5515
+ resolveDrain;
5516
+ closed = false;
5517
+ start(requestSignal) {
5518
+ if (this.closed) return void 0;
5519
+ const token = /* @__PURE__ */ Symbol("studio-run");
5520
+ this.active.add(token);
5521
+ let finished = false;
5522
+ return {
5523
+ abortSignal: AbortSignal.any([requestSignal, this.shutdownController.signal]),
5524
+ finish: () => {
5525
+ if (finished) return;
5526
+ finished = true;
5527
+ this.active.delete(token);
5528
+ if (this.active.size === 0) {
5529
+ this.resolveDrain?.();
5530
+ this.resolveDrain = void 0;
5531
+ this.drainPromise = void 0;
5532
+ }
5533
+ }
5534
+ };
5535
+ }
5536
+ close() {
5537
+ if (this.closed) return;
5538
+ this.closed = true;
5539
+ this.shutdownController.abort(new Error("Anvia Studio is shutting down."));
5540
+ }
5541
+ async drain(timeoutMs) {
5542
+ this.close();
5543
+ if (this.active.size === 0) return;
5544
+ this.drainPromise ??= new Promise((resolve2) => {
5545
+ this.resolveDrain = resolve2;
5546
+ });
5547
+ await withTimeout(this.drainPromise, timeoutMs);
5548
+ }
5549
+ };
5550
+ async function withTimeout(promise, timeoutMs) {
5551
+ let timer;
5552
+ const timeout = new Promise((_resolve, reject) => {
5553
+ timer = setTimeout(
5554
+ () => reject(new Error(`Anvia Studio shutdown timed out after ${timeoutMs}ms.`)),
5555
+ timeoutMs
5556
+ );
5557
+ });
5558
+ try {
5559
+ await Promise.race([promise, timeout]);
5560
+ } finally {
5561
+ if (timer !== void 0) clearTimeout(timer);
5562
+ }
5563
+ }
5564
+
5323
5565
  // src/runtime/sandbox-views.ts
5324
5566
  import { upgradeWebSocket } from "@hono/node-server";
5325
5567
  import { getConnInfo } from "@hono/node-server/conninfo";
@@ -6810,7 +7052,8 @@ var Studio = class {
6810
7052
  studio;
6811
7053
  server;
6812
7054
  websocketServer;
6813
- sigintHandler;
7055
+ signalHandlers;
7056
+ shutdownPromise;
6814
7057
  constructor(targets = [], options = {}) {
6815
7058
  this.options = studioOptionsFromTargets(targets, options);
6816
7059
  this.studio = createStudioApp(this.options);
@@ -6832,6 +7075,7 @@ var Studio = class {
6832
7075
  start(serveOptions = {}) {
6833
7076
  this.close();
6834
7077
  this.studio = createStudioApp(this.options);
7078
+ this.shutdownPromise = void 0;
6835
7079
  const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
6836
7080
  const serverOptions = {
6837
7081
  fetch: (request, env) => this.studio.app.fetch(request, env),
@@ -6851,16 +7095,27 @@ var Studio = class {
6851
7095
  this.logAddress(host, port);
6852
7096
  }
6853
7097
  if (serveOptions.handleSignals ?? true) {
6854
- this.sigintHandler = () => {
6855
- this.close();
6856
- process.exit(0);
7098
+ const shutdownFor = (signal) => {
7099
+ process.exitCode = signal === "SIGINT" ? 130 : 143;
7100
+ void this.shutdown(shutdownOptions(serveOptions.shutdownTimeoutMs)).catch(
7101
+ (error) => {
7102
+ console.error(error);
7103
+ process.exitCode = 1;
7104
+ }
7105
+ );
7106
+ };
7107
+ this.signalHandlers = {
7108
+ sigint: () => shutdownFor("SIGINT"),
7109
+ sigterm: () => shutdownFor("SIGTERM")
6857
7110
  };
6858
- process.once("SIGINT", this.sigintHandler);
7111
+ process.once("SIGINT", this.signalHandlers.sigint);
7112
+ process.once("SIGTERM", this.signalHandlers.sigterm);
6859
7113
  }
6860
7114
  return this;
6861
7115
  }
6862
7116
  async serve(serveOptions = {}) {
6863
- const { onShutdown, signal, ...startOptions } = serveOptions;
7117
+ const { onShutdown, shutdownTimeoutMs, signal, ...startOptions } = serveOptions;
7118
+ const failures = [];
6864
7119
  try {
6865
7120
  this.start({ ...startOptions, log: false, handleSignals: false });
6866
7121
  const server = this.server;
@@ -6875,21 +7130,55 @@ var Studio = class {
6875
7130
  this.logAddress(startOptions.hostname ?? "localhost", port);
6876
7131
  }
6877
7132
  await waitForShutdown(signal);
6878
- } finally {
6879
- this.close();
7133
+ } catch (error) {
7134
+ failures.push(error);
7135
+ }
7136
+ try {
7137
+ await this.shutdown(shutdownOptions(shutdownTimeoutMs));
7138
+ } catch (error) {
7139
+ failures.push(error);
7140
+ }
7141
+ try {
6880
7142
  await onShutdown?.();
7143
+ } catch (error) {
7144
+ failures.push(error);
7145
+ }
7146
+ if (failures.length === 1) throw failures[0];
7147
+ if (failures.length > 1) {
7148
+ throw new AggregateError(failures, "Failed to run and shut down Anvia Studio.");
6881
7149
  }
6882
7150
  }
6883
7151
  close() {
6884
- if (this.sigintHandler !== void 0) {
6885
- process.off("SIGINT", this.sigintHandler);
6886
- this.sigintHandler = void 0;
7152
+ this.removeSignalHandlers();
7153
+ this.closeNetwork();
7154
+ this.studio.close();
7155
+ }
7156
+ shutdown(options = {}) {
7157
+ const timeoutMs = options.timeoutMs ?? 3e4;
7158
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
7159
+ return Promise.reject(
7160
+ new TypeError("Anvia Studio shutdown timeoutMs must be a positive number.")
7161
+ );
6887
7162
  }
7163
+ this.shutdownPromise ??= this.shutdownResources(timeoutMs);
7164
+ return this.shutdownPromise;
7165
+ }
7166
+ async shutdownResources(timeoutMs) {
7167
+ this.removeSignalHandlers();
7168
+ this.closeNetwork();
7169
+ await this.studio.shutdown({ timeoutMs });
7170
+ }
7171
+ removeSignalHandlers() {
7172
+ if (this.signalHandlers === void 0) return;
7173
+ process.off("SIGINT", this.signalHandlers.sigint);
7174
+ process.off("SIGTERM", this.signalHandlers.sigterm);
7175
+ this.signalHandlers = void 0;
7176
+ }
7177
+ closeNetwork() {
6888
7178
  this.server?.close();
6889
7179
  this.server = void 0;
6890
7180
  this.websocketServer?.close();
6891
7181
  this.websocketServer = void 0;
6892
- this.studio.close();
6893
7182
  }
6894
7183
  logAddress(host, port) {
6895
7184
  if (isStudioUiEnabled(this.options.ui)) {
@@ -6900,6 +7189,9 @@ var Studio = class {
6900
7189
  }
6901
7190
  }
6902
7191
  };
7192
+ function shutdownOptions(timeoutMs) {
7193
+ return timeoutMs === void 0 ? {} : { timeoutMs };
7194
+ }
6903
7195
  function waitForServerListening(server) {
6904
7196
  if (server.listening) return Promise.resolve();
6905
7197
  return new Promise((resolve2, reject) => {
@@ -6962,7 +7254,8 @@ function studioOptionsFromTargets(targets, options) {
6962
7254
  const runtimeOptions = {
6963
7255
  agents: inferStudioAgents(agents, options.quickPrompts ?? {}),
6964
7256
  pipelines: inferStudioPipelines(pipelines),
6965
- evals: options.evals ?? []
7257
+ evals: options.evals ?? [],
7258
+ graphs: options.graphs === void 0 ? [] : [...options.graphs]
6966
7259
  };
6967
7260
  if (options.models !== void 0) runtimeOptions.models = options.models;
6968
7261
  if (options.stores !== void 0) runtimeOptions.stores = options.stores;
@@ -7045,6 +7338,7 @@ function createStudioApp(options) {
7045
7338
  const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
7046
7339
  const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
7047
7340
  const continuationRegistry = createStudioContinuationRegistry();
7341
+ const runLifecycle = new StudioRunLifecycle();
7048
7342
  const sandboxRegistry = createStudioSandboxRegistry(agents, options.sandboxes ?? []);
7049
7343
  const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
7050
7344
  const app = new HonoApp();
@@ -7102,12 +7396,14 @@ function createStudioApp(options) {
7102
7396
  evals: options.evals,
7103
7397
  evalMap
7104
7398
  });
7399
+ registerGraphRoutes(app, options.graphs);
7105
7400
  const knowledgeOptions = { agents };
7106
7401
  if (stores.traces !== void 0) knowledgeOptions.traceStore = stores.traces;
7107
7402
  registerKnowledgeRoutes(app, knowledgeOptions);
7108
7403
  const pipelineOptions = {
7109
7404
  pipelines,
7110
- pipelineMap
7405
+ pipelineMap,
7406
+ runLifecycle
7111
7407
  };
7112
7408
  if (stores.pipelineLogs !== void 0) pipelineOptions.logStore = stores.pipelineLogs;
7113
7409
  if (stores.pipelineRuns !== void 0) pipelineOptions.runStore = stores.pipelineRuns;
@@ -7116,7 +7412,8 @@ function createStudioApp(options) {
7116
7412
  agentMap,
7117
7413
  stores,
7118
7414
  modelRegistry,
7119
- continuationRegistry
7415
+ continuationRegistry,
7416
+ runLifecycle
7120
7417
  });
7121
7418
  if (memorySources.size > 0 || stores.sessions !== void 0) {
7122
7419
  registerMemoryRoutes(app, {
@@ -7139,6 +7436,14 @@ function createStudioApp(options) {
7139
7436
  app.all(`/${capability}`, (c) => unsupportedCapability(c, capability));
7140
7437
  app.all(`/${capability}/*`, (c) => unsupportedCapability(c, capability));
7141
7438
  }
7439
+ let closing = false;
7440
+ const beginClose = () => {
7441
+ if (closing) return;
7442
+ closing = true;
7443
+ continuationRegistry.clear();
7444
+ closeSandboxViews();
7445
+ runLifecycle.close();
7446
+ };
7142
7447
  const studio = {
7143
7448
  app,
7144
7449
  fetch(request) {
@@ -7148,8 +7453,11 @@ function createStudioApp(options) {
7148
7453
  return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
7149
7454
  },
7150
7455
  close() {
7151
- continuationRegistry.clear();
7152
- closeSandboxViews();
7456
+ beginClose();
7457
+ },
7458
+ async shutdown(shutdownOptions2 = {}) {
7459
+ beginClose();
7460
+ await runLifecycle.drain(shutdownOptions2.timeoutMs ?? 3e4);
7153
7461
  }
7154
7462
  };
7155
7463
  if (stores.sessions !== void 0) Object.assign(studio, { sessionStore: stores.sessions });