@anvia/studio 0.8.1 → 1.0.0-rc.10

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 (99) hide show
  1. package/README.md +69 -19
  2. package/dist/index.d.ts +179 -116
  3. package/dist/index.js +1683 -1487
  4. package/dist/index.js.map +1 -1
  5. package/dist/ui/assets/ArrowClockwise.es-CSEY3Qr6.js +2 -0
  6. package/dist/ui/assets/{ArrowClockwise.es-CSN-gjxO.js.map → ArrowClockwise.es-CSEY3Qr6.js.map} +1 -1
  7. package/dist/ui/assets/CaretDown.es-BHAPbv1R.js +2 -0
  8. package/dist/ui/assets/CaretDown.es-BHAPbv1R.js.map +1 -0
  9. package/dist/ui/assets/Check.es-BLChmggz.js +2 -0
  10. package/dist/ui/assets/Check.es-BLChmggz.js.map +1 -0
  11. package/dist/ui/assets/Wrench.es-BL6BmNv7.js +4 -0
  12. package/dist/ui/assets/Wrench.es-BL6BmNv7.js.map +1 -0
  13. package/dist/ui/assets/agents-page-DQqt45If.js +2 -0
  14. package/dist/ui/assets/agents-page-DQqt45If.js.map +1 -0
  15. package/dist/ui/assets/{badge-RJJoRC4P.js → badge-CcajoiAt.js} +1 -1
  16. package/dist/ui/assets/{badge-RJJoRC4P.js.map → badge-CcajoiAt.js.map} +1 -1
  17. package/dist/ui/assets/button-07gzSO47.js +2 -0
  18. package/dist/ui/assets/button-07gzSO47.js.map +1 -0
  19. package/dist/ui/assets/chunk-QGX73TSQ-D9QAGlAY.js +65 -0
  20. package/dist/ui/assets/chunk-QGX73TSQ-D9QAGlAY.js.map +1 -0
  21. package/dist/ui/assets/dialog-CNNQ3hoF.js +2 -0
  22. package/dist/ui/assets/dialog-CNNQ3hoF.js.map +1 -0
  23. package/dist/ui/assets/dist-CqMdRzwk.js +2 -0
  24. package/dist/ui/assets/dist-CqMdRzwk.js.map +1 -0
  25. package/dist/ui/assets/dist-DWqJwN1i.js +2 -0
  26. package/dist/ui/assets/dist-DWqJwN1i.js.map +1 -0
  27. package/dist/ui/assets/es2015-DsZq7PFO.js +42 -0
  28. package/dist/ui/assets/es2015-DsZq7PFO.js.map +1 -0
  29. package/dist/ui/assets/icon-CqkMSRbt.js +2 -0
  30. package/dist/ui/assets/icon-CqkMSRbt.js.map +1 -0
  31. package/dist/ui/assets/index-BdhE3teo.js +12 -0
  32. package/dist/ui/assets/index-BdhE3teo.js.map +1 -0
  33. package/dist/ui/assets/index-CFbK-pX6.css +2 -0
  34. package/dist/ui/assets/{input-BOUv3rgO.js → input-BiI6gsxA.js} +1 -1
  35. package/dist/ui/assets/{input-BOUv3rgO.js.map → input-BiI6gsxA.js.map} +1 -1
  36. package/dist/ui/assets/knowledge-model-S51n2rKh.js +2 -0
  37. package/dist/ui/assets/knowledge-model-S51n2rKh.js.map +1 -0
  38. package/dist/ui/assets/knowledge-page-DZeLh92t.js +2 -0
  39. package/dist/ui/assets/{knowledge-page-Cot_uYcE.js.map → knowledge-page-DZeLh92t.js.map} +1 -1
  40. package/dist/ui/assets/mcps-page-CDPMO2x9.js +2 -0
  41. package/dist/ui/assets/{mcps-page-BSO6ork4.js.map → mcps-page-CDPMO2x9.js.map} +1 -1
  42. package/dist/ui/assets/memory-page-D3UhjpTb.js +4 -0
  43. package/dist/ui/assets/memory-page-D3UhjpTb.js.map +1 -0
  44. package/dist/ui/assets/pipelines-page-BP8R6NI7.js +8 -0
  45. package/dist/ui/assets/pipelines-page-BP8R6NI7.js.map +1 -0
  46. package/dist/ui/assets/{renderers-B8gvjR2u.js → renderers-DHMEAblV.js} +3 -3
  47. package/dist/ui/assets/{renderers-B8gvjR2u.js.map → renderers-DHMEAblV.js.map} +1 -1
  48. package/dist/ui/assets/request-7oUPS8zY.js +4 -0
  49. package/dist/ui/assets/request-7oUPS8zY.js.map +1 -0
  50. package/dist/ui/assets/rfb-DiW3PDuT.js +6 -0
  51. package/dist/ui/assets/rfb-DiW3PDuT.js.map +1 -0
  52. package/dist/ui/assets/sandboxes-page-C6kokYwc.js +2 -0
  53. package/dist/ui/assets/sandboxes-page-C6kokYwc.js.map +1 -0
  54. package/dist/ui/assets/select-BOYfrfck.js +2 -0
  55. package/dist/ui/assets/select-BOYfrfck.js.map +1 -0
  56. package/dist/ui/assets/sessions-page-ClXzRM9K.js +2 -0
  57. package/dist/ui/assets/{sessions-page-CSN2vSKZ.js.map → sessions-page-ClXzRM9K.js.map} +1 -1
  58. package/dist/ui/assets/status-page-eA8uRLZQ.js +2 -0
  59. package/dist/ui/assets/{status-page-CgRNt0wi.js.map → status-page-eA8uRLZQ.js.map} +1 -1
  60. package/dist/ui/assets/studio-CeTsQ4gf.js +2 -0
  61. package/dist/ui/assets/studio-CeTsQ4gf.js.map +1 -0
  62. package/dist/ui/assets/textarea-DESKg_ZK.js +2 -0
  63. package/dist/ui/assets/textarea-DESKg_ZK.js.map +1 -0
  64. package/dist/ui/assets/tool-runner-D3-SlGqQ.js +2 -0
  65. package/dist/ui/assets/{tool-runner-BZN-PZJU.js.map → tool-runner-D3-SlGqQ.js.map} +1 -1
  66. package/dist/ui/assets/tools-page-D3gnQf1_.js +2 -0
  67. package/dist/ui/assets/{tools-page-DRv7aklU.js.map → tools-page-D3gnQf1_.js.map} +1 -1
  68. package/dist/ui/assets/trace-browser-D93H7Xxs.js +10 -0
  69. package/dist/ui/assets/trace-browser-D93H7Xxs.js.map +1 -0
  70. package/dist/ui/assets/transcript-item-BmjdZLbS.js +3 -0
  71. package/dist/ui/assets/transcript-item-BmjdZLbS.js.map +1 -0
  72. package/dist/ui/assets/with-selector-CRZWaZzC.js +2 -0
  73. package/dist/ui/assets/with-selector-CRZWaZzC.js.map +1 -0
  74. package/dist/ui/index.html +19 -3
  75. package/package.json +16 -11
  76. package/dist/ui/assets/ArrowClockwise.es-CSN-gjxO.js +0 -2
  77. package/dist/ui/assets/agents-page-CkNSbqAj.js +0 -2
  78. package/dist/ui/assets/agents-page-CkNSbqAj.js.map +0 -1
  79. package/dist/ui/assets/chunk-ADH7NNCS-DM6fsnDI.js +0 -2
  80. package/dist/ui/assets/chunk-ADH7NNCS-DM6fsnDI.js.map +0 -1
  81. package/dist/ui/assets/index-Bh7RLPBw.js +0 -56
  82. package/dist/ui/assets/index-Bh7RLPBw.js.map +0 -1
  83. package/dist/ui/assets/index-D-CbFx6c.css +0 -1
  84. package/dist/ui/assets/knowledge-page-Cot_uYcE.js +0 -2
  85. package/dist/ui/assets/mcps-page-BSO6ork4.js +0 -2
  86. package/dist/ui/assets/memory-page-CG84G--r.js +0 -4
  87. package/dist/ui/assets/memory-page-CG84G--r.js.map +0 -1
  88. package/dist/ui/assets/pipelines-page-Hbf5ffUH.js +0 -8
  89. package/dist/ui/assets/pipelines-page-Hbf5ffUH.js.map +0 -1
  90. package/dist/ui/assets/sandboxes-page-D910dyFO.js +0 -2
  91. package/dist/ui/assets/sandboxes-page-D910dyFO.js.map +0 -1
  92. package/dist/ui/assets/sessions-page-CSN2vSKZ.js +0 -2
  93. package/dist/ui/assets/status-page-CgRNt0wi.js +0 -2
  94. package/dist/ui/assets/tool-runner-BZN-PZJU.js +0 -2
  95. package/dist/ui/assets/tools-page-DRv7aklU.js +0 -2
  96. package/dist/ui/assets/trace-browser-B07coNNE.js +0 -10
  97. package/dist/ui/assets/trace-browser-B07coNNE.js.map +0 -1
  98. package/dist/ui/assets/transcript-item-ZNkQEaCw.js +0 -3
  99. package/dist/ui/assets/transcript-item-ZNkQEaCw.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  // src/runtime/studio.ts
2
- import { Agent as Agent2 } from "@anvia/core/internal/agent";
2
+ import { Agent, getAgentToolState as getAgentToolState3 } from "@anvia/core/internal/agent";
3
3
  import { Pipeline } from "@anvia/core/pipeline";
4
4
  import { serve } from "@hono/node-server";
5
5
  import { Hono as HonoApp } from "hono";
6
+ import { WebSocketServer } from "ws";
6
7
 
7
8
  // src/runtime/json.ts
8
9
  function toJsonValue(value) {
@@ -159,6 +160,20 @@ var StudioRunTraceObserver = class {
159
160
  this.observations.push(parentObservation);
160
161
  this.observations.push(...childTrace.observations(parentObservation.id));
161
162
  },
163
+ suspend: (suspendArgs) => {
164
+ this.observations.push(
165
+ traceObservation({
166
+ kind: "tool",
167
+ name: args.toolName,
168
+ status: "suspended",
169
+ turn: args.turn,
170
+ startedAt,
171
+ input: parseOrString(args.args),
172
+ output: toJsonValue({ interaction: suspendArgs.interaction }),
173
+ metadata: toolMetadata(args, false)
174
+ })
175
+ );
176
+ },
162
177
  error: (errorArgs) => {
163
178
  const parentObservation = traceObservation({
164
179
  kind: "tool",
@@ -176,9 +191,23 @@ var StudioRunTraceObserver = class {
176
191
  };
177
192
  }
178
193
  async end(args) {
179
- await this.save("success", {
194
+ const result = {
195
+ runId: this.props.args.runId,
196
+ status: args.status,
197
+ text: args.text
198
+ };
199
+ if (args.resumedFrom !== void 0) result.resumedFrom = args.resumedFrom;
200
+ if (args.status === "completed") {
201
+ result.output = toJsonValue(args.output);
202
+ } else if (args.status === "blocked") {
203
+ result.stage = args.stage;
204
+ } else {
205
+ result.interaction = toJsonValue(args.interaction);
206
+ }
207
+ await this.save(args.status === "suspended" ? "suspended" : "success", {
180
208
  endedAt: /* @__PURE__ */ new Date(),
181
- output: args.output,
209
+ output: args.status === "completed" ? traceOutput(args.output) : args.text,
210
+ result,
182
211
  usage: args.usage,
183
212
  messages: toJsonValue(args.messages)
184
213
  });
@@ -198,11 +227,13 @@ var StudioRunTraceObserver = class {
198
227
  return;
199
228
  }
200
229
  const metadata = traceMetadata(this.props.args, result.messages);
230
+ if (result.result !== void 0) metadata.result = result.result;
201
231
  const trace = {
202
232
  id: this.props.id,
233
+ runId: this.props.args.runId,
203
234
  sessionId,
204
235
  status,
205
- trace: this.trace,
236
+ trace: { observer: "studio", ...this.trace },
206
237
  startedAt: this.startedAt.toISOString(),
207
238
  endedAt: result.endedAt.toISOString(),
208
239
  durationMs: durationMs(this.startedAt, result.endedAt),
@@ -281,15 +312,14 @@ var ChildAgentToolTraceAccumulator = class {
281
312
  }
282
313
  if (child.type === "tool_call" && isRecord(child.toolCall)) {
283
314
  const toolCall = child.toolCall;
284
- const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
285
- const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
286
- const callId = typeof toolCall.callId === "string" ? toolCall.callId : typeof toolCall.id === "string" ? toolCall.id : void 0;
315
+ const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : "tool";
316
+ const callId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : typeof toolCall.callId === "string" ? toolCall.callId : void 0;
287
317
  const start = {
288
318
  startedAt: /* @__PURE__ */ new Date(),
289
319
  agentId,
290
320
  childTurn,
291
321
  toolName,
292
- input: toJsonValue(toolCallFunction?.arguments ?? {}),
322
+ input: toJsonValue("input" in toolCall ? toolCall.input : {}),
293
323
  completed: false
294
324
  };
295
325
  if (agentName !== void 0) start.agentName = agentName;
@@ -427,6 +457,7 @@ function traceObservation(props) {
427
457
  }
428
458
  function traceMetadata(args, messages) {
429
459
  return compactJsonObject({
460
+ runId: args.runId,
430
461
  agentName: args.agentName,
431
462
  agentDescription: args.agentDescription,
432
463
  maxTurns: args.maxTurns,
@@ -441,14 +472,12 @@ function generationMetadata(args, endArgs) {
441
472
  const request = args.request;
442
473
  const response = endArgs?.response;
443
474
  const rawResponse = isRecord(response?.rawResponse) ? response.rawResponse : void 0;
444
- const effectiveModel = request.model ?? stringValue(rawResponse?.model) ?? args.modelInfo?.defaultModel ?? "default";
475
+ const modelId = args.modelInfo?.modelId ?? stringValue(rawResponse?.model) ?? "unknown";
445
476
  const providerResponse = providerResponseSummary(rawResponse);
446
477
  const usage = response?.usage;
447
478
  return compactJsonObject({
448
479
  provider: args.modelInfo?.provider,
449
- model: effectiveModel,
450
- requestedModel: request.model,
451
- defaultModel: args.modelInfo?.defaultModel,
480
+ modelId,
452
481
  messageId: response?.messageId,
453
482
  usage,
454
483
  toolCount: request.tools.length,
@@ -458,15 +487,13 @@ function generationMetadata(args, endArgs) {
458
487
  temperature: request.temperature,
459
488
  maxTokens: request.maxTokens,
460
489
  toolChoice: request.toolChoice,
461
- additionalParamKeys: isRecord(request.additionalParams) ? Object.keys(request.additionalParams).sort() : void 0,
490
+ providerOptionKeys: isRecord(request.providerOptions) ? Object.keys(request.providerOptions).sort() : void 0,
462
491
  hasOutputSchema: request.outputSchema !== void 0,
463
492
  firstDeltaMs: endArgs?.firstDeltaMs,
464
493
  providerResponse,
465
494
  modelInfo: compactJsonObject({
466
495
  provider: args.modelInfo?.provider,
467
- model: effectiveModel,
468
- requestedModel: request.model,
469
- defaultModel: args.modelInfo?.defaultModel,
496
+ modelId,
470
497
  capabilities: args.modelInfo?.capabilities
471
498
  }),
472
499
  modelCall: compactJsonObject({
@@ -492,7 +519,6 @@ function generationMetadata(args, endArgs) {
492
519
  }
493
520
  function completionRequestSummary(request) {
494
521
  return compactJsonObject({
495
- model: request.model,
496
522
  instructions: request.instructions === void 0 ? void 0 : { present: true },
497
523
  messageCount: request.chatHistory.length,
498
524
  documentCount: request.documents.length,
@@ -502,7 +528,7 @@ function completionRequestSummary(request) {
502
528
  temperature: request.temperature,
503
529
  maxTokens: request.maxTokens,
504
530
  toolChoice: request.toolChoice,
505
- additionalParamKeys: isRecord(request.additionalParams) ? Object.keys(request.additionalParams).sort() : void 0,
531
+ providerOptionKeys: isRecord(request.providerOptions) ? Object.keys(request.providerOptions).sort() : void 0,
506
532
  hasOutputSchema: request.outputSchema !== void 0
507
533
  });
508
534
  }
@@ -539,7 +565,6 @@ function toolMetadata(args, skipped, result) {
539
565
  argumentBytes: byteLength(args.args),
540
566
  resultBytes: result === void 0 ? void 0 : byteLength(result),
541
567
  hasCallSignature: args.toolCall.signature !== void 0,
542
- hasAdditionalParams: args.toolCall.additionalParams !== void 0,
543
568
  toolDescription: args.toolDefinition?.description,
544
569
  parameterKeys: Object.keys(properties).sort(),
545
570
  requiredParameterKeys: required,
@@ -557,8 +582,7 @@ function toolMetadata(args, skipped, result) {
557
582
  mcpServerName: args.toolMetadata?.mcpServerName,
558
583
  argumentBytes: byteLength(args.args),
559
584
  resultBytes: result === void 0 ? void 0 : byteLength(result),
560
- hasCallSignature: args.toolCall.signature !== void 0,
561
- hasAdditionalParams: args.toolCall.additionalParams !== void 0
585
+ hasCallSignature: args.toolCall.signature !== void 0
562
586
  })
563
587
  });
564
588
  }
@@ -575,6 +599,16 @@ function parseOrString(value) {
575
599
  return value;
576
600
  }
577
601
  }
602
+ function traceOutput(value) {
603
+ if (typeof value === "string") {
604
+ return value;
605
+ }
606
+ try {
607
+ return JSON.stringify(toJsonValue(value));
608
+ } catch {
609
+ return String(value);
610
+ }
611
+ }
578
612
  function isRecord(value) {
579
613
  return typeof value === "object" && value !== null && !Array.isArray(value);
580
614
  }
@@ -874,70 +908,31 @@ function escapeHtml(value) {
874
908
  return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
875
909
  }
876
910
 
911
+ // src/runtime/agent-context.ts
912
+ import { isVectorContext } from "@anvia/core/agent";
913
+ function staticContextDocuments(agent) {
914
+ return agent.context.filter((input) => !isVectorContext(input));
915
+ }
916
+ function vectorContexts(agent) {
917
+ return agent.context.filter(isVectorContext);
918
+ }
919
+
877
920
  // src/runtime/agent-runs.ts
878
- import { Message } from "@anvia/core/completion";
921
+ import {
922
+ withInternalAgentRunOptions
923
+ } from "@anvia/core/internal/agent";
879
924
 
880
925
  // src/runtime/agent-utils.ts
881
926
  import {
882
- createHook
883
- } from "@anvia/core/hooks";
884
- import { Agent } from "@anvia/core/internal/agent";
927
+ createResolvedAgent,
928
+ getResolvedAgentOptions
929
+ } from "@anvia/core/internal/agent";
885
930
  function cloneAgent(agent, overrides = {}) {
886
- return new Agent({
887
- id: agent.id,
888
- name: agent.name,
889
- description: agent.description,
890
- model: agent.model,
891
- instructions: agent.instructions,
892
- staticContext: agent.staticContext,
893
- temperature: agent.temperature,
894
- maxTokens: agent.maxTokens,
895
- additionalParams: agent.additionalParams,
896
- toolSet: agent.toolSet,
897
- toolChoice: agent.toolChoice,
898
- defaultMaxTurns: agent.defaultMaxTurns,
899
- hook: agent.hook,
900
- outputSchema: agent.outputSchema,
901
- observers: agent.observers,
902
- approvals: agent.approvals,
903
- dynamicContexts: agent.dynamicContexts,
904
- dynamicTools: agent.dynamicTools,
905
- middlewares: agent.middlewares,
906
- memory: agent.memory,
907
- eventStore: agent.eventStore,
931
+ return createResolvedAgent({
932
+ ...getResolvedAgentOptions(agent),
908
933
  ...overrides
909
934
  });
910
935
  }
911
- function composeHooks(first, second) {
912
- if (first === void 0) {
913
- return second;
914
- }
915
- if (second === void 0) {
916
- return first;
917
- }
918
- return createHook({
919
- async onCompletionCall(args) {
920
- const firstAction = await first.onCompletionCall?.(args);
921
- return firstAction?.type === "terminate" ? firstAction : await second.onCompletionCall?.(args) ?? void 0;
922
- },
923
- async onCompletionResponse(args) {
924
- const firstAction = await first.onCompletionResponse?.(args);
925
- return firstAction?.type === "terminate" ? firstAction : await second.onCompletionResponse?.(args) ?? void 0;
926
- },
927
- async onToolCall(args) {
928
- const firstAction = await first.onToolCall?.(args);
929
- if (firstAction?.type === "skip" || firstAction?.type === "terminate" || firstAction?.type === "approval_request") {
930
- return firstAction;
931
- }
932
- const secondAction = await second.onToolCall?.(args);
933
- return secondAction ?? firstAction ?? void 0;
934
- },
935
- async onToolResult(args) {
936
- const firstAction = await first.onToolResult?.(args);
937
- return firstAction?.type === "terminate" ? firstAction : await second.onToolResult?.(args) ?? void 0;
938
- }
939
- });
940
- }
941
936
 
942
937
  // src/runtime/errors.ts
943
938
  function serializeError(error) {
@@ -952,7 +947,8 @@ function serializeError(error) {
952
947
 
953
948
  // src/runtime/type-guards.ts
954
949
  import {
955
- isJsonValue as isCoreJsonValue
950
+ isJsonValue as isCoreJsonValue,
951
+ isMessage
956
952
  } from "@anvia/core/completion";
957
953
  function isObject(value) {
958
954
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -963,26 +959,16 @@ function isJsonObject(value) {
963
959
  function isJsonValue(value) {
964
960
  return isCoreJsonValue(value);
965
961
  }
966
- function isMessageInput(value) {
967
- return typeof value === "string" || isMessage(value);
968
- }
969
- function isMessage(value) {
970
- if (!isObject(value) || typeof value.role !== "string") {
971
- return false;
972
- }
973
- if (value.role === "system") {
974
- return typeof value.content === "string";
975
- }
976
- if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
977
- return Array.isArray(value.content);
978
- }
979
- return false;
980
- }
981
962
  function isAgentTraceOptions(value) {
982
963
  if (!isObject(value)) {
983
964
  return false;
984
965
  }
985
- 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);
966
+ if (!Object.keys(value).every(
967
+ (key) => ["name", "userId", "sessionId", "metadata", "tags", "version", "traceId"].includes(key)
968
+ )) {
969
+ return false;
970
+ }
971
+ return optionalString(value.name) && optionalString(value.userId) && optionalString(value.sessionId) && optionalString(value.version) && optionalString(value.traceId) && optionalStringArray(value.tags) && (value.metadata === void 0 || isJsonObject(value.metadata));
986
972
  }
987
973
  function isNonNegativeInteger(value) {
988
974
  return Number.isInteger(value) && typeof value === "number" && value >= 0;
@@ -993,15 +979,9 @@ function isPositiveInteger(value) {
993
979
  function optionalString(value) {
994
980
  return value === void 0 || typeof value === "string";
995
981
  }
996
- function optionalBoolean(value) {
997
- return value === void 0 || typeof value === "boolean";
998
- }
999
982
  function optionalStringArray(value) {
1000
983
  return value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
1001
984
  }
1002
- function optionalObject(value) {
1003
- return value === void 0 || isObject(value);
1004
- }
1005
985
 
1006
986
  // src/runtime/http.ts
1007
987
  function errorResponse(c, status, code, message, details) {
@@ -1051,8 +1031,8 @@ function createStudioModelRegistry(config) {
1051
1031
  id,
1052
1032
  staticModels
1053
1033
  };
1054
- if (provider.defaultModel !== void 0) {
1055
- runtimeProvider.defaultModel = normalizeModelId(provider.defaultModel);
1034
+ if (provider.defaultModelId !== void 0) {
1035
+ runtimeProvider.defaultModelId = normalizeModelId(provider.defaultModelId);
1056
1036
  }
1057
1037
  providers.set(id, runtimeProvider);
1058
1038
  }
@@ -1061,8 +1041,10 @@ function createStudioModelRegistry(config) {
1061
1041
  agentPolicies: normalizeAgentPolicies(config.agents ?? {}),
1062
1042
  modelCache: /* @__PURE__ */ new Map()
1063
1043
  };
1064
- if (config.default !== void 0) {
1065
- Object.assign(registry, { defaultModel: normalizeModelRef(config.default) });
1044
+ if (config.defaultModelRef !== void 0) {
1045
+ Object.assign(registry, {
1046
+ defaultModelRef: normalizeModelRef(config.defaultModelRef)
1047
+ });
1066
1048
  }
1067
1049
  return registry;
1068
1050
  }
@@ -1079,7 +1061,7 @@ function studioModelsConfig(registry, agents) {
1079
1061
  models
1080
1062
  };
1081
1063
  if (provider.name !== void 0) config2.name = provider.name;
1082
- if (provider.defaultModel !== void 0) config2.defaultModel = provider.defaultModel;
1064
+ if (provider.defaultModelId !== void 0) config2.defaultModelId = provider.defaultModelId;
1083
1065
  if (provider.metadata !== void 0) config2.metadata = provider.metadata;
1084
1066
  return config2;
1085
1067
  });
@@ -1091,7 +1073,7 @@ function studioModelsConfig(registry, agents) {
1091
1073
  providers,
1092
1074
  agents: agentsConfig
1093
1075
  };
1094
- if (registry.defaultModel !== void 0) config.default = registry.defaultModel;
1076
+ if (registry.defaultModelRef !== void 0) config.defaultModelRef = registry.defaultModelRef;
1095
1077
  return config;
1096
1078
  }
1097
1079
  function registerModelRoutes(app, props) {
@@ -1105,8 +1087,8 @@ function registerModelRoutes(app, props) {
1105
1087
  const response = {
1106
1088
  providers
1107
1089
  };
1108
- if (props.registry.defaultModel !== void 0) {
1109
- response.defaultModel = props.registry.defaultModel;
1090
+ if (props.registry.defaultModelRef !== void 0) {
1091
+ response.defaultModelRef = props.registry.defaultModelRef;
1110
1092
  }
1111
1093
  return c.json(response);
1112
1094
  });
@@ -1139,7 +1121,7 @@ function resolveStudioModel(registry, input) {
1139
1121
  if (registry === void 0) {
1140
1122
  return { warnings: [] };
1141
1123
  }
1142
- const selectedRef = normalizeOptionalModelRef(input.request.model) ?? sessionModelRef(input.sessionMetadata) ?? registry.agentPolicies.get(input.agent.id)?.default ?? registry.defaultModel;
1124
+ const selectedRef = normalizeOptionalModelRef(input.request.model) ?? sessionModelRef(input.sessionMetadata) ?? registry.agentPolicies.get(input.agent.id)?.defaultModelRef ?? registry.defaultModelRef;
1143
1125
  if (selectedRef === void 0) {
1144
1126
  return { warnings: [] };
1145
1127
  }
@@ -1151,7 +1133,7 @@ function resolveStudioModel(registry, input) {
1151
1133
  }
1152
1134
  let model = registry.modelCache.get(selectedRef);
1153
1135
  if (model === void 0) {
1154
- model = provider.createCompletionModel(modelId);
1136
+ model = provider.createCompletionModel({ modelId });
1155
1137
  registry.modelCache.set(selectedRef, model);
1156
1138
  }
1157
1139
  const metadata = provider.staticModels.get(modelId);
@@ -1169,12 +1151,7 @@ function normalizeOptionalModelRef(ref) {
1169
1151
  return ref === void 0 ? void 0 : normalizeModelRef(ref);
1170
1152
  }
1171
1153
  function normalizeModelRef(ref) {
1172
- if (typeof ref === "string") {
1173
- const trimmed = ref.trim();
1174
- const parsed = parseModelRef(trimmed);
1175
- return `${parsed.providerId}:${parsed.modelId}`;
1176
- }
1177
- return `${normalizeProviderId(ref.provider)}:${normalizeModelId(ref.model)}`;
1154
+ return `${normalizeProviderId(ref.providerId)}:${normalizeModelId(ref.modelId)}`;
1178
1155
  }
1179
1156
  function parseModelRef(ref) {
1180
1157
  const index = ref.indexOf(":");
@@ -1217,12 +1194,12 @@ async function agentModelsCatalog(registry, agent) {
1217
1194
  return [];
1218
1195
  }
1219
1196
  });
1220
- const defaultModel = policy?.default ?? registry.defaultModel;
1197
+ const defaultModelRef = policy?.defaultModelRef ?? registry.defaultModelRef;
1221
1198
  const summary = {
1222
1199
  agentId: agent.id,
1223
1200
  models: [...models, ...exactPolicyModels]
1224
1201
  };
1225
- if (defaultModel !== void 0) summary.defaultModel = defaultModel;
1202
+ if (defaultModelRef !== void 0) summary.defaultModelRef = defaultModelRef;
1226
1203
  if (warnings.length > 0) summary.warnings = warnings;
1227
1204
  return summary;
1228
1205
  }
@@ -1260,7 +1237,7 @@ async function providerCatalog(provider) {
1260
1237
  models: [...models.values()].sort((left, right) => left.ref.localeCompare(right.ref))
1261
1238
  };
1262
1239
  if (provider.name !== void 0) catalog.name = provider.name;
1263
- if (provider.defaultModel !== void 0) catalog.defaultModel = provider.defaultModel;
1240
+ if (provider.defaultModelId !== void 0) catalog.defaultModelId = provider.defaultModelId;
1264
1241
  if (provider.metadata !== void 0) catalog.metadata = provider.metadata;
1265
1242
  if (warning !== void 0) catalog.warning = warning;
1266
1243
  return catalog;
@@ -1294,19 +1271,33 @@ function normalizeAgentPolicies(policies) {
1294
1271
  return new Map(
1295
1272
  Object.entries(policies).map(([agentId, policy]) => {
1296
1273
  const normalized = {};
1297
- if (policy.default !== void 0) normalized.default = normalizeModelRef(policy.default);
1274
+ if (policy.defaultModelRef !== void 0) {
1275
+ normalized.defaultModelRef = normalizeModelRef(policy.defaultModelRef);
1276
+ }
1298
1277
  if (policy.allowed !== void 0) {
1299
- normalized.allowed = policy.allowed.map(
1300
- (entry) => typeof entry === "string" && entry.trim().endsWith(":*") ? `${normalizeProviderId(entry.trim().slice(0, -2))}:*` : normalizeModelRef(entry)
1301
- );
1278
+ normalized.allowed = policy.allowed.map((entry) => {
1279
+ if (typeof entry === "string") {
1280
+ return normalizeWildcardModelRef(entry);
1281
+ }
1282
+ return normalizeModelRef(entry);
1283
+ });
1302
1284
  }
1303
1285
  return [agentId.trim(), normalized];
1304
1286
  })
1305
1287
  );
1306
1288
  }
1289
+ function normalizeWildcardModelRef(ref) {
1290
+ const normalized = ref.trim();
1291
+ if (!normalized.endsWith(":*")) {
1292
+ throw new ModelSelectionError(`Invalid wildcard model reference: ${ref}`);
1293
+ }
1294
+ return `${normalizeProviderId(normalized.slice(0, -2))}:*`;
1295
+ }
1307
1296
  function publicPolicy(policy) {
1308
1297
  const config = {};
1309
- if (policy.default !== void 0) config.default = policy.default;
1298
+ if (policy.defaultModelRef !== void 0) {
1299
+ config.defaultModelRef = policy.defaultModelRef;
1300
+ }
1310
1301
  if (policy.allowed !== void 0) config.allowed = policy.allowed;
1311
1302
  return config;
1312
1303
  }
@@ -1337,15 +1328,13 @@ function modelWarnings(ref, model, request) {
1337
1328
  function requestModalities(request) {
1338
1329
  const modalities = /* @__PURE__ */ new Set(["text"]);
1339
1330
  for (const message of requestMessages(request)) {
1340
- if (typeof message === "string") {
1341
- continue;
1342
- }
1343
1331
  if (message.role === "user" || message.role === "assistant") {
1332
+ if (typeof message.content === "string") continue;
1344
1333
  for (const content of message.content) {
1345
1334
  if (content.type === "image") {
1346
1335
  modalities.add("image");
1347
1336
  }
1348
- if (content.type === "document") {
1337
+ if (content.type === "file") {
1349
1338
  modalities.add("document");
1350
1339
  }
1351
1340
  }
@@ -1354,7 +1343,7 @@ function requestModalities(request) {
1354
1343
  return modalities;
1355
1344
  }
1356
1345
  function requestMessages(request) {
1357
- return [...request.history ?? [], request.message];
1346
+ return request.type === "messages" ? request.messages : [];
1358
1347
  }
1359
1348
  function normalizeProviderId(id) {
1360
1349
  const trimmed = id.trim();
@@ -1372,7 +1361,7 @@ function normalizeModelId(id) {
1372
1361
  }
1373
1362
 
1374
1363
  // src/runtime/streams.ts
1375
- import { createEventStream } from "@anvia/server";
1364
+ import { createClientStreamResponse, createEventStreamResponse } from "@anvia/server";
1376
1365
  var studioJsonlHeaders = {
1377
1366
  "cache-control": "no-cache, no-transform",
1378
1367
  connection: "keep-alive",
@@ -1381,7 +1370,15 @@ var studioJsonlHeaders = {
1381
1370
  "x-accel-buffering": "no"
1382
1371
  };
1383
1372
  function streamStudioJsonl(events) {
1384
- return createEventStream(withStudioStreamErrors(events), {
1373
+ return createEventStreamResponse({
1374
+ events: withStudioStreamErrors(events),
1375
+ format: "jsonl",
1376
+ headers: studioJsonlHeaders
1377
+ });
1378
+ }
1379
+ function streamStudioClient(options) {
1380
+ return createClientStreamResponse({
1381
+ events: options.events,
1385
1382
  format: "jsonl",
1386
1383
  headers: studioJsonlHeaders
1387
1384
  });
@@ -1400,8 +1397,9 @@ function withStudioStreamErrors(events) {
1400
1397
  const next = await iterator.next();
1401
1398
  if (next.done === true) {
1402
1399
  done = true;
1400
+ return next;
1403
1401
  }
1404
- return next;
1402
+ return { done: false, value: serializeStreamError(next.value) };
1405
1403
  } catch (error) {
1406
1404
  done = true;
1407
1405
  return {
@@ -1419,6 +1417,12 @@ function withStudioStreamErrors(events) {
1419
1417
  }
1420
1418
  };
1421
1419
  }
1420
+ function serializeStreamError(event) {
1421
+ if (typeof event !== "object" || event === null || !("type" in event) || event.type !== "error" || !("error" in event)) {
1422
+ return event;
1423
+ }
1424
+ return { ...event, error: serializeError(event.error) };
1425
+ }
1422
1426
  function studioStreamError(error) {
1423
1427
  return {
1424
1428
  type: "error",
@@ -1435,6 +1439,7 @@ function traceSummary(trace) {
1435
1439
  startedAt: trace.startedAt,
1436
1440
  observationCount: trace.observations.length
1437
1441
  };
1442
+ if (trace.runId !== void 0) summary.runId = trace.runId;
1438
1443
  if (trace.name !== void 0) summary.name = trace.name;
1439
1444
  if (trace.endedAt !== void 0) summary.endedAt = trace.endedAt;
1440
1445
  if (trace.durationMs !== void 0) summary.durationMs = trace.durationMs;
@@ -1626,6 +1631,16 @@ function isEventType(value) {
1626
1631
  return value === "session_log" || value === "pipeline_log" || value === "trace";
1627
1632
  }
1628
1633
 
1634
+ // src/runtime/runs.ts
1635
+ import {
1636
+ customAgentEventsToClientStream
1637
+ } from "@anvia/client";
1638
+ import { parseAgentInteractionResponse } from "@anvia/core/agent/interactions";
1639
+ import {
1640
+ isJsonValue as isJsonValue2,
1641
+ parseMessages
1642
+ } from "@anvia/core/completion";
1643
+
1629
1644
  // src/runtime/transcript.ts
1630
1645
  function renumberTranscript(entries) {
1631
1646
  return entries.map((entry, entryId) => ({ ...entry, entryId }));
@@ -1639,7 +1654,8 @@ function transcriptFromMessages(messages) {
1639
1654
  if (message.role === "user") {
1640
1655
  const attachments = attachmentsFromMessage(message);
1641
1656
  let textEntryAdded = false;
1642
- for (const content of message.content) {
1657
+ const contents2 = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
1658
+ for (const content of contents2) {
1643
1659
  if (content.type === "text") {
1644
1660
  const entry = {
1645
1661
  entryId: transcript.length,
@@ -1653,32 +1669,36 @@ function transcriptFromMessages(messages) {
1653
1669
  }
1654
1670
  }
1655
1671
  if (!textEntryAdded && attachments.length > 0) {
1656
- transcript.push({
1672
+ const entry = {
1657
1673
  entryId: transcript.length,
1658
1674
  kind: "message",
1659
1675
  role: "user",
1660
1676
  text: "",
1661
1677
  attachments
1662
- });
1678
+ };
1679
+ transcript.push(entry);
1663
1680
  }
1664
1681
  continue;
1665
1682
  }
1666
1683
  if (message.role === "tool") {
1667
1684
  for (const content of message.content) {
1668
- transcript.push({
1685
+ if (content.type !== "tool-result") {
1686
+ continue;
1687
+ }
1688
+ const entry = {
1669
1689
  entryId: transcript.length,
1670
1690
  kind: "tool",
1671
- toolName: "tool_result",
1672
- callId: content.callId ?? content.id,
1673
- result: content.content.map(
1674
- (item) => "text" in item ? item.text : `[image:${item.mediaType ?? "image/png"}]`
1675
- ).join("\n"),
1676
- structuredResult: content.content
1677
- });
1691
+ toolName: content.toolName,
1692
+ callId: content.callId ?? content.toolCallId,
1693
+ result: toolResultText(content.output)
1694
+ };
1695
+ if (content.output.type === "content") entry.structuredResult = content.output.value;
1696
+ transcript.push(entry);
1678
1697
  }
1679
1698
  continue;
1680
1699
  }
1681
- for (const content of message.content) {
1700
+ const contents = typeof message.content === "string" ? [{ type: "text", text: message.content }] : message.content;
1701
+ for (const content of contents) {
1682
1702
  if (content.type === "text") {
1683
1703
  appendAssistantTranscriptText(transcript, content.text);
1684
1704
  } else if (content.type === "reasoning") {
@@ -1689,13 +1709,13 @@ function transcriptFromMessages(messages) {
1689
1709
  };
1690
1710
  if (content.id !== void 0) entry.reasoningId = content.id;
1691
1711
  transcript.push(entry);
1692
- } else if (content.type === "tool_call") {
1712
+ } else if (content.type === "tool-call") {
1693
1713
  transcript.push({
1694
1714
  entryId: transcript.length,
1695
1715
  kind: "tool",
1696
- toolName: content.function.name,
1697
- callId: content.callId ?? content.id,
1698
- args: formatJson(content.function.arguments)
1716
+ toolName: content.toolName,
1717
+ callId: content.callId ?? content.toolCallId,
1718
+ args: formatJson(content.input)
1699
1719
  });
1700
1720
  }
1701
1721
  }
@@ -1706,28 +1726,37 @@ function attachmentsFromMessage(message) {
1706
1726
  if (message.role !== "user" && message.role !== "assistant") {
1707
1727
  return [];
1708
1728
  }
1729
+ if (typeof message.content === "string") return [];
1709
1730
  return message.content.flatMap((content) => {
1710
1731
  if (content.type === "image") {
1711
1732
  const attachment = { kind: "image" };
1712
- if (content.source.type === "base64") {
1713
- attachment.data = content.source.data;
1714
- attachment.mediaType = content.source.mediaType;
1733
+ if (content.image.type === "data") {
1734
+ attachment.data = content.image.data;
1735
+ if (content.mediaType !== void 0) attachment.mediaType = content.mediaType;
1715
1736
  } else {
1716
- attachment.url = content.source.url;
1737
+ attachment.url = content.image.url;
1717
1738
  }
1718
1739
  return [attachment];
1719
1740
  }
1720
- if (content.type === "document") {
1741
+ if (content.type === "file") {
1721
1742
  const attachment = { kind: "document" };
1722
- if (content.source.filename !== void 0) attachment.name = content.source.filename;
1723
- if (content.source.mediaType !== void 0) attachment.mediaType = content.source.mediaType;
1724
- if (content.source.type === "base64") attachment.data = content.source.data;
1725
- if (content.source.type === "url") attachment.url = content.source.url;
1743
+ if (content.filename !== void 0) attachment.name = content.filename;
1744
+ attachment.mediaType = content.mediaType;
1745
+ if (content.data.type === "data") attachment.data = content.data.data;
1746
+ if (content.data.type === "url") attachment.url = content.data.url;
1726
1747
  return [attachment];
1727
1748
  }
1728
1749
  return [];
1729
1750
  });
1730
1751
  }
1752
+ function toolResultText(output) {
1753
+ if (output.type === "text" || output.type === "error-text") return output.value;
1754
+ if (output.type === "json" || output.type === "error-json") return formatJson(output.value);
1755
+ if (output.type === "execution-denied") return output.reason ?? "Execution denied";
1756
+ return output.value.map(
1757
+ (part) => part.type === "text" ? part.text : `[file:${part.mediaType}${part.filename === void 0 ? "" : `:${part.filename}`}]`
1758
+ ).join("\n");
1759
+ }
1731
1760
  function appendAssistantTranscriptText(transcript, text) {
1732
1761
  const last = transcript.at(-1);
1733
1762
  if (last?.kind === "message" && last.role === "assistant") {
@@ -1775,48 +1804,43 @@ var AsyncEventQueue = class {
1775
1804
  return new Promise((resolve2) => this.resolvers.push(resolve2));
1776
1805
  }
1777
1806
  };
1778
- async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
1779
- const runIterator = runEvents[Symbol.asyncIterator]();
1780
- let runDone = false;
1781
- let runNext = runIterator.next();
1782
- let approvalNext = approvalEvents.next();
1783
- try {
1784
- while (runNext !== void 0 || approvalNext !== void 0) {
1785
- const pending = [];
1786
- if (runNext !== void 0) {
1787
- pending.push(runNext.then((value) => ({ source: "run", value })));
1788
- }
1789
- if (approvalNext !== void 0) {
1790
- pending.push(approvalNext.then((value) => ({ source: "approval", value })));
1791
- }
1792
- const result = await Promise.race(pending);
1793
- if (result.source === "run") {
1794
- if (result.value.done === true) {
1795
- runDone = true;
1796
- runNext = void 0;
1797
- approvalEvents.close();
1798
- } else {
1799
- runNext = runIterator.next();
1800
- yield result.value.value;
1801
- }
1802
- continue;
1803
- }
1804
- if (result.value.done === true) {
1805
- approvalNext = void 0;
1806
- } else {
1807
- approvalNext = approvalEvents.next();
1808
- yield result.value.value;
1809
- }
1810
- }
1811
- } finally {
1812
- if (!runDone && runIterator.return !== void 0) {
1813
- await runIterator.return();
1814
- }
1815
- approvalEvents.close();
1816
- }
1807
+ function streamAgentRunEvents(_c, events, options) {
1808
+ return streamStudioClient({
1809
+ events: customAgentEventsToClientStream({
1810
+ events: withAgentRunCancellation(events, options.onCancel),
1811
+ runId: options.runId,
1812
+ mapCustomEvent: studioEventToClientEvent
1813
+ })
1814
+ });
1817
1815
  }
1818
- function streamAgentRunEvents(_c, events, options = {}) {
1819
- return streamStudioJsonl(withAgentRunCancellation(events, options.onCancel));
1816
+ function studioEventToClientEvent(event, context) {
1817
+ if (event.type === "session_log" && isJsonValue2(event.log)) {
1818
+ return {
1819
+ type: "data",
1820
+ runId: context.runId,
1821
+ name: "studio.session_log",
1822
+ data: event.log,
1823
+ transient: true
1824
+ };
1825
+ }
1826
+ if (event.type === "pipeline_log" && isJsonValue2(event.log)) {
1827
+ return {
1828
+ type: "data",
1829
+ runId: context.runId,
1830
+ name: "studio.pipeline_log",
1831
+ data: event.log,
1832
+ transient: true
1833
+ };
1834
+ }
1835
+ if (event.type === "pipeline_final" && isJsonValue2(event.output)) {
1836
+ return {
1837
+ type: "data",
1838
+ runId: context.runId,
1839
+ name: "studio.pipeline_final",
1840
+ data: event.output
1841
+ };
1842
+ }
1843
+ return void 0;
1820
1844
  }
1821
1845
  function withAgentRunCancellation(events, onCancel) {
1822
1846
  const iterator = events[Symbol.asyncIterator]();
@@ -1926,7 +1950,7 @@ function createPersistedStreamingSessionTranscript(props) {
1926
1950
  if (event.type === "final" || event.type === "error") {
1927
1951
  assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
1928
1952
  }
1929
- const eventStatus = event.type === "final" ? "success" : event.type === "error" ? "error" : "running";
1953
+ const eventStatus = event.type === "final" ? event.result.status === "suspended" ? "suspended" : "success" : event.type === "error" ? "error" : "running";
1930
1954
  const saveInput = {
1931
1955
  id: props.session.id,
1932
1956
  runId: props.runId,
@@ -1936,10 +1960,10 @@ function createPersistedStreamingSessionTranscript(props) {
1936
1960
  };
1937
1961
  if (event.type === "error") saveInput.error = serializeError(event.error);
1938
1962
  await saveTranscript(saveInput);
1939
- const generatedMessages = event.type === "final" ? event.messages.slice(1) : [];
1963
+ const generatedMessages = event.type === "final" ? event.result.messages.slice(props.generatedMessagesStartIndex ?? 1) : [];
1940
1964
  if (props.persistGeneratedMessages === true && generatedMessages.length > 0) {
1941
1965
  await props.store.append({
1942
- context: { sessionId: props.session.id },
1966
+ scope: { sessionId: props.session.id },
1943
1967
  runId: props.runId,
1944
1968
  turn: 1,
1945
1969
  messages: generatedMessages
@@ -2041,9 +2065,9 @@ function acceptTranscriptStreamEvent(transcript, event) {
2041
2065
  transcript.push({
2042
2066
  entryId: transcript.length,
2043
2067
  kind: "tool",
2044
- toolName: event.toolCall.function.name,
2045
- callId: event.toolCall.callId ?? event.toolCall.id,
2046
- args: formatJson(event.toolCall.function.arguments)
2068
+ toolName: event.toolCall.toolName,
2069
+ callId: event.toolCall.callId ?? event.toolCall.toolCallId,
2070
+ args: formatJson(event.toolCall.input)
2047
2071
  });
2048
2072
  }
2049
2073
  if (event.type === "tool_result") {
@@ -2084,86 +2108,72 @@ function acceptTranscriptStreamEvent(transcript, event) {
2084
2108
  }
2085
2109
  appendChildAgentTranscriptEvent(matched, event);
2086
2110
  }
2087
- if (event.type === "tool_approval_request") {
2088
- const matched = findTranscriptToolEntry(
2089
- transcript,
2090
- event.approval.toolName,
2091
- approvalCallId(event.approval)
2092
- );
2093
- if (matched !== void 0) {
2094
- matched.approval = {
2095
- id: event.approval.id,
2096
- status: event.approval.status,
2097
- requestedAt: event.approval.requestedAt
2098
- };
2099
- }
2100
- }
2101
- if (event.type === "tool_approval_result") {
2111
+ if (event.type === "final" && event.result.status === "suspended") {
2112
+ const interaction = event.result.interaction;
2102
2113
  const matched = findTranscriptToolEntry(
2103
2114
  transcript,
2104
- event.approval.toolName,
2105
- approvalCallId(event.approval)
2115
+ interaction.toolName,
2116
+ interaction.callId ?? interaction.toolCallId
2106
2117
  );
2107
- if (matched !== void 0) {
2118
+ if (matched !== void 0 && interaction.type === "tool-approval") {
2108
2119
  const approval = {
2109
- id: event.approval.id,
2110
- status: event.approval.status,
2111
- requestedAt: event.approval.requestedAt
2120
+ id: interaction.id,
2121
+ status: "pending",
2122
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString()
2112
2123
  };
2113
- if (event.approval.resolvedAt !== void 0) approval.resolvedAt = event.approval.resolvedAt;
2114
- if (event.approval.reason !== void 0) approval.reason = event.approval.reason;
2124
+ if (interaction.reason !== void 0) approval.reason = interaction.reason;
2115
2125
  matched.approval = approval;
2116
2126
  }
2117
- }
2118
- if (event.type === "tool_question_request") {
2119
- const matched = findTranscriptToolEntry(
2120
- transcript,
2121
- event.question.toolName,
2122
- questionCallId(event.question)
2123
- );
2124
- if (matched !== void 0) {
2127
+ if (matched !== void 0 && interaction.type === "tool-question") {
2125
2128
  matched.question = {
2126
- id: event.question.id,
2127
- status: event.question.status,
2128
- requestedAt: event.question.requestedAt,
2129
- questions: event.question.questions
2129
+ id: interaction.id,
2130
+ status: "pending",
2131
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
2132
+ questions: interaction.questions.map((question) => ({
2133
+ id: question.id,
2134
+ question: question.text,
2135
+ choices: [...question.choices ?? []],
2136
+ allowCustom: question.choices === void 0 || question.allowCustom === true
2137
+ }))
2130
2138
  };
2131
2139
  }
2132
2140
  }
2133
- if (event.type === "tool_question_result") {
2141
+ if (event.type === "interaction_response") {
2142
+ const response = event.response;
2134
2143
  const matched = findTranscriptToolEntry(
2135
2144
  transcript,
2136
- event.question.toolName,
2137
- questionCallId(event.question)
2145
+ response.toolName,
2146
+ response.callId ?? response.toolCallId
2138
2147
  );
2139
- if (matched !== void 0) {
2140
- const question = {
2141
- id: event.question.id,
2142
- status: event.question.status,
2143
- requestedAt: event.question.requestedAt,
2144
- questions: event.question.questions
2148
+ const respondedAt = (/* @__PURE__ */ new Date()).toISOString();
2149
+ if (matched?.approval !== void 0 && response.type === "tool-approval-response") {
2150
+ const approval = {
2151
+ ...matched.approval,
2152
+ status: response.approved ? "approved" : "rejected",
2153
+ resolvedAt: respondedAt
2154
+ };
2155
+ if (response.reason !== void 0) approval.reason = response.reason;
2156
+ matched.approval = approval;
2157
+ }
2158
+ if (matched?.question !== void 0 && response.type === "tool-question-response") {
2159
+ matched.question = {
2160
+ ...matched.question,
2161
+ status: "answered",
2162
+ answeredAt: respondedAt,
2163
+ answers: response.answers.map((answer) => ({
2164
+ questionId: answer.questionId,
2165
+ answer: answer.value
2166
+ }))
2145
2167
  };
2146
- if (event.question.answeredAt !== void 0) question.answeredAt = event.question.answeredAt;
2147
- if (event.question.cancelledAt !== void 0) {
2148
- question.cancelledAt = event.question.cancelledAt;
2149
- }
2150
- if (event.question.answers !== void 0) question.answers = event.question.answers;
2151
- matched.question = question;
2152
2168
  }
2153
2169
  }
2154
- if (event.type === "final" && event.trace?.traceId !== void 0) {
2155
- assignTranscriptTraceId(transcript, event.trace.traceId);
2170
+ if (event.type === "final" && event.result.trace?.traceId !== void 0) {
2171
+ assignTranscriptTraceId(transcript, event.result.trace.traceId);
2156
2172
  }
2157
2173
  if (event.type === "error") {
2158
2174
  appendTranscriptAssistantError(transcript, errorText(event.error));
2159
2175
  }
2160
2176
  }
2161
- function approvalCallId(approval) {
2162
- return approval.callId ?? approval.toolCallId;
2163
- }
2164
- function questionCallId(question) {
2165
- return question.callId ?? question.toolCallId;
2166
- }
2167
2177
  function appendChildAgentTranscriptEvent(entry, event) {
2168
2178
  const childEvent = childAgentTranscriptEvent(event);
2169
2179
  if (childEvent === void 0) {
@@ -2224,9 +2234,9 @@ function childAgentTranscriptEvent(event) {
2224
2234
  const entry = {
2225
2235
  kind: "tool",
2226
2236
  agentId: event.agentId,
2227
- toolName: child.toolCall.function.name,
2228
- callId: child.toolCall.callId ?? child.toolCall.id,
2229
- args: formatJson(child.toolCall.function.arguments)
2237
+ toolName: child.toolCall.toolName,
2238
+ callId: child.toolCall.callId ?? child.toolCall.toolCallId,
2239
+ args: formatJson(child.toolCall.input)
2230
2240
  };
2231
2241
  if (event.agentName !== void 0) entry.agentName = event.agentName;
2232
2242
  return entry;
@@ -2363,17 +2373,18 @@ function extractMessageText(message) {
2363
2373
  if (message.role === "system") {
2364
2374
  return message.content;
2365
2375
  }
2376
+ if (typeof message.content === "string") {
2377
+ return message.content;
2378
+ }
2366
2379
  return message.content.flatMap((item) => {
2367
2380
  if (item.type === "text" || item.type === "reasoning") {
2368
2381
  return [item.text];
2369
2382
  }
2370
- if (item.type === "tool_call") {
2371
- return [`${item.function.name}(${formatJson(item.function.arguments)})`];
2383
+ if (item.type === "tool-call") {
2384
+ return [`${item.toolName}(${formatJson(item.input)})`];
2372
2385
  }
2373
- if (item.type === "tool_result") {
2374
- return item.content.map(
2375
- (result) => "text" in result ? result.text : `[image:${result.mediaType ?? "image/png"}]`
2376
- );
2386
+ if (item.type === "tool-result") {
2387
+ return [toolResultOutputText(item.output)];
2377
2388
  }
2378
2389
  return [];
2379
2390
  }).join("\n");
@@ -2382,23 +2393,26 @@ function optionalTranscriptAttachments(message) {
2382
2393
  if (typeof message === "string" || message.role !== "user") {
2383
2394
  return {};
2384
2395
  }
2396
+ if (typeof message.content === "string") {
2397
+ return {};
2398
+ }
2385
2399
  const attachments = message.content.flatMap((content) => {
2386
2400
  if (content.type === "image") {
2387
2401
  const attachment = { kind: "image" };
2388
- if (content.source.type === "base64") {
2389
- attachment.data = content.source.data;
2390
- attachment.mediaType = content.source.mediaType;
2402
+ if (content.image.type === "data") {
2403
+ attachment.data = content.image.data;
2404
+ if (content.mediaType !== void 0) attachment.mediaType = content.mediaType;
2391
2405
  } else {
2392
- attachment.url = content.source.url;
2406
+ attachment.url = content.image.url;
2393
2407
  }
2394
2408
  return [attachment];
2395
2409
  }
2396
- if (content.type === "document") {
2410
+ if (content.type === "file") {
2397
2411
  const attachment = { kind: "document" };
2398
- if (content.source.filename !== void 0) attachment.name = content.source.filename;
2399
- if (content.source.mediaType !== void 0) attachment.mediaType = content.source.mediaType;
2400
- if (content.source.type === "base64") attachment.data = content.source.data;
2401
- if (content.source.type === "url") attachment.url = content.source.url;
2412
+ if (content.filename !== void 0) attachment.name = content.filename;
2413
+ attachment.mediaType = content.mediaType;
2414
+ if (content.data.type === "data") attachment.data = content.data.data;
2415
+ if (content.data.type === "url") attachment.url = content.data.url;
2402
2416
  return [attachment];
2403
2417
  }
2404
2418
  return [];
@@ -2415,32 +2429,63 @@ async function parseRunRequest(c) {
2415
2429
  if (!isObject(body)) {
2416
2430
  return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
2417
2431
  }
2418
- const request = "message" in body ? parseLegacyRunRequestBody(c, body) : parseUiRunRequestBody(c, body);
2432
+ if ("message" in body || "history" in body) {
2433
+ return {
2434
+ error: errorResponse(
2435
+ c,
2436
+ 400,
2437
+ "bad_request",
2438
+ "Legacy message/history requests are not supported; use messages"
2439
+ )
2440
+ };
2441
+ }
2442
+ const request = parseRunRequestBody(c, body);
2419
2443
  if ("error" in request) {
2420
2444
  return request;
2421
2445
  }
2422
2446
  return parseRunRequestOptions(c, body, request);
2423
2447
  }
2424
- function parseLegacyRunRequestBody(c, body) {
2425
- if (!isMessageInput(body.message)) {
2448
+ function parseRunRequestBody(c, body) {
2449
+ if (body.type === "interaction_response") {
2450
+ if (typeof body.interactionId !== "string" || body.interactionId.trim().length === 0) {
2451
+ return { error: errorResponse(c, 400, "bad_request", "interactionId must be a string") };
2452
+ }
2453
+ if (body.messages !== void 0 || body.sessionId !== void 0 || body.model !== void 0) {
2454
+ return {
2455
+ error: errorResponse(
2456
+ c,
2457
+ 400,
2458
+ "bad_request",
2459
+ "Interaction responses cannot include messages, sessionId, or model"
2460
+ )
2461
+ };
2462
+ }
2463
+ try {
2464
+ return {
2465
+ type: "interaction_response",
2466
+ interactionId: body.interactionId,
2467
+ response: parseAgentInteractionResponse(body.response)
2468
+ };
2469
+ } catch {
2470
+ return {
2471
+ error: errorResponse(c, 400, "bad_request", "response must be an interaction response")
2472
+ };
2473
+ }
2474
+ }
2475
+ if (body.type !== "messages") {
2426
2476
  return {
2427
- error: errorResponse(c, 400, "bad_request", "Request body requires a string or Message")
2477
+ error: errorResponse(
2478
+ c,
2479
+ 400,
2480
+ "bad_request",
2481
+ 'type must be "messages" or "interaction_response"'
2482
+ )
2428
2483
  };
2429
2484
  }
2430
- const request = {
2431
- message: typeof body.message === "string" ? body.message : body.message
2432
- };
2433
- if ("history" in body) {
2434
- if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
2435
- return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
2436
- }
2437
- request.history = body.history;
2438
- }
2439
- return request;
2440
- }
2441
- function parseUiRunRequestBody(c, body) {
2442
- const messages = body.messages;
2443
- if (!Array.isArray(messages) || !messages.every(isMessage)) {
2485
+ let messages;
2486
+ try {
2487
+ messages = parseMessages(body.messages);
2488
+ } catch {
2444
2489
  return {
2445
2490
  error: errorResponse(c, 400, "bad_request", "messages must be a non-empty Message array")
2446
2491
  };
@@ -2456,19 +2501,21 @@ function parseUiRunRequestBody(c, body) {
2456
2501
  error: errorResponse(c, 400, "bad_request", "messages must be a non-empty Message array")
2457
2502
  };
2458
2503
  }
2459
- const history = messages.slice(0, -1);
2460
- const request = { message };
2461
- if (history.length > 0) request.history = history;
2462
- return request;
2504
+ if (message.role !== "user") {
2505
+ return {
2506
+ error: errorResponse(c, 400, "bad_request", "The final message must be user-authored")
2507
+ };
2508
+ }
2509
+ return { type: "messages", messages };
2463
2510
  }
2464
2511
  function parseRunRequestOptions(c, body, request) {
2465
- if ("sessionId" in body) {
2512
+ if (request.type === "messages" && "sessionId" in body) {
2466
2513
  if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
2467
2514
  return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
2468
2515
  }
2469
- if (request.history !== void 0) {
2516
+ if (request.messages.length !== 1 || request.messages[0]?.role !== "user") {
2470
2517
  return {
2471
- error: errorResponse(c, 400, "bad_request", "sessionId cannot be combined with history")
2518
+ error: errorResponse(c, 400, "bad_request", "sessionId requires exactly one user message")
2472
2519
  };
2473
2520
  }
2474
2521
  request.sessionId = body.sessionId;
@@ -2479,7 +2526,7 @@ function parseRunRequestOptions(c, body, request) {
2479
2526
  }
2480
2527
  request.stream = body.stream;
2481
2528
  }
2482
- if ("maxTurns" in body) {
2529
+ if (request.type === "messages" && "maxTurns" in body) {
2483
2530
  if (!isNonNegativeInteger(body.maxTurns)) {
2484
2531
  return {
2485
2532
  error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
@@ -2487,7 +2534,7 @@ function parseRunRequestOptions(c, body, request) {
2487
2534
  }
2488
2535
  request.maxTurns = body.maxTurns;
2489
2536
  }
2490
- if ("toolConcurrency" in body) {
2537
+ if (request.type === "messages" && "toolConcurrency" in body) {
2491
2538
  if (!isPositiveInteger(body.toolConcurrency)) {
2492
2539
  return {
2493
2540
  error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
@@ -2495,13 +2542,11 @@ function parseRunRequestOptions(c, body, request) {
2495
2542
  }
2496
2543
  request.toolConcurrency = body.toolConcurrency;
2497
2544
  }
2498
- if ("model" in body) {
2499
- if (typeof body.model === "string") {
2500
- request.model = body.model;
2501
- } else if (isObject(body.model) && typeof body.model.provider === "string" && typeof body.model.model === "string") {
2545
+ if (request.type === "messages" && "model" in body) {
2546
+ if (isObject(body.model) && typeof body.model.providerId === "string" && typeof body.model.modelId === "string") {
2502
2547
  request.model = {
2503
- provider: body.model.provider,
2504
- model: body.model.model
2548
+ providerId: body.model.providerId,
2549
+ modelId: body.model.modelId
2505
2550
  };
2506
2551
  } else {
2507
2552
  return {
@@ -2509,7 +2554,7 @@ function parseRunRequestOptions(c, body, request) {
2509
2554
  c,
2510
2555
  400,
2511
2556
  "bad_request",
2512
- "model must be a provider:model string or { provider, model } object"
2557
+ "model must be a { providerId, modelId } object"
2513
2558
  )
2514
2559
  };
2515
2560
  }
@@ -2530,6 +2575,14 @@ function parseRunRequestOptions(c, body, request) {
2530
2575
  }
2531
2576
  return request;
2532
2577
  }
2578
+ function toolResultOutputText(output) {
2579
+ if (output.type === "text" || output.type === "error-text") return output.value;
2580
+ if (output.type === "json" || output.type === "error-json") return formatJson(output.value);
2581
+ if (output.type === "execution-denied") return output.reason ?? "Execution denied";
2582
+ return output.value.map(
2583
+ (part) => part.type === "text" ? part.text : `[file:${part.mediaType}${part.filename === void 0 ? "" : `:${part.filename}`}]`
2584
+ ).join("\n");
2585
+ }
2533
2586
 
2534
2587
  // src/runtime/session-logs.ts
2535
2588
  async function appendSessionLog(store, input) {
@@ -2664,6 +2717,25 @@ function memorySavedLog(props) {
2664
2717
  metadata
2665
2718
  };
2666
2719
  }
2720
+ function memoryCompactedLog(props) {
2721
+ const metadata = {
2722
+ originalMessageCount: props.originalMessageCount,
2723
+ compactedMessageCount: props.compactedMessageCount,
2724
+ retainedMessageCount: props.retainedMessageCount,
2725
+ attempts: props.attempts
2726
+ };
2727
+ const usage = usageSummary(props.usage);
2728
+ if (usage !== void 0) metadata.usage = usage;
2729
+ return {
2730
+ sessionId: props.sessionId,
2731
+ runId: props.runId,
2732
+ level: "info",
2733
+ category: "memory",
2734
+ event: "memory.compacted",
2735
+ message: "Session memory compacted",
2736
+ metadata
2737
+ };
2738
+ }
2667
2739
  function runFailedLog(sessionId, runId, error, startedAt) {
2668
2740
  return {
2669
2741
  sessionId,
@@ -2680,6 +2752,9 @@ function runFailedLog(sessionId, runId, error, startedAt) {
2680
2752
  }
2681
2753
  function logsFromStreamEvent(props) {
2682
2754
  const { event, sessionId, runId } = props;
2755
+ if (event.type === "memory_compaction") {
2756
+ return [memoryCompactedLog({ sessionId, runId, ...event })];
2757
+ }
2683
2758
  if (event.type === "turn_start") {
2684
2759
  return [
2685
2760
  {
@@ -2705,12 +2780,12 @@ function logsFromStreamEvent(props) {
2705
2780
  level: "info",
2706
2781
  category: "tool",
2707
2782
  event: "tool.called",
2708
- message: `Tool ${event.toolCall.function.name} called`,
2783
+ message: `Tool ${event.toolCall.toolName} called`,
2709
2784
  metadata: {
2710
2785
  turn: event.turn,
2711
- toolName: event.toolCall.function.name,
2712
- callId: event.toolCall.callId ?? event.toolCall.id,
2713
- argumentBytes: byteLength2(formatUnknown(event.toolCall.function.arguments))
2786
+ toolName: event.toolCall.toolName,
2787
+ callId: event.toolCall.callId ?? event.toolCall.toolCallId,
2788
+ argumentBytes: byteLength2(formatUnknown(event.toolCall.input))
2714
2789
  }
2715
2790
  }
2716
2791
  ];
@@ -2759,102 +2834,79 @@ function logsFromStreamEvent(props) {
2759
2834
  ];
2760
2835
  }
2761
2836
  if (event.type === "final") {
2762
- return [
2763
- runCompletedLog({
2764
- sessionId,
2765
- runId,
2766
- durationMs: Date.now() - props.startedAt,
2767
- usage: event.usage,
2768
- output: event.output,
2769
- messageCount: event.messages.length
2770
- }),
2771
- memorySavedLog({ sessionId, runId, messageCount: event.messages.length })
2772
- ];
2773
- }
2774
- if (event.type === "error") {
2775
- return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
2776
- }
2777
- if (event.type === "tool_approval_request") {
2778
- const metadata = {
2779
- approvalId: event.approval.id,
2780
- toolName: event.approval.toolName,
2781
- status: event.approval.status,
2782
- hasReason: event.approval.reason !== void 0,
2783
- argumentBytes: byteLength2(event.approval.args)
2784
- };
2785
- if (event.approval.callId !== void 0) metadata.callId = event.approval.callId;
2786
- return [
2787
- {
2837
+ const result = event.result;
2838
+ const logs = [];
2839
+ if (result.status === "suspended") {
2840
+ logs.push({
2788
2841
  sessionId,
2789
2842
  runId,
2790
2843
  level: "info",
2791
- category: "approval",
2792
- event: "approval.requested",
2793
- message: `Approval requested for ${event.approval.toolName}`,
2794
- metadata
2795
- }
2796
- ];
2797
- }
2798
- if (event.type === "tool_approval_result") {
2799
- const metadata = {
2800
- approvalId: event.approval.id,
2801
- toolName: event.approval.toolName,
2802
- status: event.approval.status,
2803
- hasReason: event.approval.reason !== void 0
2804
- };
2805
- if (event.approval.callId !== void 0) metadata.callId = event.approval.callId;
2806
- return [
2807
- {
2808
- sessionId,
2809
- runId,
2810
- level: event.approval.status === "approved" ? "info" : "warn",
2811
- category: "approval",
2812
- event: "approval.resolved",
2813
- message: `Approval ${event.approval.status} for ${event.approval.toolName}`,
2814
- metadata
2815
- }
2816
- ];
2817
- }
2818
- if (event.type === "tool_question_request") {
2819
- const metadata = {
2820
- questionId: event.question.id,
2821
- toolName: event.question.toolName,
2822
- status: event.question.status,
2823
- questionCount: event.question.questions.length,
2824
- argumentBytes: byteLength2(event.question.args)
2825
- };
2826
- if (event.question.callId !== void 0) metadata.callId = event.question.callId;
2827
- return [
2828
- {
2844
+ category: "run",
2845
+ event: "run.suspended",
2846
+ message: `Run suspended for ${result.interaction.type}`,
2847
+ metadata: {
2848
+ interactionId: result.interaction.id,
2849
+ interactionType: result.interaction.type,
2850
+ toolName: result.interaction.toolName,
2851
+ sourceRunId: result.continuation.sourceRunId
2852
+ }
2853
+ });
2854
+ } else {
2855
+ logs.push(
2856
+ runCompletedLog({
2857
+ sessionId,
2858
+ runId,
2859
+ durationMs: Date.now() - props.startedAt,
2860
+ usage: result.usage,
2861
+ output: result.text,
2862
+ messageCount: result.messages.length
2863
+ })
2864
+ );
2865
+ }
2866
+ if (result.resumedFrom !== void 0) {
2867
+ logs.push({
2829
2868
  sessionId,
2830
2869
  runId,
2831
2870
  level: "info",
2832
- category: "question",
2833
- event: "question.requested",
2834
- message: `Question requested by ${event.question.toolName}`,
2835
- metadata
2836
- }
2837
- ];
2871
+ category: "run",
2872
+ event: "interaction.resumed",
2873
+ message: "Interaction resumed in a linked run",
2874
+ metadata: {
2875
+ interactionId: result.resumedFrom.interactionId,
2876
+ sourceRunId: result.resumedFrom.runId,
2877
+ resumedRunId: result.runId
2878
+ }
2879
+ });
2880
+ }
2881
+ logs.push(memorySavedLog({ sessionId, runId, messageCount: result.messages.length }));
2882
+ return logs;
2838
2883
  }
2839
- if (event.type === "tool_question_result") {
2840
- const cancelled = event.question.status === "cancelled";
2884
+ if (event.type === "error") {
2885
+ return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
2886
+ }
2887
+ if (event.type === "interaction_response") {
2888
+ const response = event.response;
2841
2889
  const metadata = {
2842
- questionId: event.question.id,
2843
- toolName: event.question.toolName,
2844
- status: event.question.status,
2845
- questionCount: event.question.questions.length,
2846
- argumentBytes: byteLength2(event.question.args),
2847
- answerCount: event.question.answers?.length ?? 0
2890
+ interactionId: response.interactionId,
2891
+ interactionType: response.type === "tool-approval-response" ? "tool-approval" : "tool-question",
2892
+ toolName: response.toolName,
2893
+ sourceRunId: event.sourceRunId
2848
2894
  };
2849
- if (event.question.callId !== void 0) metadata.callId = event.question.callId;
2895
+ if (response.callId !== void 0) metadata.callId = response.callId;
2896
+ if (response.type === "tool-approval-response") {
2897
+ metadata.approved = response.approved;
2898
+ metadata.hasReason = response.reason !== void 0;
2899
+ } else {
2900
+ metadata.answerCount = response.answers.length;
2901
+ }
2850
2902
  return [
2851
2903
  {
2852
2904
  sessionId,
2853
2905
  runId,
2854
2906
  level: "info",
2855
- category: "question",
2856
- event: cancelled ? "question.cancelled" : "question.answered",
2857
- message: cancelled ? `Question cancelled for ${event.question.toolName}` : `Question answered for ${event.question.toolName}`,
2907
+ category: "run",
2908
+ event: "interaction.responded",
2909
+ message: `Interaction response received for ${response.toolName}`,
2858
2910
  metadata
2859
2911
  }
2860
2912
  ];
@@ -2880,16 +2932,16 @@ function childAgentLog(event, sessionId, runId) {
2880
2932
  level: "debug",
2881
2933
  category: "tool",
2882
2934
  event: "child_tool.called",
2883
- message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.function.name}`,
2935
+ message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.toolName}`,
2884
2936
  metadata: {
2885
2937
  parentToolName: event.toolName,
2886
2938
  agentId: event.agentId,
2887
2939
  hasAgentName: event.agentName !== void 0,
2888
2940
  turn: event.turn,
2889
2941
  childTurn: child.turn,
2890
- toolName: child.toolCall.function.name,
2891
- callId: child.toolCall.callId ?? child.toolCall.id,
2892
- argumentBytes: byteLength2(formatUnknown(child.toolCall.function.arguments))
2942
+ toolName: child.toolCall.toolName,
2943
+ callId: child.toolCall.callId ?? child.toolCall.toolCallId,
2944
+ argumentBytes: byteLength2(formatUnknown(child.toolCall.input))
2893
2945
  }
2894
2946
  }
2895
2947
  ];
@@ -2940,14 +2992,16 @@ function childAgentLog(event, sessionId, runId) {
2940
2992
  ];
2941
2993
  }
2942
2994
  if (child.type === "final") {
2995
+ const result = child.result;
2943
2996
  const metadata = {
2944
2997
  parentToolName: event.toolName,
2945
2998
  agentId: event.agentId,
2946
2999
  hasAgentName: event.agentName !== void 0,
2947
- outputBytes: byteLength2(child.output),
2948
- messageCount: child.messages.length
3000
+ outputBytes: byteLength2(result.text),
3001
+ messageCount: result.messages.length,
3002
+ status: result.status
2949
3003
  };
2950
- const usage = usageSummary(child.usage);
3004
+ const usage = usageSummary(result.usage);
2951
3005
  if (usage !== void 0) metadata.usage = usage;
2952
3006
  return [
2953
3007
  {
@@ -3047,6 +3101,56 @@ async function prepareAgentRun(c, props) {
3047
3101
  if ("error" in body) {
3048
3102
  return body.error;
3049
3103
  }
3104
+ if (body.type === "interaction_response") {
3105
+ const registration = props.continuationRegistry.take(
3106
+ body.interactionId,
3107
+ agentId,
3108
+ body.response
3109
+ );
3110
+ if (registration.status === "missing") {
3111
+ return errorResponse(c, 404, "not_found", "Interaction continuation is unavailable");
3112
+ }
3113
+ if (registration.status === "claimed") {
3114
+ return errorResponse(c, 409, "conflict", "Interaction response was already claimed");
3115
+ }
3116
+ if (registration.status === "invalid") {
3117
+ return errorResponse(c, 400, "bad_request", registration.error.message);
3118
+ }
3119
+ const { registration: claimed } = registration;
3120
+ const source = claimed.context;
3121
+ const runId2 = globalThis.crypto.randomUUID();
3122
+ let resumedBody = {
3123
+ ...source.body
3124
+ };
3125
+ if (body.stream !== void 0) resumedBody = { ...resumedBody, stream: body.stream };
3126
+ if (body.metadata !== void 0) resumedBody = { ...resumedBody, metadata: body.metadata };
3127
+ if (body.trace !== void 0) resumedBody = { ...resumedBody, trace: body.trace };
3128
+ const runOptions = createRunOptions(resumedBody, agentId, source.session, c.req.raw.signal);
3129
+ const execution2 = {
3130
+ generate: (options) => source.runAgent.generate({
3131
+ continuation: claimed.continuation,
3132
+ response: body.response,
3133
+ ...options
3134
+ }),
3135
+ stream: (options) => source.runAgent.stream({
3136
+ continuation: claimed.continuation,
3137
+ response: body.response,
3138
+ ...options
3139
+ })
3140
+ };
3141
+ return {
3142
+ ...source,
3143
+ body: resumedBody,
3144
+ execution: execution2,
3145
+ failureMessages: void 0,
3146
+ memoryCompactionLogged: false,
3147
+ options: runOptions,
3148
+ runId: runId2,
3149
+ runStartedAt: Date.now(),
3150
+ generatedMessagesStartIndex: 0,
3151
+ shouldPersistSessionMessages: source.shouldPersistSessionMessages
3152
+ };
3153
+ }
3050
3154
  const session = await resolveRunSession(c, body, agentId, props.stores);
3051
3155
  if (session instanceof Response) {
3052
3156
  return session;
@@ -3073,18 +3177,18 @@ async function prepareAgentRun(c, props) {
3073
3177
  store: props.stores.sessions
3074
3178
  });
3075
3179
  const memoryMetadata = runMemoryMetadata(agentId, body, selectedModel, runId);
3076
- const promptMessage = normalizePromptMessage(body.message);
3180
+ const promptMessage = body.messages.at(-1);
3077
3181
  const sessionStore = props.stores.sessions;
3078
3182
  const shouldPersistSessionMessages = session !== void 0 && sessionStore !== void 0 && !usesStoreAsAgentMemory(runAgent, sessionStore);
3079
3183
  if (shouldPersistSessionMessages) {
3080
3184
  await sessionStore.append({
3081
- context: { sessionId: session.id, metadata: memoryMetadata },
3185
+ scope: { sessionId: session.id, metadata: memoryMetadata },
3082
3186
  runId,
3083
3187
  turn: 1,
3084
3188
  messages: [promptMessage]
3085
3189
  });
3086
3190
  }
3087
- const request = createRunRequest({
3191
+ const execution = createRunExecution({
3088
3192
  agentId,
3089
3193
  body,
3090
3194
  memoryMetadata,
@@ -3097,10 +3201,14 @@ async function prepareAgentRun(c, props) {
3097
3201
  agent,
3098
3202
  body,
3099
3203
  memoryMetadata,
3100
- request,
3204
+ execution,
3205
+ failureMessages: void 0,
3206
+ memoryCompactionLogged: false,
3207
+ options: createRunOptions(body, agentId, session, c.req.raw.signal),
3101
3208
  runAgent,
3102
3209
  runId,
3103
3210
  runStartedAt,
3211
+ generatedMessagesStartIndex: 1,
3104
3212
  selectedModel,
3105
3213
  session,
3106
3214
  sessionStore,
@@ -3142,7 +3250,7 @@ async function recordRunReceived(props) {
3142
3250
  sessionId: props.session.id,
3143
3251
  runId: props.runId,
3144
3252
  agentId: props.agentId,
3145
- message: props.body.message,
3253
+ message: props.body.messages.at(-1),
3146
3254
  stream: props.body.stream === true,
3147
3255
  hasTrace: props.body.trace !== void 0
3148
3256
  };
@@ -3189,49 +3297,48 @@ function runMemoryMetadata(agentId, body, selectedModel, runId) {
3189
3297
  metadata.studioRunId = runId;
3190
3298
  return metadata;
3191
3299
  }
3192
- function createRunRequest(props) {
3193
- const request = props.session !== void 0 ? props.runAgent.memory === void 0 ? props.runAgent.prompt([...props.session.messages, props.promptMessage]) : props.runAgent.session(props.session.id, { metadata: props.memoryMetadata }).prompt(props.body.message) : props.runAgent.prompt(
3194
- props.body.history !== void 0 ? [...props.body.history, props.promptMessage] : props.body.message
3195
- );
3196
- request.withCompletionRetries();
3197
- if (props.body.maxTurns !== void 0) {
3198
- request.maxTurns(props.body.maxTurns);
3300
+ function createRunExecution(props) {
3301
+ const session = props.session;
3302
+ if (session !== void 0 && props.runAgent.memory !== void 0) {
3303
+ return {
3304
+ generate: (options) => props.runAgent.generate({
3305
+ prompt: props.promptMessage,
3306
+ session: { sessionId: session.id, metadata: props.memoryMetadata },
3307
+ ...options
3308
+ }),
3309
+ stream: (options) => props.runAgent.stream({
3310
+ prompt: props.promptMessage,
3311
+ session: { sessionId: session.id, metadata: props.memoryMetadata },
3312
+ ...options
3313
+ })
3314
+ };
3199
3315
  }
3200
- if (props.body.toolConcurrency !== void 0) {
3201
- request.withToolConcurrency(props.body.toolConcurrency);
3316
+ const transcript = session !== void 0 ? [...session.messages, props.promptMessage] : props.body.messages;
3317
+ return {
3318
+ generate: (options) => props.runAgent.generate({ messages: transcript, ...options }),
3319
+ stream: (options) => props.runAgent.stream({ messages: transcript, ...options })
3320
+ };
3321
+ }
3322
+ function createRunOptions(body, agentId, session, abortSignal) {
3323
+ const options = { abortSignal };
3324
+ if (body.type === "messages" && body.maxTurns !== void 0) options.maxTurns = body.maxTurns;
3325
+ if (body.type === "messages" && body.toolConcurrency !== void 0) {
3326
+ options.toolConcurrency = body.toolConcurrency;
3202
3327
  }
3203
- if (props.body.trace !== void 0) {
3204
- request.withTrace(traceForRun(props.body.trace, props.agentId, props.session));
3205
- } else if (props.session !== void 0) {
3206
- request.withTrace(traceForRun(void 0, props.agentId, props.session));
3328
+ if (body.trace !== void 0) {
3329
+ options.trace = traceForRun(body.trace, agentId, session);
3330
+ } else if (session !== void 0) {
3331
+ options.trace = traceForRun(void 0, agentId, session);
3207
3332
  }
3208
- return request;
3333
+ return options;
3209
3334
  }
3210
3335
  function handleStreamingAgentRun(c, run, props) {
3211
- const runtimeEvents = new AsyncEventQueue();
3212
- const approvalContext = {
3213
- runId: run.runId,
3214
- agentId: run.agentId,
3215
- emit: (event) => runtimeEvents.push(event)
3216
- };
3217
- if (run.session !== void 0) approvalContext.sessionId = run.session.id;
3218
- if (run.body.metadata !== void 0) approvalContext.metadata = run.body.metadata;
3219
- run.request.approvals(props.approvalRuntime.createApprovals(approvalContext));
3220
- const questionContext = {
3221
- runId: run.runId,
3222
- agentId: run.agentId,
3223
- emit: (event) => runtimeEvents.push(event)
3224
- };
3225
- if (run.session !== void 0) questionContext.sessionId = run.session.id;
3226
- if (run.body.metadata !== void 0) questionContext.metadata = run.body.metadata;
3227
- const effectiveHook = composeHooks(
3228
- run.runAgent.hook,
3229
- props.questionRuntime.createHook(questionContext)
3336
+ const streamOptions = withInternalAgentRunOptions({ ...run.options }, { runId: run.runId });
3337
+ const runStream = registerStreamingContinuation(
3338
+ run.execution.stream(streamOptions),
3339
+ props.continuationRegistry,
3340
+ run
3230
3341
  );
3231
- if (effectiveHook !== void 0) {
3232
- run.request.withHook(effectiveHook);
3233
- }
3234
- const runStream = mergeRunAndApprovalEvents(run.request.stream(), runtimeEvents);
3235
3342
  let stream = runStream;
3236
3343
  let persistedRun;
3237
3344
  if (run.session !== void 0 && run.sessionStore !== void 0) {
@@ -3245,17 +3352,17 @@ function handleStreamingAgentRun(c, run, props) {
3245
3352
  }),
3246
3353
  store: run.sessionStore,
3247
3354
  session: run.session,
3248
- message: run.body.message,
3355
+ message: run.body.messages.at(-1),
3249
3356
  runId: run.runId,
3250
3357
  startedAt: run.runStartedAt,
3251
- persistGeneratedMessages: run.shouldPersistSessionMessages
3358
+ persistGeneratedMessages: run.shouldPersistSessionMessages,
3359
+ generatedMessagesStartIndex: run.generatedMessagesStartIndex
3252
3360
  });
3253
3361
  stream = persistedRun.events;
3254
3362
  }
3255
3363
  return streamAgentRunEvents(c, stream, {
3364
+ runId: run.runId,
3256
3365
  onCancel: async () => {
3257
- props.approvalRuntime.cancelRun(run.runId);
3258
- props.questionRuntime.cancelRun(run.runId);
3259
3366
  const persistence = [];
3260
3367
  if (persistedRun !== void 0) {
3261
3368
  persistence.push(persistedRun.cancel());
@@ -3274,39 +3381,57 @@ function handleStreamingAgentRun(c, run, props) {
3274
3381
  }
3275
3382
  async function handleBufferedAgentRun(c, run, props) {
3276
3383
  try {
3277
- await startBufferedSessionRun(run, props);
3278
- const response = await run.request.send();
3279
- await completeBufferedSessionRun(run, response);
3280
- return c.json(response);
3384
+ const runtimeOptions = await startBufferedSessionRun(run);
3385
+ const result = await run.execution.generate(runtimeOptions);
3386
+ if (result.status === "suspended") {
3387
+ props.continuationRegistry.register({ continuation: result.continuation, context: run });
3388
+ }
3389
+ await completeBufferedSessionRun(run, result);
3390
+ return c.json(agentRunResponse(result));
3281
3391
  } catch (error) {
3282
3392
  await failBufferedSessionRun(run, error);
3283
3393
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError(error));
3284
3394
  }
3285
3395
  }
3286
- async function startBufferedSessionRun(run, props) {
3396
+ function agentRunResponse(result) {
3397
+ if (result.status !== "suspended") return result;
3398
+ const { continuation: _continuation, messages: _messages, ...response } = result;
3399
+ return response;
3400
+ }
3401
+ async function startBufferedSessionRun(run) {
3287
3402
  if (run.session !== void 0) {
3288
3403
  await appendSessionLog(run.sessionStore, runStartedLog(run.session, run.runId));
3289
3404
  await appendSessionLog(run.sessionStore, memoryLoadedLog(run.session, run.runId));
3290
3405
  }
3291
- const questionContext = {
3292
- runId: run.runId,
3293
- agentId: run.agentId
3294
- };
3295
- if (run.session !== void 0) questionContext.sessionId = run.session.id;
3296
- if (run.body.metadata !== void 0) questionContext.metadata = run.body.metadata;
3297
- const effectiveHook = composeHooks(
3298
- run.runAgent.hook,
3299
- props.questionRuntime.createHook(questionContext)
3300
- );
3301
- const approvalContext = {
3302
- runId: run.runId,
3303
- agentId: run.agentId
3406
+ const internalOptions = {
3407
+ onFailure: ({ messages }) => {
3408
+ run.failureMessages = structuredClone(messages);
3409
+ },
3410
+ runId: run.runId
3304
3411
  };
3305
- if (run.session !== void 0) approvalContext.sessionId = run.session.id;
3306
- if (run.body.metadata !== void 0) approvalContext.metadata = run.body.metadata;
3307
- run.request.approvals(props.approvalRuntime.createApprovals(approvalContext));
3308
- if (effectiveHook !== void 0) {
3309
- run.request.withHook(effectiveHook);
3412
+ if (run.session !== void 0 && run.sessionStore !== void 0) {
3413
+ const sessionId = run.session.id;
3414
+ const sessionStore = run.sessionStore;
3415
+ internalOptions.onMemoryCompaction = async (compaction) => {
3416
+ await appendSessionLog(
3417
+ sessionStore,
3418
+ memoryCompactedLog({
3419
+ sessionId,
3420
+ runId: run.runId,
3421
+ ...compaction
3422
+ })
3423
+ );
3424
+ run.memoryCompactionLogged = true;
3425
+ };
3426
+ }
3427
+ return withInternalAgentRunOptions({ ...run.options }, internalOptions);
3428
+ }
3429
+ async function* registerStreamingContinuation(stream, registry, run) {
3430
+ for await (const event of stream) {
3431
+ if (event.type === "final" && event.result.status === "suspended") {
3432
+ registry.register({ continuation: event.result.continuation, context: run });
3433
+ }
3434
+ yield event;
3310
3435
  }
3311
3436
  }
3312
3437
  async function completeBufferedSessionRun(run, response) {
@@ -3314,10 +3439,10 @@ async function completeBufferedSessionRun(run, response) {
3314
3439
  return;
3315
3440
  }
3316
3441
  if (run.shouldPersistSessionMessages) {
3317
- const generatedMessages = response.messages.slice(1);
3442
+ const generatedMessages = response.messages.slice(run.generatedMessagesStartIndex);
3318
3443
  if (generatedMessages.length > 0) {
3319
3444
  await run.sessionStore.append({
3320
- context: { sessionId: run.session.id, metadata: run.memoryMetadata },
3445
+ scope: { sessionId: run.session.id, metadata: run.memoryMetadata },
3321
3446
  runId: run.runId,
3322
3447
  turn: 1,
3323
3448
  messages: generatedMessages
@@ -3330,21 +3455,85 @@ async function completeBufferedSessionRun(run, response) {
3330
3455
  await run.sessionStore.saveSessionRunTranscript({
3331
3456
  id: run.session.id,
3332
3457
  runId: run.runId,
3333
- ...optionalTitle(run.body.message),
3458
+ ...optionalTitle(run.body.messages.at(-1)),
3334
3459
  transcript,
3335
- status: "success"
3460
+ status: response.status === "suspended" ? "suspended" : "success"
3336
3461
  });
3462
+ if (response.memoryCompaction !== void 0 && !run.memoryCompactionLogged) {
3463
+ await appendSessionLog(
3464
+ run.sessionStore,
3465
+ memoryCompactedLog({
3466
+ sessionId: run.session.id,
3467
+ runId: run.runId,
3468
+ ...response.memoryCompaction
3469
+ })
3470
+ );
3471
+ run.memoryCompactionLogged = true;
3472
+ }
3337
3473
  await appendSessionLog(
3338
3474
  run.sessionStore,
3339
- runCompletedLog({
3475
+ response.status === "suspended" ? {
3476
+ sessionId: run.session.id,
3477
+ runId: run.runId,
3478
+ level: "info",
3479
+ category: "run",
3480
+ event: "run.suspended",
3481
+ message: `Run suspended for ${response.interaction.type}`,
3482
+ metadata: {
3483
+ interactionId: response.interaction.id,
3484
+ interactionType: response.interaction.type,
3485
+ toolName: response.interaction.toolName,
3486
+ sourceRunId: response.continuation.sourceRunId
3487
+ }
3488
+ } : runCompletedLog({
3340
3489
  sessionId: run.session.id,
3341
3490
  runId: run.runId,
3342
3491
  durationMs: durationMs2,
3343
3492
  usage: response.usage,
3344
- output: response.output,
3493
+ output: response.text,
3345
3494
  messageCount: response.messages.length
3346
3495
  })
3347
3496
  );
3497
+ if (response.resumedFrom !== void 0) {
3498
+ const responseMessage = response.messages[0];
3499
+ const responsePart = responseMessage?.role === "tool" ? responseMessage.content[0] : void 0;
3500
+ if (responsePart !== void 0 && responsePart.type !== "tool-result") {
3501
+ const metadata = {
3502
+ interactionId: responsePart.interactionId,
3503
+ interactionType: responsePart.type === "tool-approval-response" ? "tool-approval" : "tool-question",
3504
+ toolName: responsePart.toolName,
3505
+ sourceRunId: response.resumedFrom.runId
3506
+ };
3507
+ if (responsePart.type === "tool-approval-response") {
3508
+ metadata.approved = responsePart.approved;
3509
+ metadata.hasReason = responsePart.reason !== void 0;
3510
+ } else {
3511
+ metadata.answerCount = responsePart.answers.length;
3512
+ }
3513
+ await appendSessionLog(run.sessionStore, {
3514
+ sessionId: run.session.id,
3515
+ runId: run.runId,
3516
+ level: "info",
3517
+ category: "run",
3518
+ event: "interaction.responded",
3519
+ message: `Interaction response received for ${responsePart.toolName}`,
3520
+ metadata
3521
+ });
3522
+ }
3523
+ await appendSessionLog(run.sessionStore, {
3524
+ sessionId: run.session.id,
3525
+ runId: run.runId,
3526
+ level: "info",
3527
+ category: "run",
3528
+ event: "interaction.resumed",
3529
+ message: "Interaction resumed in a linked run",
3530
+ metadata: {
3531
+ interactionId: response.resumedFrom.interactionId,
3532
+ sourceRunId: response.resumedFrom.runId,
3533
+ resumedRunId: response.runId
3534
+ }
3535
+ });
3536
+ }
3348
3537
  await appendSessionLog(
3349
3538
  run.sessionStore,
3350
3539
  memorySavedLog({
@@ -3358,17 +3547,14 @@ async function failBufferedSessionRun(run, error) {
3358
3547
  if (run.session === void 0 || run.sessionStore === void 0) {
3359
3548
  return;
3360
3549
  }
3361
- const messages = await run.sessionStore.load({
3362
- sessionId: run.session.id,
3363
- metadata: run.memoryMetadata
3364
- });
3365
3550
  const durationMs2 = Date.now() - run.runStartedAt;
3366
- const transcript = transcriptFromMessages(messages.slice(run.session.messageCount));
3551
+ const messages = run.failureMessages ?? [run.body.messages.at(-1)];
3552
+ const transcript = transcriptFromMessages([...messages]);
3367
3553
  assignTranscriptRunDuration(transcript, durationMs2);
3368
3554
  await run.sessionStore.saveSessionRunTranscript({
3369
3555
  id: run.session.id,
3370
3556
  runId: run.runId,
3371
- ...optionalTitle(run.body.message),
3557
+ ...optionalTitle(run.body.messages.at(-1)),
3372
3558
  transcript,
3373
3559
  status: "error",
3374
3560
  error: serializeError(error)
@@ -3378,261 +3564,13 @@ async function failBufferedSessionRun(run, error) {
3378
3564
  runFailedLog(run.session.id, run.runId, error, run.runStartedAt)
3379
3565
  );
3380
3566
  }
3381
- function normalizePromptMessage(message) {
3382
- return typeof message === "string" ? Message.user(message) : message;
3383
- }
3384
3567
  function usesStoreAsAgentMemory(agent, store) {
3385
3568
  const memoryStore = agent.memory?.store;
3386
3569
  return memoryStore !== void 0 && rawObservedStore(memoryStore) === rawObservedStore(store);
3387
3570
  }
3388
3571
 
3389
- // src/runtime/query.ts
3390
- function optionalQueryString(value) {
3391
- const trimmed = value?.trim();
3392
- return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
3393
- }
3394
- function parseLimit(value, defaultMax = 50, max = 100) {
3395
- if (value === void 0 || value.trim().length === 0) {
3396
- return defaultMax;
3397
- }
3398
- const limit = Number(value);
3399
- if (!Number.isInteger(limit) || limit <= 0) {
3400
- return void 0;
3401
- }
3402
- return Math.min(limit, max);
3403
- }
3404
- function parseTraceStatus(value) {
3405
- const status = optionalQueryString(value);
3406
- if (status === void 0) {
3407
- return void 0;
3408
- }
3409
- return status === "running" || status === "success" || status === "error" ? status : false;
3410
- }
3411
- function parseAfter(value) {
3412
- if (value === void 0 || value.trim().length === 0) {
3413
- return void 0;
3414
- }
3415
- const after = Number(value);
3416
- if (!Number.isInteger(after) || after < 0) {
3417
- return false;
3418
- }
3419
- return after;
3420
- }
3421
-
3422
- // src/runtime/approvals.ts
3423
- function registerApprovalRoutes(app, approvals) {
3424
- app.get("/approvals", (c) => {
3425
- const status = parseApprovalStatus(c.req.query("status"));
3426
- if (status === false) {
3427
- return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
3428
- }
3429
- const options = {};
3430
- const runId = optionalQueryString(c.req.query("runId"));
3431
- const agentId = optionalQueryString(c.req.query("agentId"));
3432
- const sessionId = optionalQueryString(c.req.query("sessionId"));
3433
- if (status !== void 0) {
3434
- options.status = status;
3435
- }
3436
- if (runId !== void 0) {
3437
- options.runId = runId;
3438
- }
3439
- if (agentId !== void 0) {
3440
- options.agentId = agentId;
3441
- }
3442
- if (sessionId !== void 0) {
3443
- options.sessionId = sessionId;
3444
- }
3445
- return c.json({
3446
- approvals: approvals.list(options)
3447
- });
3448
- });
3449
- app.post("/approvals/:approvalId/decision", async (c) => {
3450
- const body = await parseApprovalDecisionRequest(c);
3451
- if ("error" in body) {
3452
- return body.error;
3453
- }
3454
- const result = approvals.decide(c.req.param("approvalId"), body);
3455
- if (result === "missing") {
3456
- return errorResponse(c, 404, "not_found", "Approval not found");
3457
- }
3458
- if (result === "resolved") {
3459
- return errorResponse(c, 409, "conflict", "Approval is already resolved");
3460
- }
3461
- return c.json(result);
3462
- });
3463
- }
3464
- function parseApprovalStatus(value) {
3465
- const status = optionalQueryString(value);
3466
- if (status === void 0) {
3467
- return void 0;
3468
- }
3469
- return status === "pending" || status === "resolved" ? status : false;
3470
- }
3471
- async function parseApprovalDecisionRequest(c) {
3472
- let body;
3473
- try {
3474
- body = await c.req.json();
3475
- } catch {
3476
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3477
- }
3478
- if (!isObject(body)) {
3479
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3480
- }
3481
- if (typeof body.approved !== "boolean") {
3482
- return { error: errorResponse(c, 400, "bad_request", "approved must be a boolean") };
3483
- }
3484
- if ("reason" in body && typeof body.reason !== "string") {
3485
- return { error: errorResponse(c, 400, "bad_request", "reason must be a string") };
3486
- }
3487
- const reason = typeof body.reason === "string" && body.reason.trim().length > 0 ? body.reason.trim() : void 0;
3488
- return approvalDecision(body.approved, reason);
3489
- }
3490
- function createApprovalRuntime() {
3491
- const approvals = /* @__PURE__ */ new Map();
3492
- return {
3493
- approvals,
3494
- createApprovals(context) {
3495
- return {
3496
- async handler(request) {
3497
- const approvalRequest = {
3498
- toolName: request.toolName,
3499
- internalCallId: request.internalCallId,
3500
- args: request.rawArgs
3501
- };
3502
- if (request.toolCallId !== void 0) approvalRequest.toolCallId = request.toolCallId;
3503
- if (request.reason !== void 0) approvalRequest.reason = request.reason;
3504
- if (request.rejectMessage !== void 0) {
3505
- approvalRequest.rejectMessage = request.rejectMessage;
3506
- }
3507
- const decision = await requestApproval(approvals, context, approvalRequest);
3508
- return approvalDecision(decision.approved, decision.reason);
3509
- }
3510
- };
3511
- },
3512
- list(options) {
3513
- return [...approvals.values()].filter((approval) => {
3514
- if (options.status === "pending" && approval.status !== "pending") {
3515
- return false;
3516
- }
3517
- if (options.status === "resolved" && approval.status === "pending") {
3518
- return false;
3519
- }
3520
- if (options.runId !== void 0 && approval.runId !== options.runId) {
3521
- return false;
3522
- }
3523
- if (options.agentId !== void 0 && approval.agentId !== options.agentId) {
3524
- return false;
3525
- }
3526
- if (options.sessionId !== void 0 && approval.sessionId !== options.sessionId) {
3527
- return false;
3528
- }
3529
- return true;
3530
- }).map(publicApproval);
3531
- },
3532
- cancelRun(runId) {
3533
- const cancelled = [];
3534
- for (const approval of approvals.values()) {
3535
- if (!isPendingApproval(approval) || approval.runId !== runId) {
3536
- continue;
3537
- }
3538
- const resolved = resolveApproval(approval, "cancelled", {
3539
- reason: "Run cancelled in Anvia Studio."
3540
- });
3541
- approvals.set(approval.id, resolved);
3542
- approval.emit?.({ type: "tool_approval_result", approval: resolved });
3543
- approval.resolve(approvalDecision(false, resolved.reason));
3544
- cancelled.push(resolved);
3545
- }
3546
- return cancelled;
3547
- },
3548
- decide(id, decision) {
3549
- const approval = approvals.get(id);
3550
- if (approval === void 0) {
3551
- return "missing";
3552
- }
3553
- if (!isPendingApproval(approval)) {
3554
- return "resolved";
3555
- }
3556
- const reason = decision.approved ? decision.reason : decision.reason ?? approval.rejectMessage ?? "Rejected in Anvia Studio.";
3557
- const resolved = resolveApproval(
3558
- approval,
3559
- decision.approved ? "approved" : "rejected",
3560
- reason === void 0 ? {} : { reason }
3561
- );
3562
- approvals.set(id, resolved);
3563
- approval.emit?.({ type: "tool_approval_result", approval: resolved });
3564
- approval.resolve(approvalDecision(decision.approved, reason));
3565
- return publicApproval(resolved);
3566
- }
3567
- };
3568
- }
3569
- async function requestApproval(approvals, context, request) {
3570
- const id = globalThis.crypto.randomUUID();
3571
- const approval = {
3572
- id,
3573
- runId: context.runId,
3574
- agentId: context.agentId,
3575
- toolName: request.toolName,
3576
- internalCallId: request.internalCallId,
3577
- args: request.args,
3578
- status: "pending",
3579
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3580
- resolve: () => {
3581
- }
3582
- };
3583
- if (context.sessionId !== void 0) approval.sessionId = context.sessionId;
3584
- if (request.toolCallId !== void 0) approval.callId = request.toolCallId;
3585
- if (request.reason !== void 0) approval.reason = request.reason;
3586
- if (request.rejectMessage !== void 0) approval.rejectMessage = request.rejectMessage;
3587
- if (context.emit !== void 0) approval.emit = context.emit;
3588
- const decision = new Promise((resolve2) => {
3589
- approval.resolve = (decision2) => {
3590
- const current = approvals.get(id);
3591
- if (!isPendingApproval(current)) {
3592
- if (current !== void 0) {
3593
- resolve2(approvalDecision(current.status === "approved", current.reason));
3594
- }
3595
- return;
3596
- }
3597
- const reason = decision2.approved ? decision2.reason : decision2.reason ?? request.rejectMessage ?? "Rejected in Anvia Studio.";
3598
- const resolved = resolveApproval(
3599
- current,
3600
- decision2.approved ? "approved" : "rejected",
3601
- reason === void 0 ? {} : { reason }
3602
- );
3603
- approvals.set(id, resolved);
3604
- context.emit?.({ type: "tool_approval_result", approval: resolved });
3605
- resolve2(approvalDecision(decision2.approved, reason));
3606
- };
3607
- });
3608
- approvals.set(id, approval);
3609
- context.emit?.({ type: "tool_approval_request", approval: publicApproval(approval) });
3610
- return decision;
3611
- }
3612
- function isPendingApproval(approval) {
3613
- return approval !== void 0 && approval.status === "pending" && "resolve" in approval;
3614
- }
3615
- function resolveApproval(approval, status, options = {}) {
3616
- const resolved = {
3617
- ...approval,
3618
- status,
3619
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
3620
- };
3621
- if (options.reason !== void 0) resolved.reason = options.reason;
3622
- return publicApproval(resolved);
3623
- }
3624
- function approvalDecision(approved, reason) {
3625
- const decision = { approved };
3626
- if (reason !== void 0) decision.reason = reason;
3627
- return decision;
3628
- }
3629
- function publicApproval(approval) {
3630
- const { emit, rejectMessage, resolve: resolve2, ...rest } = approval;
3631
- void emit;
3632
- void rejectMessage;
3633
- void resolve2;
3634
- return rest;
3635
- }
3572
+ // src/runtime/config.ts
3573
+ import { isQuestionTool } from "@anvia/core/tool";
3636
3574
 
3637
3575
  // src/runtime/eval-config.ts
3638
3576
  var CASE_PREVIEW_LIMIT = 5;
@@ -3712,239 +3650,40 @@ function isRecord2(value) {
3712
3650
  return value !== null && typeof value === "object" && !Array.isArray(value);
3713
3651
  }
3714
3652
 
3715
- // src/runtime/tool-metadata.ts
3716
- import { ToolSet } from "@anvia/core/tool";
3717
- var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
3718
- function agentToolItems(agent) {
3719
- return [
3720
- ...agent.agent.toolSet.values().map((tool) => ({ tool, source: "static" })),
3721
- ...agent.agent.dynamicTools.flatMap((registration) => {
3722
- const maybeToolSet = registration.index.toolSet;
3723
- if (!(maybeToolSet instanceof ToolSet)) {
3724
- return [];
3725
- }
3726
- return maybeToolSet.values().map((tool) => ({ tool, source: "dynamic" }));
3727
- })
3728
- ];
3729
- }
3730
- function approvalMetadata(tool) {
3731
- const approval = tool.approval;
3732
- if (approval === void 0 || typeof approval !== "object" || approval === null) {
3733
- return { required: false };
3734
- }
3735
- const policy = approval;
3736
- const metadata = { required: true };
3737
- if (typeof policy.reason === "string") metadata.reason = policy.reason;
3738
- if (typeof policy.rejectMessage === "string") metadata.rejectMessage = policy.rejectMessage;
3739
- return metadata;
3740
- }
3741
- function mcpServerName(tool) {
3742
- const metadata = tool[MCP_TOOL_METADATA_KEY];
3743
- if (typeof metadata !== "object" || metadata === null) {
3744
- return void 0;
3745
- }
3746
- const serverName = metadata.serverName;
3747
- return typeof serverName === "string" && serverName.length > 0 ? serverName : void 0;
3748
- }
3749
- function agentHasMcpTools(agent) {
3750
- return agentToolItems(agent).some(({ tool }) => mcpServerName(tool) !== void 0);
3751
- }
3752
-
3753
- // src/runtime/config.ts
3754
- function runnerId(options) {
3755
- return options.id ?? "anvia-studio";
3756
- }
3757
- function agentConfig(agent) {
3758
- const name = agent.name ?? agent.agent.name;
3759
- const description = agent.description ?? agent.agent.description;
3760
- const config = {
3761
- id: agent.id,
3762
- quickPrompts: agent.quickPrompts ?? []
3763
- };
3764
- if (name !== void 0) config.name = name;
3765
- if (description !== void 0) config.description = description;
3766
- if (agent.metadata !== void 0) config.metadata = agent.metadata;
3767
- return config;
3768
- }
3769
- function agentRuntimeSummary(agent) {
3770
- const tools = agentToolItems(agent);
3771
- const name = agent.name ?? agent.agent.name;
3772
- const description = agent.description ?? agent.agent.description;
3773
- const summary = {
3774
- id: agent.id,
3775
- model: toJsonValue2(agent.agent.model),
3776
- toolCount: tools.length,
3777
- staticToolCount: tools.filter((item) => item.source === "static").length,
3778
- dynamicToolCount: tools.filter((item) => item.source === "dynamic").length,
3779
- approvalToolCount: tools.filter((item) => item.tool.approval !== void 0).length,
3780
- mcpToolCount: tools.filter((item) => mcpServerName(item.tool) !== void 0).length,
3781
- staticContextCount: agent.agent.staticContext.length,
3782
- dynamicContextCount: agent.agent.dynamicContexts.length,
3783
- observerCount: agent.agent.observers.length,
3784
- hasMemory: agent.agent.memory !== void 0,
3785
- hasHook: agent.agent.hook !== void 0,
3786
- hasOutputSchema: agent.agent.outputSchema !== void 0
3787
- };
3788
- if (name !== void 0) summary.name = name;
3789
- if (description !== void 0) summary.description = description;
3790
- if (agent.agent.defaultMaxTurns !== void 0) {
3791
- summary.defaultMaxTurns = agent.agent.defaultMaxTurns;
3792
- }
3793
- if (agent.metadata !== void 0) summary.metadata = agent.metadata;
3794
- return summary;
3795
- }
3796
- function pipelineConfig(pipeline) {
3797
- const graph = pipeline.pipeline.graph();
3798
- const stageNodes = graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output");
3799
- const config = {
3800
- id: pipeline.id,
3801
- stageCount: stageNodes.length,
3802
- edgeCount: graph.edges.length,
3803
- hasParallelStages: graph.nodes.some((node) => node.kind === "parallel"),
3804
- agentCount: graph.nodes.filter((node) => node.kind === "agent").length,
3805
- extractorCount: graph.nodes.filter((node) => node.kind === "extractor").length
3806
- };
3807
- if (pipeline.name !== void 0) config.name = pipeline.name;
3808
- if (pipeline.description !== void 0) config.description = pipeline.description;
3809
- if (pipeline.metadata !== void 0) config.metadata = pipeline.metadata;
3810
- return config;
3811
- }
3812
- function buildConfig(options, agents, pipelines, stores, sandboxCount = 0) {
3813
- const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
3814
- const config = {
3815
- id: runnerId(options),
3816
- agents: agents.map(agentConfig),
3817
- pipelines: pipelines.map(pipelineConfig),
3818
- evals: options.evals.map(evalConfig),
3819
- chat: {
3820
- quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
3821
- },
3822
- capabilities: capabilityConfig(options, agents, pipelines, stores, sandboxCount),
3823
- unsupportedCapabilities: unsupportedCapabilities(stores)
3824
- };
3825
- if (options.name !== void 0) config.name = options.name;
3826
- if (options.description !== void 0) config.description = options.description;
3827
- if (options.version !== void 0) config.version = options.version;
3828
- if (models !== void 0) config.models = models;
3829
- return config;
3830
- }
3831
- function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0) {
3832
- const capabilities = {
3833
- agents: { enabled: true },
3834
- observability: { enabled: true },
3835
- status: { enabled: true }
3836
- };
3837
- if (stores.sessions !== void 0) {
3838
- capabilities.sessions = { enabled: true };
3839
- }
3840
- if (stores.sessions !== void 0 || agents.some((agent) => agent.agent.memory !== void 0)) {
3841
- capabilities.memory = { enabled: true };
3842
- }
3843
- if (stores.traces !== void 0) {
3844
- capabilities.traces = { enabled: true };
3845
- }
3846
- if (pipelines.length > 0) {
3847
- capabilities.pipelines = { enabled: true };
3848
- }
3849
- if (_options.evals.length > 0) {
3850
- capabilities.evals = { enabled: true };
3851
- }
3852
- if (agents.some(
3853
- (agent) => agent.agent.toolSet.values().length > 0 || agent.agent.dynamicTools.length > 0
3854
- )) {
3855
- capabilities.tools = { enabled: true };
3856
- }
3857
- if (agents.some(agentHasMcpTools)) {
3858
- capabilities.mcps = { enabled: true };
3859
- }
3860
- if (sandboxCount > 0) {
3861
- capabilities.sandboxes = { enabled: true };
3862
- }
3863
- if (agents.some(
3864
- (agent) => agent.agent.hook !== void 0 || agent.agent.toolSet.values().some((tool) => tool.approval)
3865
- )) {
3866
- capabilities.approvals = { enabled: true };
3867
- }
3868
- if (agents.some(
3869
- (agent) => agent.agent.staticContext.length > 0 || agent.agent.dynamicContexts.length > 0 || agent.agent.dynamicTools.length > 0
3870
- )) {
3871
- capabilities.knowledge = { enabled: true };
3872
- }
3873
- return capabilities;
3874
- }
3875
- function unsupportedCapabilities(stores) {
3876
- const capabilities = [];
3877
- if (stores.sessions === void 0) capabilities.push("sessions");
3878
- if (stores.traces === void 0) capabilities.push("traces");
3879
- return capabilities;
3880
- }
3881
- function toJsonValue2(value) {
3882
- return serializeUnknown(value);
3883
- }
3653
+ // src/runtime/knowledge.ts
3654
+ import { getAgentToolState } from "@anvia/core/internal/agent";
3884
3655
 
3885
- // src/runtime/evals.ts
3886
- import { runEvalSuite } from "@anvia/core/evals";
3887
- function registerEvalRoutes(app, props) {
3888
- app.get(
3889
- "/evals",
3890
- (c) => c.json({
3891
- evals: props.evals.map(evalConfig)
3892
- })
3893
- );
3894
- app.get("/evals/:evalId", (c) => {
3895
- const suite = props.evalMap.get(c.req.param("evalId"));
3896
- if (suite === void 0) {
3897
- return errorResponse(c, 404, "not_found", "Eval suite not found");
3898
- }
3899
- return c.json(evalConfig(suite));
3900
- });
3901
- app.post("/evals/:evalId/runs", async (c) => {
3902
- const suite = props.evalMap.get(c.req.param("evalId"));
3903
- if (suite === void 0) {
3904
- return errorResponse(c, 404, "not_found", "Eval suite not found");
3905
- }
3906
- const body = await parseEvalRunRequest(c);
3907
- if ("error" in body) {
3908
- return body.error;
3909
- }
3910
- const runId = globalThis.crypto.randomUUID();
3911
- const startedAt = Date.now();
3912
- const runOptions = { ...suite };
3913
- if (body.concurrency !== void 0) runOptions.concurrency = body.concurrency;
3914
- const result = await runEvalSuite(runOptions);
3915
- const endedAt = Date.now();
3916
- const jsonResult = toJsonValue(result);
3917
- const response = {
3918
- runId,
3919
- suiteId: suite.id ?? suite.name,
3920
- startedAt: new Date(startedAt).toISOString(),
3921
- endedAt: new Date(endedAt).toISOString(),
3922
- durationMs: endedAt - startedAt,
3923
- result: isJsonObject(jsonResult) ? jsonResult : { value: jsonResult }
3924
- };
3925
- return c.json(response);
3926
- });
3656
+ // src/runtime/query.ts
3657
+ function optionalQueryString(value) {
3658
+ const trimmed = value?.trim();
3659
+ return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
3927
3660
  }
3928
- async function parseEvalRunRequest(c) {
3929
- let body = {};
3930
- try {
3931
- body = await c.req.json();
3932
- } catch {
3933
- body = {};
3661
+ function parseLimit(value, defaultMax = 50, max = 100) {
3662
+ if (value === void 0 || value.trim().length === 0) {
3663
+ return defaultMax;
3934
3664
  }
3935
- if (!isObject(body)) {
3936
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3665
+ const limit = Number(value);
3666
+ if (!Number.isInteger(limit) || limit <= 0) {
3667
+ return void 0;
3937
3668
  }
3938
- const request = {};
3939
- if ("concurrency" in body) {
3940
- if (!isPositiveInteger(body.concurrency)) {
3941
- return {
3942
- error: errorResponse(c, 400, "bad_request", "concurrency must be a positive integer")
3943
- };
3944
- }
3945
- request.concurrency = body.concurrency;
3669
+ return Math.min(limit, max);
3670
+ }
3671
+ function parseTraceStatus(value) {
3672
+ const status = optionalQueryString(value);
3673
+ if (status === void 0) {
3674
+ return void 0;
3946
3675
  }
3947
- return request;
3676
+ return status === "running" || status === "success" || status === "suspended" || status === "error" ? status : false;
3677
+ }
3678
+ function parseAfter(value) {
3679
+ if (value === void 0 || value.trim().length === 0) {
3680
+ return void 0;
3681
+ }
3682
+ const after = Number(value);
3683
+ if (!Number.isInteger(after) || after < 0) {
3684
+ return false;
3685
+ }
3686
+ return after;
3948
3687
  }
3949
3688
 
3950
3689
  // src/runtime/knowledge.ts
@@ -3984,12 +3723,17 @@ function registerKnowledgeRoutes(app, props) {
3984
3723
  return c.json(page);
3985
3724
  });
3986
3725
  }
3726
+ function agentHasKnowledge(agent) {
3727
+ const toolIndexes = getAgentToolState(agent.agent).toolIndexes;
3728
+ return agent.agent.context.length > 0 || toolIndexes.length > 0;
3729
+ }
3987
3730
  async function agentKnowledgeConfig(agent) {
3988
3731
  const agentName = agent.name ?? agent.agent.name;
3732
+ const staticContext = staticContextDocuments(agent.agent);
3989
3733
  const config = {
3990
3734
  agentId: agent.id,
3991
3735
  sources: await knowledgeSources(agent),
3992
- staticContext: agent.agent.staticContext.map((document) => {
3736
+ staticContext: staticContext.map((document) => {
3993
3737
  const item = { id: document.id, text: document.text };
3994
3738
  if (document.additionalProps !== void 0) {
3995
3739
  item.additionalProps = jsonObjectFromRecord(document.additionalProps);
@@ -4001,51 +3745,54 @@ async function agentKnowledgeConfig(agent) {
4001
3745
  return config;
4002
3746
  }
4003
3747
  async function knowledgeSources(agent) {
3748
+ const toolIndexes = getAgentToolState(agent.agent).toolIndexes;
3749
+ const staticContext = staticContextDocuments(agent.agent);
3750
+ const indexedContext = vectorContexts(agent.agent);
4004
3751
  const sources = [
4005
3752
  {
4006
3753
  sourceId: staticSourceId(),
4007
3754
  kind: "static_context",
4008
3755
  label: "Static context",
4009
- count: agent.agent.staticContext.length,
3756
+ count: staticContext.length,
4010
3757
  inspectable: true,
4011
- itemCount: agent.agent.staticContext.length
3758
+ itemCount: staticContext.length
4012
3759
  }
4013
3760
  ];
4014
3761
  const dynamicContextSources = await Promise.all(
4015
- agent.agent.dynamicContexts.map(async (registration, index) => {
4016
- const inspect = inspectFn(registration.index);
4017
- const count = await inspectableCount(inspect, registration.options.filter);
3762
+ indexedContext.map(async (vectorContext, index) => {
3763
+ const inspect = inspectFn(vectorContext.store);
3764
+ const count = await inspectableCount(inspect, vectorContext.filter);
4018
3765
  const source = {
4019
3766
  sourceId: dynamicContextSourceId(index),
4020
3767
  kind: "dynamic_context",
4021
3768
  label: `Dynamic context ${index + 1}`,
4022
3769
  count: 1,
4023
3770
  registrationIndex: index,
4024
- topK: registration.options.topK,
3771
+ topK: vectorContext.topK,
4025
3772
  inspectable: inspect !== void 0
4026
3773
  };
4027
- if (registration.options.threshold !== void 0) {
4028
- source.threshold = registration.options.threshold;
3774
+ if (vectorContext.minScore !== void 0) {
3775
+ source.minScore = vectorContext.minScore;
4029
3776
  }
4030
3777
  if (count !== void 0) source.itemCount = count;
4031
3778
  return source;
4032
3779
  })
4033
3780
  );
4034
3781
  const dynamicToolSources = await Promise.all(
4035
- agent.agent.dynamicTools.map(async (registration, index) => {
4036
- const inspect = inspectFn(registration.index);
4037
- const count = await inspectableCount(inspect, registration.options.filter);
3782
+ toolIndexes.map(async (toolIndex, index) => {
3783
+ const inspect = inspectFn(toolIndex);
3784
+ const count = await inspectableCount(inspect, toolIndex.filter);
4038
3785
  const source = {
4039
3786
  sourceId: dynamicToolsSourceId(index),
4040
3787
  kind: "dynamic_tools",
4041
3788
  label: `Dynamic tools ${index + 1}`,
4042
3789
  count: 1,
4043
3790
  registrationIndex: index,
4044
- topK: registration.options.topK,
3791
+ topK: toolIndex.topK,
4045
3792
  inspectable: inspect !== void 0
4046
3793
  };
4047
- if (registration.options.threshold !== void 0) {
4048
- source.threshold = registration.options.threshold;
3794
+ if (toolIndex.minScore !== void 0) {
3795
+ source.minScore = toolIndex.minScore;
4049
3796
  }
4050
3797
  if (count !== void 0) source.itemCount = count;
4051
3798
  return source;
@@ -4064,20 +3811,20 @@ async function knowledgeItemsPage(agent, sourceId, request) {
4064
3811
  if (sourceId === staticSourceId()) {
4065
3812
  return staticKnowledgeItemsPage(agent, request);
4066
3813
  }
4067
- const dynamicContextIndex = dynamicSourceIndex(sourceId, "dynamic_context");
4068
- if (dynamicContextIndex !== void 0) {
4069
- const registration = agent.agent.dynamicContexts[dynamicContextIndex];
4070
- if (registration === void 0) {
3814
+ const vectorContextIndex = dynamicSourceIndex(sourceId, "dynamic_context");
3815
+ if (vectorContextIndex !== void 0) {
3816
+ const vectorContext = vectorContexts(agent.agent)[vectorContextIndex];
3817
+ if (vectorContext === void 0) {
4071
3818
  return void 0;
4072
3819
  }
4073
- const inspect = inspectFn(registration.index);
3820
+ const inspect = inspectFn(vectorContext.store);
4074
3821
  if (inspect === void 0) {
4075
3822
  return nonInspectablePage(agent.id, sourceId, "dynamic_context");
4076
3823
  }
4077
3824
  const page = await inspect({
4078
3825
  limit: request.limit,
4079
3826
  cursor: request.cursor,
4080
- filter: registration.options.filter
3827
+ filter: vectorContext.filter
4081
3828
  });
4082
3829
  const result = {
4083
3830
  agentId: agent.id,
@@ -4092,18 +3839,18 @@ async function knowledgeItemsPage(agent, sourceId, request) {
4092
3839
  }
4093
3840
  const dynamicToolsIndex = dynamicSourceIndex(sourceId, "dynamic_tools");
4094
3841
  if (dynamicToolsIndex !== void 0) {
4095
- const registration = agent.agent.dynamicTools[dynamicToolsIndex];
4096
- if (registration === void 0) {
3842
+ const toolIndex = getAgentToolState(agent.agent).toolIndexes[dynamicToolsIndex];
3843
+ if (toolIndex === void 0) {
4097
3844
  return void 0;
4098
3845
  }
4099
- const inspect = inspectFn(registration.index);
3846
+ const inspect = inspectFn(toolIndex);
4100
3847
  if (inspect === void 0) {
4101
3848
  return nonInspectablePage(agent.id, sourceId, "dynamic_tools");
4102
3849
  }
4103
3850
  const page = await inspect({
4104
3851
  limit: request.limit,
4105
3852
  cursor: request.cursor,
4106
- filter: registration.options.filter
3853
+ filter: toolIndex.filter
4107
3854
  });
4108
3855
  const result = {
4109
3856
  agentId: agent.id,
@@ -4118,16 +3865,17 @@ async function knowledgeItemsPage(agent, sourceId, request) {
4118
3865
  }
4119
3866
  return void 0;
4120
3867
  }
4121
- function inspectFn(index) {
4122
- if (!isRecord3(index) || typeof index.inspect !== "function") {
3868
+ function inspectFn(store) {
3869
+ if (!isRecord3(store) || typeof store.inspect !== "function") {
4123
3870
  return void 0;
4124
3871
  }
4125
- const inspect = index.inspect;
4126
- return (request) => inspect.call(index, request);
3872
+ const inspect = store.inspect;
3873
+ return (request) => inspect.call(store, request);
4127
3874
  }
4128
3875
  function staticKnowledgeItemsPage(agent, request) {
3876
+ const staticContext = staticContextDocuments(agent.agent);
4129
3877
  const start = Math.max(0, Math.trunc(Number(request.cursor ?? "0")));
4130
- const page = agent.agent.staticContext.slice(start, start + request.limit);
3878
+ const page = staticContext.slice(start, start + request.limit);
4131
3879
  const nextOffset = start + page.length;
4132
3880
  const result = {
4133
3881
  agentId: agent.id,
@@ -4145,9 +3893,9 @@ function staticKnowledgeItemsPage(agent, request) {
4145
3893
  }
4146
3894
  return item;
4147
3895
  }),
4148
- totalCount: agent.agent.staticContext.length
3896
+ totalCount: staticContext.length
4149
3897
  };
4150
- if (nextOffset < agent.agent.staticContext.length) result.nextCursor = String(nextOffset);
3898
+ if (nextOffset < staticContext.length) result.nextCursor = String(nextOffset);
4151
3899
  return result;
4152
3900
  }
4153
3901
  function nonInspectablePage(agentId, sourceId, kind) {
@@ -4336,6 +4084,283 @@ function isRecord3(value) {
4336
4084
  return typeof value === "object" && value !== null && !Array.isArray(value);
4337
4085
  }
4338
4086
 
4087
+ // src/runtime/tool-metadata.ts
4088
+ import { getAgentToolState as getAgentToolState2 } from "@anvia/core/internal/agent";
4089
+ import { isMcpTool } from "@anvia/core/mcp";
4090
+ function agentToolItems(agent) {
4091
+ const state = getAgentToolState2(agent.agent);
4092
+ const items = state.staticTools.map((tool) => ({ tool, source: "static" }));
4093
+ const names = new Set(items.map(({ tool }) => tool.name));
4094
+ for (const index of state.toolIndexes) {
4095
+ for (const tool of index.tools) {
4096
+ if (!names.has(tool.name)) {
4097
+ names.add(tool.name);
4098
+ items.push({ tool, source: "dynamic" });
4099
+ }
4100
+ }
4101
+ }
4102
+ return items;
4103
+ }
4104
+ function approvalMetadata(tool) {
4105
+ const approval = tool.requiresApproval;
4106
+ if (approval === void 0 || approval === false) {
4107
+ return { required: false };
4108
+ }
4109
+ if (typeof approval !== "object" || approval === null) {
4110
+ return { required: true };
4111
+ }
4112
+ const policy = approval;
4113
+ const metadata = { required: true };
4114
+ if (typeof policy.reason === "string") metadata.reason = policy.reason;
4115
+ return metadata;
4116
+ }
4117
+ function toolRequiresApproval(tool) {
4118
+ const approval = tool.requiresApproval;
4119
+ return approval !== void 0 && approval !== false;
4120
+ }
4121
+ function mcpServerName(tool) {
4122
+ return isMcpTool(tool) ? tool.mcp.serverName : void 0;
4123
+ }
4124
+ function agentHasMcpServers(agent) {
4125
+ return agent.agent.mcpServers.length > 0;
4126
+ }
4127
+
4128
+ // src/runtime/config.ts
4129
+ function runnerId(options) {
4130
+ return options.id ?? "anvia-studio";
4131
+ }
4132
+ function agentConfig(agent) {
4133
+ const name = agent.name ?? agent.agent.name;
4134
+ const description = agent.description ?? agent.agent.description;
4135
+ const config = {
4136
+ id: agent.id,
4137
+ quickPrompts: agent.quickPrompts ?? []
4138
+ };
4139
+ if (name !== void 0) config.name = name;
4140
+ if (description !== void 0) config.description = description;
4141
+ if (agent.metadata !== void 0) config.metadata = agent.metadata;
4142
+ return config;
4143
+ }
4144
+ function agentRuntimeSummary(agent) {
4145
+ const tools = agentToolItems(agent);
4146
+ const staticContext = staticContextDocuments(agent.agent);
4147
+ const indexedContext = vectorContexts(agent.agent);
4148
+ const name = agent.name ?? agent.agent.name;
4149
+ const description = agent.description ?? agent.agent.description;
4150
+ const summary = {
4151
+ id: agent.id,
4152
+ model: toJsonValue2(agent.agent.model),
4153
+ toolCount: tools.length,
4154
+ staticToolCount: tools.filter((item) => item.source === "static").length,
4155
+ dynamicToolCount: tools.filter((item) => item.source === "dynamic").length,
4156
+ approvalToolCount: tools.filter((item) => toolRequiresApproval(item.tool)).length,
4157
+ mcpToolCount: tools.filter((item) => mcpServerName(item.tool) !== void 0).length,
4158
+ staticContextCount: staticContext.length,
4159
+ dynamicContextCount: indexedContext.length,
4160
+ observerCount: Object.keys(agent.agent.observability?.observers ?? {}).length,
4161
+ hasMemory: agent.agent.memory !== void 0,
4162
+ hasLifecycle: agent.agent.lifecycle !== void 0,
4163
+ hasOutputSchema: agent.agent.outputSchema !== void 0
4164
+ };
4165
+ if (name !== void 0) summary.name = name;
4166
+ if (description !== void 0) summary.description = description;
4167
+ if (agent.agent.defaultMaxTurns !== void 0) {
4168
+ summary.defaultMaxTurns = agent.agent.defaultMaxTurns;
4169
+ }
4170
+ if (agent.metadata !== void 0) summary.metadata = agent.metadata;
4171
+ return summary;
4172
+ }
4173
+ function pipelineConfig(pipeline) {
4174
+ const graph = pipeline.pipeline.graph();
4175
+ const stageNodes = graph.nodes.filter((node) => node.kind !== "input" && node.kind !== "output");
4176
+ const config = {
4177
+ id: pipeline.id,
4178
+ stageCount: stageNodes.length,
4179
+ edgeCount: graph.edges.length,
4180
+ hasParallelStages: graph.nodes.some((node) => node.kind === "parallel"),
4181
+ agentCount: graph.nodes.filter((node) => node.kind === "agent").length,
4182
+ extractorCount: graph.nodes.filter((node) => node.kind === "extractor").length
4183
+ };
4184
+ if (pipeline.name !== void 0) config.name = pipeline.name;
4185
+ if (pipeline.description !== void 0) config.description = pipeline.description;
4186
+ if (pipeline.metadata !== void 0) config.metadata = pipeline.metadata;
4187
+ return config;
4188
+ }
4189
+ function buildConfig(options, agents, pipelines, stores, sandboxCount = 0) {
4190
+ const models = options.models === void 0 ? void 0 : studioModelsConfig(createStudioModelRegistry(options.models), agents);
4191
+ const config = {
4192
+ id: runnerId(options),
4193
+ agents: agents.map(agentConfig),
4194
+ pipelines: pipelines.map(pipelineConfig),
4195
+ evals: options.evals.map(evalConfig),
4196
+ chat: {
4197
+ quickPrompts: Object.fromEntries(agents.map((agent) => [agent.id, agent.quickPrompts ?? []]))
4198
+ },
4199
+ capabilities: capabilityConfig(options, agents, pipelines, stores, sandboxCount),
4200
+ unsupportedCapabilities: unsupportedCapabilities(stores)
4201
+ };
4202
+ if (options.name !== void 0) config.name = options.name;
4203
+ if (options.description !== void 0) config.description = options.description;
4204
+ if (options.version !== void 0) config.version = options.version;
4205
+ if (models !== void 0) config.models = models;
4206
+ return config;
4207
+ }
4208
+ function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0) {
4209
+ const capabilities = {
4210
+ agents: { enabled: true },
4211
+ observability: { enabled: true },
4212
+ status: { enabled: true }
4213
+ };
4214
+ if (stores.sessions !== void 0) {
4215
+ capabilities.sessions = { enabled: true };
4216
+ }
4217
+ if (stores.sessions !== void 0 || agents.some((agent) => agent.agent.memory !== void 0)) {
4218
+ capabilities.memory = { enabled: true };
4219
+ }
4220
+ if (stores.traces !== void 0) {
4221
+ capabilities.traces = { enabled: true };
4222
+ }
4223
+ if (pipelines.length > 0) {
4224
+ capabilities.pipelines = { enabled: true };
4225
+ }
4226
+ if (_options.evals.length > 0) {
4227
+ capabilities.evals = { enabled: true };
4228
+ }
4229
+ if (agents.some((agent) => agent.agent.tools.length > 0)) {
4230
+ capabilities.tools = { enabled: true };
4231
+ }
4232
+ if (agents.some(agentHasMcpServers)) {
4233
+ capabilities.mcps = { enabled: true };
4234
+ }
4235
+ if (sandboxCount > 0) {
4236
+ capabilities.sandboxes = { enabled: true };
4237
+ }
4238
+ if (agents.some(
4239
+ (agent) => agent.agent.tools.some((tool) => toolRequiresApproval(tool) || isQuestionTool(tool))
4240
+ )) {
4241
+ capabilities.interactions = { enabled: true };
4242
+ }
4243
+ if (agents.some(agentHasKnowledge)) {
4244
+ capabilities.knowledge = { enabled: true };
4245
+ }
4246
+ return capabilities;
4247
+ }
4248
+ function unsupportedCapabilities(stores) {
4249
+ const capabilities = [];
4250
+ if (stores.sessions === void 0) capabilities.push("sessions");
4251
+ if (stores.traces === void 0) capabilities.push("traces");
4252
+ return capabilities;
4253
+ }
4254
+ function toJsonValue2(value) {
4255
+ return serializeUnknown(value);
4256
+ }
4257
+
4258
+ // src/runtime/evals.ts
4259
+ import { runEvalSuite } from "@anvia/core/evals";
4260
+ function registerEvalRoutes(app, props) {
4261
+ app.get(
4262
+ "/evals",
4263
+ (c) => c.json({
4264
+ evals: props.evals.map(evalConfig)
4265
+ })
4266
+ );
4267
+ app.get("/evals/:evalId", (c) => {
4268
+ const suite = props.evalMap.get(c.req.param("evalId"));
4269
+ if (suite === void 0) {
4270
+ return errorResponse(c, 404, "not_found", "Eval suite not found");
4271
+ }
4272
+ return c.json(evalConfig(suite));
4273
+ });
4274
+ app.post("/evals/:evalId/runs", async (c) => {
4275
+ const suite = props.evalMap.get(c.req.param("evalId"));
4276
+ if (suite === void 0) {
4277
+ return errorResponse(c, 404, "not_found", "Eval suite not found");
4278
+ }
4279
+ const body = await parseEvalRunRequest(c);
4280
+ if ("error" in body) {
4281
+ return body.error;
4282
+ }
4283
+ const runId = globalThis.crypto.randomUUID();
4284
+ const startedAt = Date.now();
4285
+ const runOptions = { ...suite };
4286
+ if (body.concurrency !== void 0) runOptions.concurrency = body.concurrency;
4287
+ const result = await runEvalSuite(runOptions);
4288
+ const endedAt = Date.now();
4289
+ const jsonResult = toJsonValue(result);
4290
+ const response = {
4291
+ runId,
4292
+ suiteId: suite.id ?? suite.name,
4293
+ startedAt: new Date(startedAt).toISOString(),
4294
+ endedAt: new Date(endedAt).toISOString(),
4295
+ durationMs: endedAt - startedAt,
4296
+ result: isJsonObject(jsonResult) ? jsonResult : { value: jsonResult }
4297
+ };
4298
+ return c.json(response);
4299
+ });
4300
+ }
4301
+ async function parseEvalRunRequest(c) {
4302
+ let body = {};
4303
+ try {
4304
+ body = await c.req.json();
4305
+ } catch {
4306
+ body = {};
4307
+ }
4308
+ if (!isObject(body)) {
4309
+ return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
4310
+ }
4311
+ const request = {};
4312
+ if ("concurrency" in body) {
4313
+ if (!isPositiveInteger(body.concurrency)) {
4314
+ return {
4315
+ error: errorResponse(c, 400, "bad_request", "concurrency must be a positive integer")
4316
+ };
4317
+ }
4318
+ request.concurrency = body.concurrency;
4319
+ }
4320
+ return request;
4321
+ }
4322
+
4323
+ // src/runtime/interactions.ts
4324
+ import {
4325
+ assertAgentInteractionResponse
4326
+ } from "@anvia/core/agent/interactions";
4327
+ function createStudioContinuationRegistry() {
4328
+ const pending = /* @__PURE__ */ new Map();
4329
+ const claimed = /* @__PURE__ */ new Set();
4330
+ return {
4331
+ register(registration) {
4332
+ const id = registration.continuation.interaction.id;
4333
+ if (pending.has(id) || claimed.has(id)) {
4334
+ throw new TypeError(`Duplicate Studio interaction ID "${id}".`);
4335
+ }
4336
+ pending.set(id, registration);
4337
+ },
4338
+ take(interactionId, agentId, response) {
4339
+ if (claimed.has(interactionId)) return { status: "claimed" };
4340
+ const registration = pending.get(interactionId);
4341
+ if (registration === void 0 || registration.continuation.agentId !== agentId) {
4342
+ return { status: "missing" };
4343
+ }
4344
+ const request = registration.continuation.interaction;
4345
+ try {
4346
+ assertAgentInteractionResponse(request, response);
4347
+ } catch (error) {
4348
+ return {
4349
+ status: "invalid",
4350
+ error: error instanceof TypeError ? error : new TypeError(String(error))
4351
+ };
4352
+ }
4353
+ pending.delete(interactionId);
4354
+ claimed.add(interactionId);
4355
+ return { status: "ready", registration };
4356
+ },
4357
+ clear() {
4358
+ pending.clear();
4359
+ claimed.clear();
4360
+ }
4361
+ };
4362
+ }
4363
+
4339
4364
  // src/runtime/mcps.ts
4340
4365
  function registerMcpRoutes(app, props) {
4341
4366
  app.get("/agents/:agentId/mcps", async (c) => {
@@ -4351,43 +4376,29 @@ function registerMcpRoutes(app, props) {
4351
4376
  });
4352
4377
  }
4353
4378
  async function agentMcpMetadata(agent) {
4354
- const servers = /* @__PURE__ */ new Map();
4355
- const seen = /* @__PURE__ */ new Set();
4356
- for (const { tool, source } of agentToolItems(agent)) {
4357
- const serverName = mcpServerName(tool);
4358
- if (serverName === void 0) {
4359
- continue;
4360
- }
4361
- const definition = await tool.definition("");
4362
- const key = `${serverName}:${source}:${definition.name}`;
4363
- if (seen.has(key)) {
4364
- continue;
4365
- }
4366
- seen.add(key);
4367
- const tools = servers.get(serverName) ?? [];
4368
- tools.push({
4369
- name: definition.name,
4370
- description: definition.description,
4371
- parameters: definition.parameters,
4372
- source,
4373
- approval: approvalMetadata(tool)
4374
- });
4375
- servers.set(serverName, tools);
4376
- }
4377
- return [...servers.entries()].map(([name, tools]) => {
4378
- const sortedTools = tools.sort((left, right) => {
4379
- if (left.source !== right.source) {
4380
- return left.source === "static" ? -1 : 1;
4381
- }
4382
- return left.name.localeCompare(right.name);
4383
- });
4384
- return {
4385
- agentId: agent.id,
4386
- name,
4387
- toolCount: sortedTools.length,
4388
- tools: sortedTools
4389
- };
4390
- }).sort((left, right) => left.name.localeCompare(right.name));
4379
+ return Promise.all(
4380
+ agent.agent.mcpServers.map(async (server) => {
4381
+ const tools = await Promise.all(
4382
+ server.tools.map(async (tool) => {
4383
+ const definition = await tool.definition("");
4384
+ return {
4385
+ name: definition.name,
4386
+ description: definition.description,
4387
+ parameters: definition.parameters,
4388
+ source: "static",
4389
+ approval: approvalMetadata(tool)
4390
+ };
4391
+ })
4392
+ );
4393
+ const sortedTools = tools.sort((left, right) => left.name.localeCompare(right.name));
4394
+ return {
4395
+ agentId: agent.id,
4396
+ name: server.name,
4397
+ toolCount: sortedTools.length,
4398
+ tools: sortedTools
4399
+ };
4400
+ })
4401
+ ).then((servers) => servers.sort((left, right) => left.name.localeCompare(right.name)));
4391
4402
  }
4392
4403
 
4393
4404
  // src/runtime/memory.ts
@@ -4609,7 +4620,7 @@ function agentMemorySource(ref, label, agentId, storeKindValue, inspector) {
4609
4620
  ).slice(0, options.limit);
4610
4621
  },
4611
4622
  async getConversation(conversationRef) {
4612
- const conversation = await inspector.getConversation(conversationRef);
4623
+ const conversation = await inspector.getConversation({ ref: conversationRef });
4613
4624
  return conversation === void 0 ? void 0 : inspectorConversationDetail(conversation, summary.agentIds);
4614
4625
  }
4615
4626
  };
@@ -4832,12 +4843,12 @@ function pipelineRunFailedLog(pipelineId, runId, error, startedAt) {
4832
4843
  }
4833
4844
  };
4834
4845
  }
4835
- function pipelineStageLog(pipelineId, runId, event) {
4846
+ function pipelineStageLog(event, studioPipelineId = event.pipelineId) {
4836
4847
  const category = stageCategory(event.node);
4837
4848
  if (event.type === "stage_started") {
4838
4849
  return {
4839
- pipelineId,
4840
- runId,
4850
+ pipelineId: studioPipelineId,
4851
+ runId: event.runId,
4841
4852
  level: "debug",
4842
4853
  category,
4843
4854
  event: `${event.node.kind}.started`,
@@ -4847,8 +4858,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4847
4858
  }
4848
4859
  if (event.type === "stage_completed") {
4849
4860
  return {
4850
- pipelineId,
4851
- runId,
4861
+ pipelineId: studioPipelineId,
4862
+ runId: event.runId,
4852
4863
  level: "debug",
4853
4864
  category,
4854
4865
  event: `${event.node.kind}.completed`,
@@ -4860,8 +4871,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4860
4871
  };
4861
4872
  }
4862
4873
  return {
4863
- pipelineId,
4864
- runId,
4874
+ pipelineId: studioPipelineId,
4875
+ runId: event.runId,
4865
4876
  level: "error",
4866
4877
  category,
4867
4878
  event: `${event.node.kind}.failed`,
@@ -4888,6 +4899,7 @@ function stageCategory(node) {
4888
4899
  function nodeMetadata(node) {
4889
4900
  const metadata = {
4890
4901
  nodeId: node.id,
4902
+ nodePath: [...node.path],
4891
4903
  kind: node.kind,
4892
4904
  label: node.label
4893
4905
  };
@@ -5049,17 +5061,24 @@ async function executePipelineRun(c, props, pipeline, body) {
5049
5061
  if (body.metadata !== void 0) streamOptions.metadata = body.metadata;
5050
5062
  if (props.logStore !== void 0) streamOptions.logStore = props.logStore;
5051
5063
  if (props.runStore !== void 0) streamOptions.runStore = props.runStore;
5064
+ streamOptions.abortSignal = c.req.raw.signal;
5052
5065
  return streamPipelineRun(c, streamOptions);
5053
5066
  }
5054
5067
  try {
5055
5068
  await appendPipelineLog(props.logStore, pipelineRunStartedLog(pipeline, runId));
5056
- const output = await pipeline.pipeline.run(body.input, {
5069
+ const result = await pipeline.pipeline.run({
5070
+ input: body.input,
5071
+ runId,
5072
+ metadata: body.metadata,
5073
+ abortSignal: c.req.raw.signal,
5057
5074
  observer: {
5058
5075
  async onEvent(event) {
5059
- await appendPipelineLog(props.logStore, pipelineStageLog(pipeline.id, runId, event));
5076
+ await appendPipelineLog(props.logStore, pipelineStageLog(event, pipeline.id));
5060
5077
  }
5061
- }
5078
+ },
5079
+ failOnObserverError: true
5062
5080
  });
5081
+ const output = result.output;
5063
5082
  const jsonOutput = toJsonValue(output);
5064
5083
  const endedAt = Date.now();
5065
5084
  const successRecord = {
@@ -5124,19 +5143,24 @@ function streamPipelineRun(_c, props) {
5124
5143
  async function* pipelineRunEvents(props) {
5125
5144
  yield* emitPipelineLog(props.logStore, pipelineRunStartedLog(props.pipeline, props.runId));
5126
5145
  const events = new AsyncEventQueue();
5127
- const run = props.pipeline.pipeline.run(props.input, {
5146
+ const run = props.pipeline.pipeline.run({
5147
+ input: props.input,
5148
+ runId: props.runId,
5149
+ metadata: props.metadata,
5150
+ abortSignal: props.abortSignal,
5128
5151
  observer: {
5129
5152
  async onEvent(event) {
5130
5153
  const log = await appendPipelineLog(
5131
5154
  props.logStore,
5132
- pipelineStageLog(props.pipeline.id, props.runId, event)
5155
+ pipelineStageLog(event, props.pipeline.id)
5133
5156
  );
5134
5157
  if (log !== void 0) {
5135
5158
  events.push({ type: "pipeline_log", log });
5136
5159
  }
5137
5160
  }
5138
- }
5139
- }).then(async (output) => {
5161
+ },
5162
+ failOnObserverError: true
5163
+ }).then(async ({ output }) => {
5140
5164
  const jsonOutput = toJsonValue(output);
5141
5165
  const endedAt = Date.now();
5142
5166
  const successRecord = {
@@ -5289,272 +5313,284 @@ function parsePipelineLogAfter(value) {
5289
5313
  return after;
5290
5314
  }
5291
5315
 
5292
- // src/runtime/questions.ts
5293
- import { createHook as createHook2 } from "@anvia/core/hooks";
5294
- import { parseToolArgs } from "@anvia/core/tool";
5295
- function registerQuestionRoutes(app, questions) {
5296
- app.get("/questions", (c) => {
5297
- const status = parseQuestionStatus(c.req.query("status"));
5298
- if (status === false) {
5299
- return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
5300
- }
5301
- const options = {};
5302
- const runId = optionalQueryString(c.req.query("runId"));
5303
- const agentId = optionalQueryString(c.req.query("agentId"));
5304
- const sessionId = optionalQueryString(c.req.query("sessionId"));
5305
- if (status !== void 0) {
5306
- options.status = status;
5307
- }
5308
- if (runId !== void 0) {
5309
- options.runId = runId;
5310
- }
5311
- if (agentId !== void 0) {
5312
- options.agentId = agentId;
5313
- }
5314
- if (sessionId !== void 0) {
5315
- options.sessionId = sessionId;
5316
- }
5317
- return c.json({ questions: questions.list(options) });
5316
+ // src/runtime/sandbox-views.ts
5317
+ import { upgradeWebSocket } from "@hono/node-server";
5318
+ import { getConnInfo } from "@hono/node-server/conninfo";
5319
+ import { WebSocket } from "ws";
5320
+ var minLeaseTimeoutMs = 5e3;
5321
+ var maxLeaseTimeoutMs = 12e4;
5322
+ var maxPendingWebSocketBytes = 1024 * 1024;
5323
+ function registerSandboxViewRoutes(app, registry) {
5324
+ const leases = /* @__PURE__ */ new Map();
5325
+ const upstreamSockets = /* @__PURE__ */ new Set();
5326
+ app.get("/sandboxes/:sandboxRef/views/:viewId/control", async (c) => {
5327
+ const resolved = await authorizedView(c, registry);
5328
+ if (resolved instanceof Response) return resolved;
5329
+ pruneExpiredLease(leases, viewKey(resolved));
5330
+ return c.json(resolved.view.source.control.snapshot());
5318
5331
  });
5319
- app.post("/questions/:questionId/answer", async (c) => {
5320
- const body = await parseQuestionAnswerRequest(c);
5321
- if ("error" in body) {
5322
- return body.error;
5323
- }
5324
- const result = questions.answer(c.req.param("questionId"), body.answers);
5325
- if (result === "missing") {
5326
- return errorResponse(c, 404, "not_found", "Question not found");
5327
- }
5328
- if (result === "resolved") {
5329
- return errorResponse(c, 409, "conflict", "Question is already answered");
5330
- }
5331
- return c.json(result);
5332
+ app.get("/sandboxes/:sandboxRef/views/:viewId/connection", async (c) => {
5333
+ const resolved = await authorizedView(c, registry);
5334
+ if (resolved instanceof Response) return resolved;
5335
+ const response = {
5336
+ sandboxRef: resolved.entry.summary.ref,
5337
+ viewId: resolved.view.id,
5338
+ protocol: "novnc",
5339
+ websocketPath: `/sandboxes/${encodeURIComponent(resolved.entry.summary.ref)}/views/${encodeURIComponent(resolved.view.id)}/ws`,
5340
+ authentication: resolved.view.authentication.type === "password" ? { type: "password", password: resolved.view.authentication.password } : { type: "none" }
5341
+ };
5342
+ c.header("cache-control", "no-store");
5343
+ return c.json(response);
5332
5344
  });
5333
- }
5334
- function parseQuestionStatus(value) {
5335
- const status = optionalQueryString(value);
5336
- if (status === void 0) {
5337
- return void 0;
5338
- }
5339
- return status === "pending" || status === "resolved" ? status : false;
5340
- }
5341
- async function parseQuestionAnswerRequest(c) {
5342
- let body;
5343
- try {
5344
- body = await c.req.json();
5345
- } catch {
5346
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
5347
- }
5348
- if (!isObject(body)) {
5349
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
5350
- }
5351
- if (!Array.isArray(body.answers)) {
5352
- return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
5353
- }
5354
- const answers = [];
5355
- for (const answer of body.answers) {
5356
- if (!isObject(answer)) {
5357
- return { error: errorResponse(c, 400, "bad_request", "answers must contain objects") };
5345
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/acquire", async (c) => {
5346
+ const resolved = await authorizedView(c, registry);
5347
+ if (resolved instanceof Response) return resolved;
5348
+ const body = await readControlBody(c);
5349
+ if (body instanceof Response) return body;
5350
+ const key = viewKey(resolved);
5351
+ pruneExpiredLease(leases, key);
5352
+ if (leases.has(key)) {
5353
+ return errorResponse(c, 409, "conflict", "Browser view is already controlled");
5358
5354
  }
5359
- if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
5360
- return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
5361
- }
5362
- if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
5363
- return { error: errorResponse(c, 400, "bad_request", "answer must be a string") };
5355
+ try {
5356
+ const lease = await resolved.view.source.control.acquireHumanControl({
5357
+ ownerId: body.ownerId,
5358
+ leaseTimeoutMs: body.leaseTimeoutMs,
5359
+ abortSignal: c.req.raw.signal
5360
+ });
5361
+ leases.set(key, lease);
5362
+ return c.json(leaseResponse(lease), 201);
5363
+ } catch (error) {
5364
+ return controlErrorResponse(c, error);
5364
5365
  }
5365
- if ("choice" in answer && typeof answer.choice !== "string") {
5366
- return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
5366
+ });
5367
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/renew", async (c) => {
5368
+ const resolved = await authorizedView(c, registry);
5369
+ if (resolved instanceof Response) return resolved;
5370
+ const body = await readLeaseBody(c);
5371
+ if (body instanceof Response) return body;
5372
+ const key = viewKey(resolved);
5373
+ pruneExpiredLease(leases, key);
5374
+ const lease = leases.get(key);
5375
+ if (lease === void 0 || lease.id !== body.leaseId) {
5376
+ return errorResponse(c, 409, "conflict", "Browser control lease is not active");
5367
5377
  }
5368
- if ("custom" in answer && typeof answer.custom !== "boolean") {
5369
- return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
5378
+ try {
5379
+ lease.renew({ leaseTimeoutMs: body.leaseTimeoutMs });
5380
+ return c.json(leaseResponse(lease));
5381
+ } catch (error) {
5382
+ leases.delete(key);
5383
+ return controlErrorResponse(c, error);
5370
5384
  }
5371
- const normalized = {
5372
- questionId: answer.questionId.trim(),
5373
- answer: answer.answer.trim()
5374
- };
5375
- if (typeof answer.choice === "string") normalized.choice = answer.choice;
5376
- if (typeof answer.custom === "boolean") normalized.custom = answer.custom;
5377
- answers.push(normalized);
5378
- }
5379
- return { answers };
5385
+ });
5386
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/release", async (c) => {
5387
+ const resolved = await authorizedView(c, registry);
5388
+ if (resolved instanceof Response) return resolved;
5389
+ const body = await readReleaseBody(c);
5390
+ if (body instanceof Response) return body;
5391
+ const key = viewKey(resolved);
5392
+ const lease = leases.get(key);
5393
+ if (lease === void 0 || lease.id !== body.leaseId) {
5394
+ return errorResponse(c, 409, "conflict", "Browser control lease is not active");
5395
+ }
5396
+ lease.release();
5397
+ leases.delete(key);
5398
+ return c.json({ released: true });
5399
+ });
5400
+ app.get("/sandboxes/:sandboxRef/views/:viewId/ws", async (c) => {
5401
+ const resolved = await authorizedView(c, registry);
5402
+ if (resolved instanceof Response) return resolved;
5403
+ const upstreamUrl = `ws://${hostForUrl(resolved.host)}:${resolved.port}/websockify`;
5404
+ return upgradeWebSocket(c, createProxyEvents(upstreamUrl, upstreamSockets), {
5405
+ onError: () => void 0
5406
+ });
5407
+ });
5408
+ return () => {
5409
+ for (const lease of leases.values()) lease.release();
5410
+ leases.clear();
5411
+ for (const socket of upstreamSockets) socket.close(1001, "Studio closed");
5412
+ upstreamSockets.clear();
5413
+ };
5380
5414
  }
5381
- function createQuestionRuntime() {
5382
- const questions = /* @__PURE__ */ new Map();
5415
+ function createProxyEvents(upstreamUrl, sockets) {
5416
+ let upstream;
5417
+ let pendingBytes = 0;
5418
+ const pending = [];
5383
5419
  return {
5384
- questions,
5385
- createHook(context) {
5386
- return createHook2({
5387
- async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
5388
- if (toolName !== "ask_question") {
5389
- return control.run();
5390
- }
5391
- const prompts = normalizeQuestionPrompts(parseToolArgs(args));
5392
- if ("error" in prompts) {
5393
- return control.skip(prompts.error);
5394
- }
5395
- const request = {
5396
- toolName,
5397
- internalCallId,
5398
- args,
5399
- questions: prompts.questions
5400
- };
5401
- if (toolCallId !== void 0) request.toolCallId = toolCallId;
5402
- const answers = await requestQuestion(questions, context, request);
5403
- return control.skip(JSON.stringify({ answers }));
5404
- }
5420
+ onOpen(_event, downstream) {
5421
+ const protocols = downstream.protocol === null || downstream.protocol.length === 0 ? void 0 : [downstream.protocol];
5422
+ upstream = new WebSocket(upstreamUrl, protocols, {
5423
+ headers: { origin: "http://127.0.0.1" }
5424
+ });
5425
+ sockets.add(upstream);
5426
+ upstream.binaryType = "arraybuffer";
5427
+ upstream.once("open", () => {
5428
+ for (const message of pending) upstream?.send(message);
5429
+ pending.length = 0;
5430
+ pendingBytes = 0;
5431
+ });
5432
+ upstream.on("message", (data, isBinary) => {
5433
+ const value = isBinary ? Uint8Array.from(Buffer.from(data)) : data.toString();
5434
+ downstream.send(value);
5405
5435
  });
5436
+ upstream.once("close", (code, reason) => {
5437
+ sockets.delete(upstream);
5438
+ downstream.close(normalizeCloseCode(code), reason.toString("utf8").slice(0, 123));
5439
+ });
5440
+ upstream.once("error", () => downstream.close(1011, "noVNC upstream failed"));
5406
5441
  },
5407
- list(options) {
5408
- return [...questions.values()].filter((question) => {
5409
- if (options.status === "pending" && question.status !== "pending") {
5410
- return false;
5411
- }
5412
- if (options.status === "resolved" && question.status === "pending") {
5413
- return false;
5414
- }
5415
- if (options.runId !== void 0 && question.runId !== options.runId) {
5416
- return false;
5417
- }
5418
- if (options.agentId !== void 0 && question.agentId !== options.agentId) {
5419
- return false;
5442
+ onMessage(event, downstream) {
5443
+ void normalizeWebSocketMessage(event.data).then((message) => {
5444
+ if (upstream?.readyState === WebSocket.OPEN) {
5445
+ upstream.send(message);
5446
+ return;
5420
5447
  }
5421
- if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
5422
- return false;
5448
+ pendingBytes += typeof message === "string" ? Buffer.byteLength(message) : message.byteLength;
5449
+ if (pendingBytes > maxPendingWebSocketBytes) {
5450
+ downstream.close(1009, "Pending noVNC messages exceeded the limit");
5451
+ return;
5423
5452
  }
5424
- return true;
5425
- }).map(publicQuestion);
5453
+ pending.push(message);
5454
+ }).catch(() => downstream.close(1003, "Unsupported noVNC message"));
5426
5455
  },
5427
- cancelRun(runId) {
5428
- const cancelled = [];
5429
- for (const question of questions.values()) {
5430
- if (!isPendingQuestion(question) || question.runId !== runId) {
5431
- continue;
5432
- }
5433
- const resolved = cancelQuestion(question);
5434
- questions.set(question.id, resolved);
5435
- question.emit?.({ type: "tool_question_result", question: resolved });
5436
- question.resolve([]);
5437
- cancelled.push(resolved);
5438
- }
5439
- return cancelled;
5456
+ onClose() {
5457
+ upstream?.close(1e3, "Viewer disconnected");
5458
+ if (upstream !== void 0) sockets.delete(upstream);
5440
5459
  },
5441
- answer(id, answers) {
5442
- const question = questions.get(id);
5443
- if (question === void 0) {
5444
- return "missing";
5445
- }
5446
- if (!isPendingQuestion(question)) {
5447
- return "resolved";
5448
- }
5449
- const resolved = resolveQuestion(question, answers);
5450
- questions.set(id, resolved);
5451
- question.emit?.({ type: "tool_question_result", question: resolved });
5452
- question.resolve(answers);
5453
- return publicQuestion(resolved);
5460
+ onError() {
5461
+ upstream?.close(1011, "Viewer connection failed");
5454
5462
  }
5455
5463
  };
5456
5464
  }
5457
- async function requestQuestion(questions, context, request) {
5458
- const id = globalThis.crypto.randomUUID();
5459
- const question = {
5460
- id,
5461
- runId: context.runId,
5462
- agentId: context.agentId,
5463
- toolName: request.toolName,
5464
- internalCallId: request.internalCallId,
5465
- args: request.args,
5466
- questions: request.questions,
5467
- status: "pending",
5468
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
5469
- resolve: () => {
5470
- }
5471
- };
5472
- if (context.sessionId !== void 0) question.sessionId = context.sessionId;
5473
- if (request.toolCallId !== void 0) question.callId = request.toolCallId;
5474
- if (context.emit !== void 0) question.emit = context.emit;
5475
- const answer = new Promise((resolve2) => {
5476
- question.resolve = (answers) => {
5477
- resolve2(answers);
5478
- };
5479
- });
5480
- questions.set(id, question);
5481
- context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
5482
- return answer;
5483
- }
5484
- function normalizeQuestionPrompts(args) {
5485
- if (!isObject(args)) {
5486
- return { error: "ask_question requires a JSON object with questions." };
5487
- }
5488
- const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
5489
- if (rawQuestions.length === 0) {
5490
- return { error: "ask_question requires at least one question." };
5491
- }
5492
- const questions = [];
5493
- for (const [index, question] of rawQuestions.entries()) {
5494
- const normalized = normalizeQuestionPrompt(question, index);
5495
- if (normalized === void 0) {
5496
- return {
5497
- error: "ask_question requires every question to include text and at least one choice."
5498
- };
5465
+ async function authorizedView(c, registry) {
5466
+ const entry = registry.get(c.req.param("sandboxRef") ?? "");
5467
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5468
+ const view = entry.views.get(c.req.param("viewId") ?? "");
5469
+ if (view === void 0) return errorResponse(c, 404, "not_found", "Sandbox view not found");
5470
+ if (!await authorize(c, entry, view)) {
5471
+ return errorResponse(c, 403, "forbidden", "Sandbox view access denied");
5472
+ }
5473
+ const published = entry.inspector.publishedPorts?.find(
5474
+ (port) => port.containerPort === view.source.containerPort && port.protocol === "tcp" && isLoopbackAddress(port.host)
5475
+ );
5476
+ if (published === void 0) {
5477
+ return errorResponse(c, 503, "internal_error", "Sandbox view port is unavailable");
5478
+ }
5479
+ return { entry, view, host: published.host, port: published.hostPort };
5480
+ }
5481
+ async function authorize(c, entry, view) {
5482
+ if (view.access.mode === "authorize") {
5483
+ try {
5484
+ return await view.access.authorize({
5485
+ request: c.req.raw,
5486
+ sandboxRef: entry.summary.ref,
5487
+ viewId: view.id
5488
+ }) === true;
5489
+ } catch {
5490
+ return false;
5499
5491
  }
5500
- questions.push(normalized);
5501
5492
  }
5502
- return { questions };
5493
+ let remoteAddress;
5494
+ try {
5495
+ const address = getConnInfo(c).remote.address;
5496
+ if (address === void 0) return false;
5497
+ remoteAddress = address;
5498
+ } catch {
5499
+ return false;
5500
+ }
5501
+ if (!isLoopbackAddress(remoteAddress)) return false;
5502
+ const expectedOrigin = new URL(c.req.url).origin;
5503
+ const origin = c.req.header("origin");
5504
+ return origin === expectedOrigin || origin === void 0 && c.req.header("sec-fetch-site") === "same-origin";
5503
5505
  }
5504
- function normalizeQuestionPrompt(value, index) {
5505
- if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
5506
- return void 0;
5506
+ async function readControlBody(c) {
5507
+ const body = await readJsonObject(c);
5508
+ if (body instanceof Response) return body;
5509
+ if (typeof body.ownerId !== "string" || body.ownerId.length === 0 || body.ownerId.length > 200) {
5510
+ return errorResponse(c, 400, "bad_request", "ownerId must be a non-empty string");
5507
5511
  }
5508
- const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
5509
- if (choices.length === 0) {
5510
- return void 0;
5512
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5513
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5511
5514
  }
5512
- return {
5513
- id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
5514
- question: value.question.trim(),
5515
- choices
5516
- };
5515
+ return { ownerId: body.ownerId, leaseTimeoutMs: body.leaseTimeoutMs };
5517
5516
  }
5518
- function normalizeQuestionChoice(value) {
5519
- if (typeof value === "string" && value.trim().length > 0) {
5520
- return { label: value.trim(), value: value.trim() };
5517
+ async function readLeaseBody(c) {
5518
+ const body = await readJsonObject(c);
5519
+ if (body instanceof Response) return body;
5520
+ if (typeof body.leaseId !== "string" || body.leaseId.length === 0) {
5521
+ return errorResponse(c, 400, "bad_request", "leaseId must be a non-empty string");
5521
5522
  }
5522
- if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
5523
- return void 0;
5523
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5524
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5524
5525
  }
5525
- return {
5526
- label: value.label.trim(),
5527
- value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
5528
- };
5526
+ return { leaseId: body.leaseId, leaseTimeoutMs: body.leaseTimeoutMs };
5529
5527
  }
5530
- function isPendingQuestion(question) {
5531
- return question !== void 0 && question.status === "pending" && "resolve" in question;
5528
+ async function readReleaseBody(c) {
5529
+ const body = await readJsonObject(c);
5530
+ if (body instanceof Response) return body;
5531
+ if (typeof body.leaseId !== "string" || body.leaseId.length === 0) {
5532
+ return errorResponse(c, 400, "bad_request", "leaseId must be a non-empty string");
5533
+ }
5534
+ return { leaseId: body.leaseId };
5532
5535
  }
5533
- function resolveQuestion(question, answers) {
5534
- return publicQuestion({
5535
- ...question,
5536
- status: "answered",
5537
- answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
5538
- answers
5539
- });
5536
+ async function readJsonObject(c) {
5537
+ let body;
5538
+ try {
5539
+ body = await c.req.json();
5540
+ } catch {
5541
+ return errorResponse(c, 400, "bad_request", "Request body must be valid JSON");
5542
+ }
5543
+ return isRecord4(body) ? body : errorResponse(c, 400, "bad_request", "Request body must be an object");
5540
5544
  }
5541
- function cancelQuestion(question) {
5542
- return publicQuestion({
5543
- ...question,
5544
- status: "cancelled",
5545
- cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
5546
- });
5545
+ function controlErrorResponse(c, error) {
5546
+ if (isRecord4(error) && error.code === "human_controlled") {
5547
+ return errorResponse(c, 409, "conflict", "Browser view is already controlled");
5548
+ }
5549
+ if (isRecord4(error) && error.code === "invalid_state") {
5550
+ return errorResponse(c, 409, "conflict", "Browser control lease is no longer active");
5551
+ }
5552
+ return errorResponse(c, 500, "internal_error", "Browser control operation failed");
5553
+ }
5554
+ function leaseResponse(lease) {
5555
+ return { id: lease.id, ownerId: lease.ownerId, expiresAt: lease.expiresAt };
5556
+ }
5557
+ function pruneExpiredLease(leases, key) {
5558
+ const lease = leases.get(key);
5559
+ if (lease !== void 0 && Date.parse(lease.expiresAt) <= Date.now()) {
5560
+ lease.release();
5561
+ leases.delete(key);
5562
+ }
5563
+ }
5564
+ function viewKey(resolved) {
5565
+ return `${resolved.entry.summary.ref}\0${resolved.view.id}`;
5566
+ }
5567
+ function isLeaseTimeout(value) {
5568
+ return Number.isSafeInteger(value) && Number(value) >= minLeaseTimeoutMs && Number(value) <= maxLeaseTimeoutMs;
5569
+ }
5570
+ function isLoopbackAddress(value) {
5571
+ return value === "localhost" || value === "127.0.0.1" || value === "::1" || value.startsWith("127.") || value.startsWith("::ffff:127.");
5572
+ }
5573
+ function hostForUrl(host) {
5574
+ return host.includes(":") ? `[${host}]` : host;
5575
+ }
5576
+ async function normalizeWebSocketMessage(value) {
5577
+ if (typeof value === "string") return value;
5578
+ if (value instanceof ArrayBuffer) return Uint8Array.from(new Uint8Array(value));
5579
+ if (ArrayBuffer.isView(value)) {
5580
+ return Uint8Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
5581
+ }
5582
+ if (value instanceof Blob) return Uint8Array.from(new Uint8Array(await value.arrayBuffer()));
5583
+ throw new TypeError("Unsupported noVNC WebSocket message type.");
5584
+ }
5585
+ function normalizeCloseCode(code) {
5586
+ return code >= 1e3 && code <= 4999 && code !== 1004 && code !== 1005 && code !== 1006 ? code : 1001;
5547
5587
  }
5548
- function publicQuestion(question) {
5549
- const { emit, resolve: resolve2, ...rest } = question;
5550
- void emit;
5551
- void resolve2;
5552
- return rest;
5588
+ function isRecord4(value) {
5589
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5553
5590
  }
5554
5591
 
5555
5592
  // src/runtime/sandboxes.ts
5556
5593
  import path from "path";
5557
- var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
5558
5594
  var maxFileResponseBytes = 10 * 1024 * 1024;
5559
5595
  var defaultProcessLogBytes = 64 * 1024;
5560
5596
  var maxProcessLogBytes = 1024 * 1024;
@@ -5573,51 +5609,41 @@ var StudioSandboxRegistry = class {
5573
5609
  return this.entries.get(ref);
5574
5610
  }
5575
5611
  };
5576
- function createStudioSandboxRegistry(agents) {
5577
- const drafts = /* @__PURE__ */ new Map();
5578
- for (const agent of agents) {
5579
- for (const { tool } of agentToolItems(agent)) {
5580
- const session = sandboxSessionFromTool(tool);
5581
- if (session === void 0) {
5582
- continue;
5583
- }
5584
- const draft = drafts.get(session) ?? {
5585
- session,
5586
- agentIds: /* @__PURE__ */ new Set(),
5587
- toolNames: /* @__PURE__ */ new Set()
5588
- };
5589
- draft.agentIds.add(agent.id);
5590
- draft.toolNames.add(tool.name);
5591
- drafts.set(session, draft);
5592
- }
5593
- }
5594
- const refCounts = /* @__PURE__ */ new Map();
5595
- const entries = [...drafts.values()].sort(
5596
- (left, right) => `${left.session.provider}\0${left.session.id}`.localeCompare(
5597
- `${right.session.provider}\0${right.session.id}`
5598
- )
5599
- ).map((draft) => {
5600
- const baseRef = sandboxRef(draft.session.provider, draft.session.id);
5601
- const occurrence = (refCounts.get(baseRef) ?? 0) + 1;
5602
- refCounts.set(baseRef, occurrence);
5603
- const ref = occurrence === 1 ? baseRef : `${baseRef}.${occurrence}`;
5612
+ function createStudioSandboxRegistry(agents, registrations) {
5613
+ const agentIds = new Set(agents.map((agent) => agent.id));
5614
+ const refs = /* @__PURE__ */ new Set();
5615
+ const entries = registrations.map((registration, index) => {
5616
+ validateRegistration(registration, index, agentIds);
5617
+ const inspector = registration.inspector;
5618
+ const ref = sandboxRef(inspector.provider, inspector.id);
5619
+ if (refs.has(ref)) {
5620
+ throw new TypeError(
5621
+ `sandboxes contains a duplicate provider/id registration: ${inspector.provider}/${inspector.id}`
5622
+ );
5623
+ }
5624
+ refs.add(ref);
5625
+ const views = new Map((registration.views ?? []).map((view) => [view.id, view]));
5604
5626
  return {
5605
- session: draft.session,
5627
+ inspector,
5628
+ views,
5606
5629
  summary: {
5607
5630
  ref,
5608
- id: draft.session.id,
5609
- provider: draft.session.provider,
5610
- workdir: draft.session.workdir,
5611
- agentIds: [...draft.agentIds].sort(),
5612
- toolNames: [...draft.toolNames].sort(),
5631
+ id: inspector.id,
5632
+ provider: inspector.provider,
5633
+ workdir: inspector.workdir,
5634
+ agentIds: sortedUnique(registration.agentIds ?? []),
5635
+ toolNames: sortedUnique(registration.toolNames ?? []),
5636
+ views: [...views.values()].map((view) => ({ id: view.id, label: view.label, protocol: view.source.protocol })).sort((left, right) => left.id.localeCompare(right.id)),
5613
5637
  capabilities: {
5614
- files: true,
5615
- ports: supportsPorts(draft.session),
5616
- processes: supportsProcesses(draft.session)
5638
+ files: supportsFiles(inspector),
5639
+ ports: supportsPorts(inspector),
5640
+ processes: supportsProcesses(inspector),
5641
+ views: views.size > 0
5617
5642
  }
5618
5643
  }
5619
5644
  };
5620
5645
  });
5646
+ entries.sort((left, right) => left.summary.ref.localeCompare(right.summary.ref));
5621
5647
  return new StudioSandboxRegistry(entries);
5622
5648
  }
5623
5649
  function registerSandboxRoutes(app, registry) {
@@ -5626,24 +5652,19 @@ function registerSandboxRoutes(app, registry) {
5626
5652
  app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
5627
5653
  app.get("/sandboxes/:sandboxRef", (c) => {
5628
5654
  const entry = registry.get(c.req.param("sandboxRef"));
5629
- if (entry === void 0) {
5630
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5631
- }
5632
- return c.json(copySummary(entry.summary));
5655
+ return entry === void 0 ? errorResponse(c, 404, "not_found", "Sandbox not found") : c.json(copySummary(entry.summary));
5633
5656
  });
5634
5657
  app.get("/sandboxes/:sandboxRef/files", async (c) => {
5635
5658
  const entry = registry.get(c.req.param("sandboxRef"));
5636
- if (entry === void 0) {
5637
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5638
- }
5659
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5660
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files");
5639
5661
  try {
5640
5662
  const requestedPath = c.req.query("path") ?? ".";
5641
5663
  const normalizedPath = normalizeSandboxPath(requestedPath, true);
5642
- const entries = await listSandboxFiles(entry.session, normalizedPath);
5643
5664
  return c.json({
5644
5665
  sandboxRef: entry.summary.ref,
5645
5666
  path: normalizedPath,
5646
- entries
5667
+ entries: await listSandboxFiles(entry.inspector, normalizedPath, c.req.raw.signal)
5647
5668
  });
5648
5669
  } catch (error) {
5649
5670
  return sandboxErrorResponse(c, error);
@@ -5651,9 +5672,8 @@ function registerSandboxRoutes(app, registry) {
5651
5672
  });
5652
5673
  app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
5653
5674
  const entry = registry.get(c.req.param("sandboxRef"));
5654
- if (entry === void 0) {
5655
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5656
- }
5675
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5676
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files.content");
5657
5677
  try {
5658
5678
  const requestedPath = c.req.query("path");
5659
5679
  if (requestedPath === void 0) {
@@ -5664,8 +5684,11 @@ function registerSandboxRoutes(app, registry) {
5664
5684
  throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
5665
5685
  }
5666
5686
  const normalizedPath = normalizeSandboxPath(requestedPath, false);
5667
- const parentPath = path.posix.dirname(normalizedPath);
5668
- const siblings = await listSandboxFiles(entry.session, parentPath);
5687
+ const siblings = await listSandboxFiles(
5688
+ entry.inspector,
5689
+ path.posix.dirname(normalizedPath),
5690
+ c.req.raw.signal
5691
+ );
5669
5692
  const file = siblings.find((candidate) => candidate.path === normalizedPath);
5670
5693
  if (file === void 0) {
5671
5694
  throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
@@ -5677,25 +5700,24 @@ function registerSandboxRoutes(app, registry) {
5677
5700
  "Sandbox content path must refer to a regular file"
5678
5701
  );
5679
5702
  }
5680
- if (file.size !== void 0 && file.size > maxFileResponseBytes) {
5703
+ if (file.size !== void 0 && file.size > maxFileResponseBytes)
5681
5704
  throw fileTooLarge(file.size);
5682
- }
5683
- const rawBytes = await entry.session.readFile(normalizedPath);
5705
+ const rawBytes = await entry.inspector.readFile({
5706
+ path: normalizedPath,
5707
+ abortSignal: c.req.raw.signal
5708
+ });
5684
5709
  if (!(rawBytes instanceof Uint8Array)) {
5685
5710
  throw new TypeError("Sandbox readFile returned an invalid byte payload");
5686
5711
  }
5687
- if (rawBytes.byteLength > maxFileResponseBytes) {
5688
- throw fileTooLarge(rawBytes.byteLength);
5689
- }
5690
- const bytes = new Uint8Array(rawBytes.byteLength);
5691
- bytes.set(rawBytes);
5712
+ if (rawBytes.byteLength > maxFileResponseBytes) throw fileTooLarge(rawBytes.byteLength);
5713
+ const bytes = rawBytes.slice();
5692
5714
  const disposition = download === "1" ? "attachment" : "inline";
5693
5715
  const filename = encodeURIComponent(path.posix.basename(normalizedPath));
5694
5716
  return new Response(bytes, {
5695
5717
  headers: {
5696
5718
  "cache-control": "no-store",
5697
5719
  "content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
5698
- "content-length": String(bytes.byteLength),
5720
+ "content-length": `${bytes.byteLength}`,
5699
5721
  "content-type": "application/octet-stream",
5700
5722
  "x-content-type-options": "nosniff"
5701
5723
  }
@@ -5706,16 +5728,12 @@ function registerSandboxRoutes(app, registry) {
5706
5728
  });
5707
5729
  app.get("/sandboxes/:sandboxRef/ports", (c) => {
5708
5730
  const entry = registry.get(c.req.param("sandboxRef"));
5709
- if (entry === void 0) {
5710
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5711
- }
5712
- if (!supportsPorts(entry.session)) {
5713
- return unsupportedSandboxOperation(c, "ports");
5714
- }
5731
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5732
+ if (!supportsPorts(entry.inspector)) return unsupportedSandboxOperation(c, "ports");
5715
5733
  try {
5716
5734
  return c.json({
5717
5735
  sandboxRef: entry.summary.ref,
5718
- ports: normalizePorts(entry.session.publishedPorts)
5736
+ ports: normalizePorts(entry.inspector.publishedPorts)
5719
5737
  });
5720
5738
  } catch (error) {
5721
5739
  return sandboxErrorResponse(c, error);
@@ -5723,16 +5741,16 @@ function registerSandboxRoutes(app, registry) {
5723
5741
  });
5724
5742
  app.get("/sandboxes/:sandboxRef/processes", async (c) => {
5725
5743
  const entry = registry.get(c.req.param("sandboxRef"));
5726
- if (entry === void 0) {
5727
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5728
- }
5729
- if (!supportsProcesses(entry.session)) {
5744
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5745
+ if (!supportsProcesses(entry.inspector)) {
5730
5746
  return unsupportedSandboxOperation(c, "processes");
5731
5747
  }
5732
5748
  try {
5733
5749
  return c.json({
5734
5750
  sandboxRef: entry.summary.ref,
5735
- processes: normalizeProcesses(await entry.session.listProcesses())
5751
+ processes: normalizeProcesses(
5752
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5753
+ )
5736
5754
  });
5737
5755
  } catch (error) {
5738
5756
  return sandboxErrorResponse(c, error);
@@ -5740,21 +5758,25 @@ function registerSandboxRoutes(app, registry) {
5740
5758
  });
5741
5759
  app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
5742
5760
  const entry = registry.get(c.req.param("sandboxRef"));
5743
- if (entry === void 0) {
5744
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5745
- }
5746
- if (!supportsProcesses(entry.session)) {
5761
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5762
+ if (!supportsProcesses(entry.inspector)) {
5747
5763
  return unsupportedSandboxOperation(c, "processes.logs");
5748
5764
  }
5749
5765
  try {
5750
5766
  const tailBytes = parseTailBytes(c.req.query("tailBytes"));
5751
5767
  const processId = c.req.param("processId");
5752
- const processes = normalizeProcesses(await entry.session.listProcesses());
5768
+ const processes = normalizeProcesses(
5769
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5770
+ );
5753
5771
  if (!processes.some((process2) => process2.id === processId)) {
5754
5772
  throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
5755
5773
  }
5756
5774
  const logs = normalizeProcessLogs(
5757
- await entry.session.readProcessLogs(processId, { tailBytes }),
5775
+ await entry.inspector.readProcessLogs({
5776
+ processId,
5777
+ tailBytes,
5778
+ abortSignal: c.req.raw.signal
5779
+ }),
5758
5780
  tailBytes
5759
5781
  );
5760
5782
  return c.json({
@@ -5771,21 +5793,86 @@ async function sandboxNoStore(c, next) {
5771
5793
  c.header("cache-control", "no-store");
5772
5794
  await next();
5773
5795
  }
5774
- function sandboxSessionFromTool(tool) {
5775
- const metadata = tool[sandboxToolMetadataKey];
5776
- if (!isRecord4(metadata)) {
5777
- return void 0;
5796
+ function validateRegistration(registration, index, knownAgentIds) {
5797
+ if (!isRecord5(registration) || !isInspector(registration.inspector)) {
5798
+ throw new TypeError(`sandboxes[${index}] must contain a valid inspector.`);
5799
+ }
5800
+ const inspector = registration.inspector;
5801
+ const files = supportsFiles(inspector);
5802
+ if (inspector.listFiles === void 0 !== (inspector.readFile === void 0)) {
5803
+ throw new TypeError(`sandboxes[${index}] must register both file inspector methods.`);
5804
+ }
5805
+ const processes = supportsProcesses(inspector);
5806
+ if (inspector.listProcesses === void 0 !== (inspector.readProcessLogs === void 0)) {
5807
+ throw new TypeError(`sandboxes[${index}] must register both process inspector methods.`);
5808
+ }
5809
+ if (!files && !supportsPorts(inspector) && !processes) {
5810
+ throw new TypeError(`sandboxes[${index}] inspector exposes no capabilities.`);
5811
+ }
5812
+ for (const agentId of sortedUnique(registration.agentIds ?? [])) {
5813
+ if (!knownAgentIds.has(agentId)) {
5814
+ throw new TypeError(`sandboxes[${index}] references an unknown agent: ${agentId}`);
5815
+ }
5816
+ }
5817
+ sortedUnique(registration.toolNames ?? []);
5818
+ validateViews(registration.views, inspector, index);
5819
+ }
5820
+ function validateViews(views, inspector, registrationIndex) {
5821
+ if (views === void 0) return;
5822
+ if (!Array.isArray(views))
5823
+ throw new TypeError(`sandboxes[${registrationIndex}].views must be an array.`);
5824
+ if (!supportsPorts(inspector)) {
5825
+ throw new TypeError(`sandboxes[${registrationIndex}] views require published port inspection.`);
5826
+ }
5827
+ const ids = /* @__PURE__ */ new Set();
5828
+ for (const [viewIndex, view] of views.entries()) {
5829
+ const prefix = `sandboxes[${registrationIndex}].views[${viewIndex}]`;
5830
+ const candidate = view;
5831
+ if (!isRecord5(candidate) || typeof candidate.id !== "string" || !/^[a-z0-9](?:[a-z0-9_-]{0,62})$/.test(candidate.id)) {
5832
+ throw new TypeError(`${prefix}.id must be a stable lowercase identifier.`);
5833
+ }
5834
+ if (ids.has(candidate.id)) throw new TypeError(`${prefix}.id is duplicated: ${candidate.id}`);
5835
+ ids.add(candidate.id);
5836
+ if (typeof candidate.label !== "string" || candidate.label.length === 0) {
5837
+ throw new TypeError(`${prefix}.label must be a non-empty string.`);
5838
+ }
5839
+ const source = candidate.source;
5840
+ if (!isRecord5(source) || source.protocol !== "novnc" || !isPort(source.containerPort) || !isRecord5(source.control) || typeof source.control.snapshot !== "function" || typeof source.control.acquireHumanControl !== "function") {
5841
+ throw new TypeError(`${prefix}.source must be a valid noVNC view source.`);
5842
+ }
5843
+ const port = inspector.publishedPorts?.find(
5844
+ (candidate2) => candidate2.containerPort === source.containerPort && candidate2.protocol === "tcp"
5845
+ );
5846
+ if (port === void 0 || !isLoopbackHost(port.host)) {
5847
+ throw new TypeError(`${prefix}.source must resolve to a loopback-published TCP port.`);
5848
+ }
5849
+ const access = candidate.access;
5850
+ if (!isRecord5(access) || access.mode !== "local" && access.mode !== "authorize") {
5851
+ throw new TypeError(`${prefix}.access must explicitly select local or authorize mode.`);
5852
+ }
5853
+ if (access.mode === "authorize" && typeof access.authorize !== "function") {
5854
+ throw new TypeError(`${prefix}.access.authorize must be a function.`);
5855
+ }
5856
+ const authentication = candidate.authentication;
5857
+ if (!isRecord5(authentication) || authentication.type !== "none" && authentication.type !== "password") {
5858
+ throw new TypeError(`${prefix}.authentication must explicitly select none or password.`);
5859
+ }
5860
+ if (authentication.type === "password" && (typeof authentication.password !== "string" || authentication.password.length === 0)) {
5861
+ throw new TypeError(`${prefix}.authentication.password must be a non-empty string.`);
5862
+ }
5778
5863
  }
5779
- return isSandboxSession(metadata.session) ? metadata.session : void 0;
5780
5864
  }
5781
- function isSandboxSession(value) {
5782
- return isRecord4(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.provider === "string" && value.provider.length > 0 && typeof value.workdir === "string" && value.workdir.length > 0 && typeof value.listFiles === "function" && typeof value.readFile === "function";
5865
+ function isInspector(value) {
5866
+ return isRecord5(value) && typeof value.id === "string" && value.id.length > 0 && typeof value.provider === "string" && value.provider.length > 0 && typeof value.workdir === "string" && value.workdir.length > 0;
5783
5867
  }
5784
- function supportsPorts(session) {
5785
- return Array.isArray(session.publishedPorts);
5868
+ function supportsFiles(inspector) {
5869
+ return typeof inspector.listFiles === "function" && typeof inspector.readFile === "function";
5786
5870
  }
5787
- function supportsProcesses(session) {
5788
- return typeof session.listProcesses === "function" && typeof session.readProcessLogs === "function";
5871
+ function supportsPorts(inspector) {
5872
+ return Array.isArray(inspector.publishedPorts);
5873
+ }
5874
+ function supportsProcesses(inspector) {
5875
+ return typeof inspector.listProcesses === "function" && typeof inspector.readProcessLogs === "function";
5789
5876
  }
5790
5877
  function sandboxRef(provider, id) {
5791
5878
  return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
@@ -5795,22 +5882,26 @@ function copySummary(summary) {
5795
5882
  ...summary,
5796
5883
  agentIds: [...summary.agentIds],
5797
5884
  toolNames: [...summary.toolNames],
5885
+ views: summary.views.map((view) => ({ ...view })),
5798
5886
  capabilities: { ...summary.capabilities }
5799
5887
  };
5800
5888
  }
5801
- async function listSandboxFiles(session, filePath) {
5802
- return normalizeFileEntries(await session.listFiles(filePath)).sort((left, right) => {
5803
- if (left.type === "directory" && right.type !== "directory") return -1;
5804
- if (left.type !== "directory" && right.type === "directory") return 1;
5805
- return left.path.localeCompare(right.path);
5806
- });
5889
+ function isLoopbackHost(host) {
5890
+ return host === "127.0.0.1" || host === "::1" || host === "localhost";
5891
+ }
5892
+ async function listSandboxFiles(inspector, filePath, abortSignal) {
5893
+ return normalizeFileEntries(await inspector.listFiles({ path: filePath, abortSignal })).sort(
5894
+ (left, right) => {
5895
+ if (left.type === "directory" && right.type !== "directory") return -1;
5896
+ if (left.type !== "directory" && right.type === "directory") return 1;
5897
+ return left.path.localeCompare(right.path);
5898
+ }
5899
+ );
5807
5900
  }
5808
5901
  function normalizeFileEntries(value) {
5809
- if (!Array.isArray(value)) {
5810
- throw new TypeError("Sandbox listFiles returned an invalid payload");
5811
- }
5902
+ if (!Array.isArray(value)) throw new TypeError("Sandbox listFiles returned an invalid payload");
5812
5903
  return value.map((item) => {
5813
- if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5904
+ if (!isRecord5(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5814
5905
  throw new TypeError("Sandbox listFiles returned an invalid entry");
5815
5906
  }
5816
5907
  let entryPath;
@@ -5821,7 +5912,7 @@ function normalizeFileEntries(value) {
5821
5912
  }
5822
5913
  const entry = { path: entryPath, type: item.type };
5823
5914
  if (item.size !== void 0) {
5824
- if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
5915
+ if (!Number.isSafeInteger(item.size) || Number(item.size) < 0) {
5825
5916
  throw new TypeError("Sandbox listFiles returned an invalid file size");
5826
5917
  }
5827
5918
  entry.size = item.size;
@@ -5830,11 +5921,10 @@ function normalizeFileEntries(value) {
5830
5921
  });
5831
5922
  }
5832
5923
  function normalizePorts(value) {
5833
- if (!Array.isArray(value)) {
5924
+ if (!Array.isArray(value))
5834
5925
  throw new TypeError("Sandbox publishedPorts returned an invalid payload");
5835
- }
5836
5926
  return value.map((item) => {
5837
- if (!isRecord4(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
5927
+ if (!isRecord5(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
5838
5928
  throw new TypeError("Sandbox publishedPorts returned an invalid entry");
5839
5929
  }
5840
5930
  return {
@@ -5846,17 +5936,16 @@ function normalizePorts(value) {
5846
5936
  });
5847
5937
  }
5848
5938
  function normalizeProcesses(value) {
5849
- if (!Array.isArray(value)) {
5939
+ if (!Array.isArray(value))
5850
5940
  throw new TypeError("Sandbox listProcesses returned an invalid payload");
5851
- }
5852
5941
  return value.map((item) => {
5853
- if (!isRecord4(item) || typeof item.id !== "string" || typeof item.command !== "string" || !Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string") || !isProcessStatus(item.status) || typeof item.startedAt !== "string") {
5942
+ if (!isRecord5(item) || typeof item.id !== "string" || typeof item.command !== "string" || !Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string") || !isProcessStatus(item.status) || typeof item.startedAt !== "string") {
5854
5943
  throw new TypeError("Sandbox listProcesses returned an invalid entry");
5855
5944
  }
5856
5945
  const process2 = {
5857
5946
  id: item.id,
5858
5947
  command: item.command,
5859
- args: item.args,
5948
+ args: [...item.args],
5860
5949
  status: item.status,
5861
5950
  startedAt: item.startedAt
5862
5951
  };
@@ -5867,11 +5956,11 @@ function normalizeProcesses(value) {
5867
5956
  });
5868
5957
  }
5869
5958
  function normalizeProcessLogs(value, maxBytes) {
5870
- if (!isRecord4(value) || typeof value.stdout !== "string" || typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
5959
+ if (!isRecord5(value) || !(value.stdout instanceof Uint8Array) || !(value.stderr instanceof Uint8Array) || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
5871
5960
  throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
5872
5961
  }
5873
- const stdout = boundedLogText(value.stdout, maxBytes);
5874
- const stderr = boundedLogText(value.stderr, maxBytes);
5962
+ const stdout = boundedLogBytes(value.stdout, maxBytes);
5963
+ const stderr = boundedLogBytes(value.stderr, maxBytes);
5875
5964
  return {
5876
5965
  stdout: stdout.text,
5877
5966
  stderr: stderr.text,
@@ -5879,21 +5968,13 @@ function normalizeProcessLogs(value, maxBytes) {
5879
5968
  stderrTruncated: value.stderrTruncated || stderr.truncated
5880
5969
  };
5881
5970
  }
5882
- function boundedLogText(value, maxBytes) {
5883
- const bytes = new TextEncoder().encode(value);
5884
- if (bytes.byteLength <= maxBytes) {
5885
- return { text: value, truncated: false };
5886
- }
5887
- if (maxBytes === 0) {
5888
- return { text: "", truncated: true };
5889
- }
5890
- let start = bytes.byteLength - maxBytes;
5891
- while (start < bytes.byteLength && (bytes[start] ?? 0) >> 6 === 2) {
5892
- start += 1;
5893
- }
5971
+ function boundedLogBytes(value, maxBytes) {
5972
+ if (maxBytes === 0) return { text: "", truncated: value.byteLength > 0 };
5973
+ let start = Math.max(0, value.byteLength - maxBytes);
5974
+ while (start < value.byteLength && ((value[start] ?? 0) & 192) === 128) start += 1;
5894
5975
  return {
5895
- text: new TextDecoder().decode(bytes.subarray(start)),
5896
- truncated: true
5976
+ text: new TextDecoder("utf-8", { fatal: true }).decode(value.subarray(start)),
5977
+ truncated: start > 0
5897
5978
  };
5898
5979
  }
5899
5980
  function normalizeSandboxPath(input, allowRoot) {
@@ -5916,9 +5997,7 @@ function normalizeSandboxPath(input, allowRoot) {
5916
5997
  return normalized;
5917
5998
  }
5918
5999
  function parseTailBytes(value) {
5919
- if (value === void 0) {
5920
- return defaultProcessLogBytes;
5921
- }
6000
+ if (value === void 0) return defaultProcessLogBytes;
5922
6001
  const parsed = Number(value);
5923
6002
  if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
5924
6003
  throw new SandboxRouteError(
@@ -5939,15 +6018,13 @@ function sandboxErrorResponse(c, error) {
5939
6018
  if (error instanceof SandboxRouteError) {
5940
6019
  return errorResponse(c, error.status, error.code, error.message);
5941
6020
  }
5942
- const name = errorName(error);
5943
- if (name === "SandboxPathError") {
5944
- return errorResponse(c, 400, "bad_request", errorMessage(error));
5945
- }
5946
- if (name === "SandboxFileSizeError") {
6021
+ const code = errorCode(error);
6022
+ if (code === "invalid_path") return errorResponse(c, 400, "bad_request", errorMessage(error));
6023
+ if (code === "file_too_large") {
5947
6024
  return errorResponse(c, 413, "payload_too_large", errorMessage(error));
5948
6025
  }
5949
- if (name === "SandboxSessionDestroyedError") {
5950
- return errorResponse(c, 409, "conflict", "Sandbox session is no longer available");
6026
+ if (code === "invalid_state" || code === "sandbox_not_found") {
6027
+ return errorResponse(c, 409, "conflict", "Sandbox is no longer available");
5951
6028
  }
5952
6029
  return errorResponse(
5953
6030
  c,
@@ -5973,6 +6050,18 @@ var SandboxRouteError = class extends Error {
5973
6050
  status;
5974
6051
  code;
5975
6052
  };
6053
+ function sortedUnique(values) {
6054
+ if (!Array.isArray(values)) throw new TypeError("Sandbox associations must be arrays.");
6055
+ const result = /* @__PURE__ */ new Set();
6056
+ for (const value of values) {
6057
+ if (typeof value !== "string" || value.length === 0) {
6058
+ throw new TypeError("Sandbox associations must contain non-empty strings.");
6059
+ }
6060
+ if (result.has(value)) throw new TypeError(`Sandbox association is duplicated: ${value}`);
6061
+ result.add(value);
6062
+ }
6063
+ return [...result].sort();
6064
+ }
5976
6065
  function isFileType(value) {
5977
6066
  return value === "file" || value === "directory" || value === "symlink" || value === "other";
5978
6067
  }
@@ -5982,14 +6071,14 @@ function isProcessStatus(value) {
5982
6071
  function isPort(value) {
5983
6072
  return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
5984
6073
  }
5985
- function isRecord4(value) {
5986
- return typeof value === "object" && value !== null;
6074
+ function isRecord5(value) {
6075
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5987
6076
  }
5988
- function errorName(error) {
5989
- return isRecord4(error) && typeof error.name === "string" ? error.name : "";
6077
+ function errorCode(error) {
6078
+ return isRecord5(error) && typeof error.code === "string" ? error.code : void 0;
5990
6079
  }
5991
6080
  function errorMessage(error) {
5992
- return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
6081
+ return isRecord5(error) && typeof error.message === "string" ? error.message : "Sandbox failed";
5993
6082
  }
5994
6083
 
5995
6084
  // src/runtime/sessions.ts
@@ -6140,6 +6229,16 @@ function createInMemoryStudioStore() {
6140
6229
  }
6141
6230
  var InMemoryStudioStore = class {
6142
6231
  kind = "memory";
6232
+ compaction = {
6233
+ snapshot: ({ scope }) => {
6234
+ const messages = this.sessions.get(scope.sessionId)?.messages ?? [];
6235
+ return Promise.resolve({
6236
+ revision: memoryRevision(messages),
6237
+ messages: cloneMessages(messages)
6238
+ });
6239
+ },
6240
+ replacePrefix: (options) => this.replaceCompactionPrefix(options)
6241
+ };
6143
6242
  sessions = /* @__PURE__ */ new Map();
6144
6243
  traces = /* @__PURE__ */ new Map();
6145
6244
  pipelineLogs = /* @__PURE__ */ new Map();
@@ -6181,20 +6280,20 @@ var InMemoryStudioStore = class {
6181
6280
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6182
6281
  return materializeSession(session);
6183
6282
  }
6184
- load(context) {
6185
- return Promise.resolve(this.sessions.get(context.sessionId)?.messages ?? []);
6283
+ load({ scope }) {
6284
+ return Promise.resolve(cloneMessages(this.sessions.get(scope.sessionId)?.messages ?? []));
6186
6285
  }
6187
6286
  append(input) {
6188
- const session = this.sessions.get(input.context.sessionId);
6287
+ const session = this.sessions.get(input.scope.sessionId);
6189
6288
  if (session !== void 0) {
6190
- session.messages.push(...input.messages);
6289
+ session.messages.push(...cloneMessages(input.messages));
6191
6290
  session.messageCount = session.messages.length;
6192
6291
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6193
6292
  }
6194
6293
  return Promise.resolve();
6195
6294
  }
6196
- clear(context) {
6197
- const session = this.sessions.get(context.sessionId);
6295
+ clear({ scope }) {
6296
+ const session = this.sessions.get(scope.sessionId);
6198
6297
  if (session !== void 0) {
6199
6298
  session.messages = [];
6200
6299
  session.runs = [];
@@ -6205,13 +6304,26 @@ var InMemoryStudioStore = class {
6205
6304
  }
6206
6305
  async recordError(input) {
6207
6306
  await this.saveSessionRunTranscript({
6208
- id: input.context.sessionId,
6209
- runId: studioRunId(input.context) ?? input.runId,
6307
+ id: input.scope.sessionId,
6308
+ runId: studioRunId(input.scope) ?? input.runId,
6210
6309
  transcript: transcriptFromMessages(input.messages),
6211
6310
  status: "error",
6212
6311
  error: serializeJsonError(input.error)
6213
6312
  });
6214
6313
  }
6314
+ replaceCompactionPrefix(input) {
6315
+ if (!Number.isSafeInteger(input.messageCount) || input.messageCount < 1) {
6316
+ throw new RangeError("messageCount must be a positive integer.");
6317
+ }
6318
+ const session = this.sessions.get(input.scope.sessionId);
6319
+ if (session === void 0 || memoryRevision(session.messages) !== input.revision || input.messageCount > session.messages.length) {
6320
+ return Promise.resolve({ status: "conflict" });
6321
+ }
6322
+ session.messages.splice(0, input.messageCount, structuredClone(input.replacement));
6323
+ session.messageCount = session.messages.length;
6324
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6325
+ return Promise.resolve({ status: "committed" });
6326
+ }
6215
6327
  saveSessionRunTranscript(input) {
6216
6328
  const session = this.sessions.get(input.id);
6217
6329
  if (session === void 0) {
@@ -6337,18 +6449,24 @@ function sessionSummary(session) {
6337
6449
  function materializeSession(session) {
6338
6450
  return {
6339
6451
  ...sessionSummary(session),
6340
- messages: [...session.messages],
6452
+ messages: cloneMessages(session.messages),
6341
6453
  transcript: renumberTranscript(session.runs.flatMap((run) => run.transcript))
6342
6454
  };
6343
6455
  }
6456
+ function cloneMessages(messages) {
6457
+ return structuredClone([...messages]);
6458
+ }
6344
6459
  function traceAgentId(trace) {
6345
6460
  const nestedMetadata = trace.metadata?.metadata;
6346
6461
  return isJsonObject(nestedMetadata) && typeof nestedMetadata.agentId === "string" ? nestedMetadata.agentId : void 0;
6347
6462
  }
6348
- function studioRunId(context) {
6349
- const value = context.metadata?.studioRunId;
6463
+ function studioRunId(scope) {
6464
+ const value = scope.metadata?.studioRunId;
6350
6465
  return typeof value === "string" && value.length > 0 ? value : void 0;
6351
6466
  }
6467
+ function memoryRevision(messages) {
6468
+ return JSON.stringify(messages);
6469
+ }
6352
6470
  function serializeJsonError(error) {
6353
6471
  if (error instanceof Error) {
6354
6472
  return {
@@ -6684,6 +6802,7 @@ var Studio = class {
6684
6802
  options;
6685
6803
  studio;
6686
6804
  server;
6805
+ websocketServer;
6687
6806
  sigintHandler;
6688
6807
  constructor(targets = [], options = {}) {
6689
6808
  this.options = studioOptionsFromTargets(targets, options);
@@ -6708,9 +6827,15 @@ var Studio = class {
6708
6827
  this.studio = createStudioApp(this.options);
6709
6828
  const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
6710
6829
  const serverOptions = {
6711
- fetch: (request) => this.fetch(request),
6830
+ fetch: (request, env) => this.studio.app.fetch(request, env),
6712
6831
  port
6713
6832
  };
6833
+ this.websocketServer = new WebSocketServer({ noServer: true });
6834
+ serverOptions.websocket = {
6835
+ // @types/ws overloads are stricter than Hono's structural adapter, while Hono officially
6836
+ // supports ws.WebSocketServer. Keep the interop assertion at this single ownership boundary.
6837
+ server: this.websocketServer
6838
+ };
6714
6839
  if (serveOptions.hostname !== void 0) serverOptions.hostname = serveOptions.hostname;
6715
6840
  this.server = serve(serverOptions);
6716
6841
  const log = serveOptions.log ?? true;
@@ -6755,6 +6880,8 @@ var Studio = class {
6755
6880
  }
6756
6881
  this.server?.close();
6757
6882
  this.server = void 0;
6883
+ this.websocketServer?.close();
6884
+ this.websocketServer = void 0;
6758
6885
  this.studio.close();
6759
6886
  }
6760
6887
  logAddress(host, port) {
@@ -6820,7 +6947,7 @@ function waitForShutdown(signal) {
6820
6947
  });
6821
6948
  }
6822
6949
  function studioOptionsFromTargets(targets, options) {
6823
- const agents = targets.filter((target) => target instanceof Agent2);
6950
+ const agents = targets.filter((target) => target instanceof Agent);
6824
6951
  const pipelines = targets.filter(
6825
6952
  // biome-ignore lint/suspicious/noExplicitAny: Studio accepts heterogeneous user pipelines.
6826
6953
  (target) => target instanceof Pipeline
@@ -6833,6 +6960,21 @@ function studioOptionsFromTargets(targets, options) {
6833
6960
  if (options.models !== void 0) runtimeOptions.models = options.models;
6834
6961
  if (options.stores !== void 0) runtimeOptions.stores = options.stores;
6835
6962
  if (options.ui !== void 0) runtimeOptions.ui = options.ui;
6963
+ if (options.sandboxes !== void 0) {
6964
+ runtimeOptions.sandboxes = options.sandboxes.map((registration) => {
6965
+ let snapshot = { inspector: registration.inspector };
6966
+ if (registration.agentIds !== void 0) {
6967
+ snapshot = { ...snapshot, agentIds: [...registration.agentIds] };
6968
+ }
6969
+ if (registration.toolNames !== void 0) {
6970
+ snapshot = { ...snapshot, toolNames: [...registration.toolNames] };
6971
+ }
6972
+ if (registration.views !== void 0) {
6973
+ snapshot = { ...snapshot, views: [...registration.views] };
6974
+ }
6975
+ return snapshot;
6976
+ });
6977
+ }
6836
6978
  return runtimeOptions;
6837
6979
  }
6838
6980
  function inferStudioAgents(agents, quickPrompts) {
@@ -6873,13 +7015,13 @@ function uniqueAgentId(baseId, ids) {
6873
7015
  }
6874
7016
  function agentMetadata(agent) {
6875
7017
  const metadata = {
6876
- staticContextCount: agent.staticContext.length,
6877
- dynamicContextCount: agent.dynamicContexts.length,
6878
- dynamicToolCount: agent.dynamicTools.length,
7018
+ staticContextCount: staticContextDocuments(agent).length,
7019
+ dynamicContextCount: vectorContexts(agent).length,
7020
+ dynamicToolCount: getAgentToolState3(agent).toolIndexes.length,
7021
+ hasLifecycle: agent.lifecycle !== void 0,
6879
7022
  hasOutputSchema: agent.outputSchema !== void 0,
6880
- hasHook: agent.hook !== void 0,
6881
- observerCount: agent.observers.length,
6882
- approvalToolCount: agent.toolSet.values().filter((tool) => tool.approval !== void 0).length
7023
+ observerCount: Object.keys(agent.observability?.observers ?? {}).length,
7024
+ approvalToolCount: agent.tools.filter(toolRequiresApproval).length
6883
7025
  };
6884
7026
  if (agent.defaultMaxTurns !== void 0) metadata.defaultMaxTurns = agent.defaultMaxTurns;
6885
7027
  return metadata;
@@ -6895,9 +7037,8 @@ function createStudioApp(options) {
6895
7037
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
6896
7038
  const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
6897
7039
  const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
6898
- const approvalRuntime = createApprovalRuntime();
6899
- const questionRuntime = createQuestionRuntime();
6900
- const sandboxRegistry = createStudioSandboxRegistry(agents);
7040
+ const continuationRegistry = createStudioContinuationRegistry();
7041
+ const sandboxRegistry = createStudioSandboxRegistry(agents, options.sandboxes ?? []);
6901
7042
  const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
6902
7043
  const app = new HonoApp();
6903
7044
  const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
@@ -6948,8 +7089,7 @@ function createStudioApp(options) {
6948
7089
  registerMcpRoutes(app, { agentMap });
6949
7090
  registerToolRoutes(app, { agentMap });
6950
7091
  registerSandboxRoutes(app, sandboxRegistry);
6951
- registerApprovalRoutes(app, approvalRuntime);
6952
- registerQuestionRoutes(app, questionRuntime);
7092
+ const closeSandboxViews = registerSandboxViewRoutes(app, sandboxRegistry);
6953
7093
  registerObservabilityRoutes(app, observabilityHub);
6954
7094
  registerEvalRoutes(app, {
6955
7095
  evals: options.evals,
@@ -6969,8 +7109,7 @@ function createStudioApp(options) {
6969
7109
  agentMap,
6970
7110
  stores,
6971
7111
  modelRegistry,
6972
- approvalRuntime,
6973
- questionRuntime
7112
+ continuationRegistry
6974
7113
  });
6975
7114
  if (memorySources.size > 0 || stores.sessions !== void 0) {
6976
7115
  registerMemoryRoutes(app, {
@@ -7002,6 +7141,8 @@ function createStudioApp(options) {
7002
7141
  return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
7003
7142
  },
7004
7143
  close() {
7144
+ continuationRegistry.clear();
7145
+ closeSandboxViews();
7005
7146
  }
7006
7147
  };
7007
7148
  if (stores.sessions !== void 0) Object.assign(studio, { sessionStore: stores.sessions });
@@ -7009,30 +7150,37 @@ function createStudioApp(options) {
7009
7150
  return studio;
7010
7151
  }
7011
7152
  function withStudioTraceObserver(studioAgent, traceStore) {
7012
- if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
7013
- return studioAgent;
7153
+ if (traceStore === void 0) return studioAgent;
7154
+ const existing = studioAgent.agent.observability;
7155
+ const studioObserver = existing?.observers.studio;
7156
+ if (studioObserver !== void 0 && !(studioObserver instanceof StudioTraceObserver)) {
7157
+ throw new TypeError('Studio reserves the observer name "studio" for local trace persistence.');
7158
+ }
7159
+ for (const [name, observer] of Object.entries(existing?.observers ?? {})) {
7160
+ if (observer instanceof StudioTraceObserver && name !== "studio") {
7161
+ throw new TypeError('StudioTraceObserver must be registered with the name "studio".');
7162
+ }
7014
7163
  }
7015
7164
  return {
7016
7165
  ...studioAgent,
7017
7166
  agent: cloneAgent(studioAgent.agent, {
7018
- observers: [
7019
- ...studioAgent.agent.observers,
7020
- { observer: new StudioTraceObserver({ store: traceStore }) }
7021
- ]
7167
+ observability: {
7168
+ observers: {
7169
+ ...existing?.observers ?? {},
7170
+ studio: studioObserver ?? new StudioTraceObserver({ store: traceStore })
7171
+ },
7172
+ primaryTrace: "studio",
7173
+ errorPolicy: existing?.errorPolicy ?? "ignore"
7174
+ }
7022
7175
  })
7023
7176
  };
7024
7177
  }
7025
- function hasStudioTraceObserver(agent) {
7026
- return agent.observers.some(
7027
- (registration) => registration.observer instanceof StudioTraceObserver
7028
- );
7029
- }
7030
7178
 
7031
7179
  // src/storage/sqlite-store.ts
7032
7180
  import { mkdirSync } from "fs";
7033
7181
  import { createRequire } from "module";
7034
7182
  import { dirname, resolve } from "path";
7035
- import { isJsonValue as isJsonValue2 } from "@anvia/core/completion";
7183
+ import { isJsonValue as isJsonValue3 } from "@anvia/core/completion";
7036
7184
  var DatabaseSync;
7037
7185
  function createSqliteSessionStore(options = {}) {
7038
7186
  return new SqliteSessionStore(options.path ?? ":memory:");
@@ -7043,6 +7191,13 @@ var SqliteSessionStore = class {
7043
7191
  }
7044
7192
  path;
7045
7193
  kind = "sqlite";
7194
+ compaction = {
7195
+ snapshot: ({ scope }) => {
7196
+ const messages = this.listSessionMessages(scope.sessionId);
7197
+ return Promise.resolve({ revision: memoryRevision2(messages), messages });
7198
+ },
7199
+ replacePrefix: (options) => this.replaceCompactionPrefix(options)
7200
+ };
7046
7201
  db;
7047
7202
  listSessions(options) {
7048
7203
  const db = this.database();
@@ -7114,8 +7269,8 @@ var SqliteSessionStore = class {
7114
7269
  }
7115
7270
  return this.getSession(id);
7116
7271
  }
7117
- load(context) {
7118
- const session = this.getSession(context.sessionId);
7272
+ load({ scope }) {
7273
+ const session = this.getSession(scope.sessionId);
7119
7274
  return Promise.resolve(session?.messages ?? []);
7120
7275
  }
7121
7276
  append(input) {
@@ -7126,20 +7281,20 @@ var SqliteSessionStore = class {
7126
7281
  `SELECT id, agent_id, title, metadata_json, created_at, updated_at
7127
7282
  FROM anvia_studio_sessions
7128
7283
  WHERE id = $id`
7129
- ).get({ $id: input.context.sessionId });
7284
+ ).get({ $id: input.scope.sessionId });
7130
7285
  if (row === void 0) {
7131
7286
  db.exec("ROLLBACK");
7132
7287
  return Promise.resolve();
7133
7288
  }
7134
7289
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7135
- const nextIndex = this.nextMessageIndex(input.context.sessionId);
7136
- this.insertMessages(input.context.sessionId, input.messages, nextIndex, updatedAt);
7290
+ const nextIndex = this.nextMessageIndex(input.scope.sessionId);
7291
+ this.insertMessages(input.scope.sessionId, input.messages, nextIndex, updatedAt);
7137
7292
  db.prepare(
7138
7293
  `UPDATE anvia_studio_sessions
7139
7294
  SET updated_at = $updatedAt
7140
7295
  WHERE id = $id`
7141
7296
  ).run({
7142
- $id: input.context.sessionId,
7297
+ $id: input.scope.sessionId,
7143
7298
  $updatedAt: updatedAt
7144
7299
  });
7145
7300
  db.exec("COMMIT");
@@ -7151,7 +7306,7 @@ var SqliteSessionStore = class {
7151
7306
  throw error;
7152
7307
  }
7153
7308
  }
7154
- clear(context) {
7309
+ clear({ scope }) {
7155
7310
  const db = this.database();
7156
7311
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7157
7312
  try {
@@ -7161,14 +7316,14 @@ var SqliteSessionStore = class {
7161
7316
  SET updated_at = $updatedAt
7162
7317
  WHERE id = $id`
7163
7318
  ).run({
7164
- $id: context.sessionId,
7319
+ $id: scope.sessionId,
7165
7320
  $updatedAt: updatedAt
7166
7321
  });
7167
7322
  db.prepare("DELETE FROM anvia_studio_session_messages WHERE session_id = $id").run({
7168
- $id: context.sessionId
7323
+ $id: scope.sessionId
7169
7324
  });
7170
7325
  db.prepare("DELETE FROM anvia_studio_session_runs WHERE session_id = $id").run({
7171
- $id: context.sessionId
7326
+ $id: scope.sessionId
7172
7327
  });
7173
7328
  db.exec("COMMIT");
7174
7329
  return Promise.resolve();
@@ -7180,17 +7335,51 @@ var SqliteSessionStore = class {
7180
7335
  }
7181
7336
  }
7182
7337
  async recordError(input) {
7183
- const runId = studioRunId2(input.context) ?? input.runId;
7184
- const existing = this.getSessionRun(input.context.sessionId, runId);
7338
+ const runId = studioRunId2(input.scope) ?? input.runId;
7339
+ const existing = this.getSessionRun(input.scope.sessionId, runId);
7185
7340
  const transcript = existing === void 0 || parseJsonArray(existing.transcript_json).length === 0 ? transcriptFromMessages(input.messages) : parseJsonArray(existing.transcript_json);
7186
7341
  await this.saveSessionRunTranscript({
7187
- id: input.context.sessionId,
7342
+ id: input.scope.sessionId,
7188
7343
  runId,
7189
7344
  transcript,
7190
7345
  status: "error",
7191
7346
  error: serializeJsonError2(input.error)
7192
7347
  });
7193
7348
  }
7349
+ replaceCompactionPrefix(input) {
7350
+ if (!Number.isSafeInteger(input.messageCount) || input.messageCount < 1) {
7351
+ throw new RangeError("messageCount must be a positive integer.");
7352
+ }
7353
+ const db = this.database();
7354
+ try {
7355
+ db.exec("BEGIN IMMEDIATE");
7356
+ const messages = this.listSessionMessages(input.scope.sessionId);
7357
+ if (memoryRevision2(messages) !== input.revision || input.messageCount > messages.length || this.getSessionRow(input.scope.sessionId) === void 0) {
7358
+ db.exec("ROLLBACK");
7359
+ return Promise.resolve({ status: "conflict" });
7360
+ }
7361
+ db.prepare("DELETE FROM anvia_studio_session_messages WHERE session_id = $id").run({
7362
+ $id: input.scope.sessionId
7363
+ });
7364
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7365
+ this.insertMessages(
7366
+ input.scope.sessionId,
7367
+ [input.replacement, ...messages.slice(input.messageCount)],
7368
+ 0,
7369
+ updatedAt
7370
+ );
7371
+ db.prepare(
7372
+ `UPDATE anvia_studio_sessions
7373
+ SET updated_at = $updatedAt
7374
+ WHERE id = $id`
7375
+ ).run({ $id: input.scope.sessionId, $updatedAt: updatedAt });
7376
+ db.exec("COMMIT");
7377
+ return Promise.resolve({ status: "committed" });
7378
+ } catch (error) {
7379
+ if (db.isTransaction) db.exec("ROLLBACK");
7380
+ throw error;
7381
+ }
7382
+ }
7194
7383
  saveSessionRunTranscript(input) {
7195
7384
  const db = this.database();
7196
7385
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7886,7 +8075,7 @@ var SqliteSessionStore = class {
7886
8075
  );
7887
8076
  messages.forEach((message, messageOffset) => {
7888
8077
  const messageIndex = startIndex + messageOffset;
7889
- if (message.metadata !== void 0 && !isJsonValue2(message.metadata)) {
8078
+ if (message.metadata !== void 0 && !isJsonValue3(message.metadata)) {
7890
8079
  throw new TypeError("Studio message metadata must be a strict JSON value.");
7891
8080
  }
7892
8081
  insertMessage.run({
@@ -7987,6 +8176,9 @@ function messageParts(message) {
7987
8176
  if (message.role === "system") {
7988
8177
  return [{ type: "text", value: { type: "text", text: message.content } }];
7989
8178
  }
8179
+ if (typeof message.content === "string") {
8180
+ return [{ type: "text", value: { type: "text", text: message.content } }];
8181
+ }
7990
8182
  return message.content.map((content) => ({
7991
8183
  type: content.type,
7992
8184
  value: content
@@ -7995,40 +8187,40 @@ function messageParts(message) {
7995
8187
  function messageFromRows(row, partRows) {
7996
8188
  const parts = partRows.map((partRow) => JSON.parse(partRow.part_json));
7997
8189
  const metadata = parseJsonValue(row.metadata_json);
7998
- if (metadata !== void 0 && !isJsonValue2(metadata)) {
7999
- throw new TypeError("Stored Studio message metadata is not a strict JSON value.");
8190
+ if (metadata !== void 0 && (!isJsonValue3(metadata) || typeof metadata !== "object" || metadata === null || Array.isArray(metadata))) {
8191
+ throw new TypeError("Stored Studio message metadata is not a strict JSON object.");
8000
8192
  }
8001
8193
  if (row.role === "system") {
8002
- const message = {
8194
+ let message = {
8003
8195
  role: "system",
8004
8196
  content: systemContentFromParts(parts)
8005
8197
  };
8006
- if (metadata !== void 0) message.metadata = metadata;
8198
+ if (metadata !== void 0) message = { ...message, metadata };
8007
8199
  return message;
8008
8200
  }
8009
8201
  if (row.role === "user") {
8010
- const message = {
8202
+ let message = {
8011
8203
  role: "user",
8012
8204
  content: parts
8013
8205
  };
8014
- if (metadata !== void 0) message.metadata = metadata;
8206
+ if (metadata !== void 0) message = { ...message, metadata };
8015
8207
  return message;
8016
8208
  }
8017
8209
  if (row.role === "assistant") {
8018
- const message = {
8210
+ let message = {
8019
8211
  role: "assistant",
8020
8212
  content: parts
8021
8213
  };
8022
- if (row.message_id !== null) message.id = row.message_id;
8023
- if (metadata !== void 0) message.metadata = metadata;
8214
+ if (row.message_id !== null) message = { ...message, id: row.message_id };
8215
+ if (metadata !== void 0) message = { ...message, metadata };
8024
8216
  return message;
8025
8217
  }
8026
8218
  if (row.role === "tool") {
8027
- const message = {
8219
+ let message = {
8028
8220
  role: "tool",
8029
8221
  content: parts
8030
8222
  };
8031
- if (metadata !== void 0) message.metadata = metadata;
8223
+ if (metadata !== void 0) message = { ...message, metadata };
8032
8224
  return message;
8033
8225
  }
8034
8226
  throw new Error(`Unsupported stored message role: ${row.role}`);
@@ -8100,6 +8292,7 @@ function toTraceSummary(row) {
8100
8292
  if (error !== void 0) summary.error = error;
8101
8293
  if (usage !== void 0) summary.usage = usage;
8102
8294
  if (metadata !== void 0) summary.metadata = metadata;
8295
+ if (typeof metadata?.runId === "string") summary.runId = metadata.runId;
8103
8296
  return summary;
8104
8297
  }
8105
8298
  function parseJsonArray(value) {
@@ -8112,10 +8305,13 @@ function parseJsonValue(value) {
8112
8305
  }
8113
8306
  return JSON.parse(value);
8114
8307
  }
8115
- function studioRunId2(context) {
8116
- const value = context.metadata?.studioRunId;
8308
+ function studioRunId2(scope) {
8309
+ const value = scope.metadata?.studioRunId;
8117
8310
  return typeof value === "string" && value.length > 0 ? value : void 0;
8118
8311
  }
8312
+ function memoryRevision2(messages) {
8313
+ return JSON.stringify(messages);
8314
+ }
8119
8315
  function serializeJsonError2(error) {
8120
8316
  if (error instanceof Error) {
8121
8317
  return {