@anvia/studio 1.0.0-rc.1 → 1.0.0-rc.3

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 (97) hide show
  1. package/README.md +56 -10
  2. package/dist/index.d.ts +176 -116
  3. package/dist/index.js +1408 -1216
  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-yNj_oSyD.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-ucKNS0fS.js.map → agents-page-DQqt45If.js.map} +1 -1
  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-D2ECOWN5-fs3ih0yA.js +65 -0
  20. package/dist/ui/assets/chunk-D2ECOWN5-fs3ih0yA.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-BGNC3dLD.css +1 -0
  32. package/dist/ui/assets/index-BLaXiUWO.js +12 -0
  33. package/dist/ui/assets/index-BLaXiUWO.js.map +1 -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-DzRhA9qQ.js +2 -0
  39. package/dist/ui/assets/{knowledge-page-vwFOGhkh.js.map → knowledge-page-DzRhA9qQ.js.map} +1 -1
  40. package/dist/ui/assets/mcps-page-ZVngljXI.js +2 -0
  41. package/dist/ui/assets/{mcps-page-Bsu_-w7A.js.map → mcps-page-ZVngljXI.js.map} +1 -1
  42. package/dist/ui/assets/{memory-page-DJ_nq7Ip.js → memory-page-DAJk0Zof.js} +4 -4
  43. package/dist/ui/assets/memory-page-DAJk0Zof.js.map +1 -0
  44. package/dist/ui/assets/pipelines-page-8osYA129.js +8 -0
  45. package/dist/ui/assets/pipelines-page-8osYA129.js.map +1 -0
  46. package/dist/ui/assets/{renderers-5kT5HxmZ.js → renderers-BPGo4d2i.js} +3 -3
  47. package/dist/ui/assets/{renderers-5kT5HxmZ.js.map → renderers-BPGo4d2i.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-BLGrHrQT.js.map → sessions-page-ClXzRM9K.js.map} +1 -1
  58. package/dist/ui/assets/status-page-DfuQ0fpa.js +2 -0
  59. package/dist/ui/assets/{status-page-D-BUKV_r.js.map → status-page-DfuQ0fpa.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-Cdrcs2v4.js +2 -0
  65. package/dist/ui/assets/{tool-runner-6-N-OOCI.js.map → tool-runner-Cdrcs2v4.js.map} +1 -1
  66. package/dist/ui/assets/tools-page-C865VQmM.js +2 -0
  67. package/dist/ui/assets/{tools-page-BEQJgFX0.js.map → tools-page-C865VQmM.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-BadzBIzE.js +3 -0
  71. package/dist/ui/assets/transcript-item-BadzBIzE.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-yNj_oSyD.js +0 -2
  77. package/dist/ui/assets/agents-page-ucKNS0fS.js +0 -2
  78. package/dist/ui/assets/chunk-ADH7NNCS-DM6fsnDI.js +0 -2
  79. package/dist/ui/assets/chunk-ADH7NNCS-DM6fsnDI.js.map +0 -1
  80. package/dist/ui/assets/index-D-CbFx6c.css +0 -1
  81. package/dist/ui/assets/index-DIV9J1_T.js +0 -56
  82. package/dist/ui/assets/index-DIV9J1_T.js.map +0 -1
  83. package/dist/ui/assets/knowledge-page-vwFOGhkh.js +0 -2
  84. package/dist/ui/assets/mcps-page-Bsu_-w7A.js +0 -2
  85. package/dist/ui/assets/memory-page-DJ_nq7Ip.js.map +0 -1
  86. package/dist/ui/assets/pipelines-page-BsHeSdbO.js +0 -8
  87. package/dist/ui/assets/pipelines-page-BsHeSdbO.js.map +0 -1
  88. package/dist/ui/assets/sandboxes-page-BQuQepXk.js +0 -2
  89. package/dist/ui/assets/sandboxes-page-BQuQepXk.js.map +0 -1
  90. package/dist/ui/assets/sessions-page-BLGrHrQT.js +0 -2
  91. package/dist/ui/assets/status-page-D-BUKV_r.js +0 -2
  92. package/dist/ui/assets/tool-runner-6-N-OOCI.js +0 -2
  93. package/dist/ui/assets/tools-page-BEQJgFX0.js +0 -2
  94. package/dist/ui/assets/trace-browser-DdGcJyNX.js +0 -10
  95. package/dist/ui/assets/trace-browser-DdGcJyNX.js.map +0 -1
  96. package/dist/ui/assets/transcript-item-Bbf77TZa.js +0 -3
  97. package/dist/ui/assets/transcript-item-Bbf77TZa.js.map +0 -1
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { Agent, getAgentToolState as getAgentToolState3 } from "@anvia/core/inte
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,12 +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,
203
233
  runId: this.props.args.runId,
204
234
  sessionId,
205
235
  status,
206
- trace: this.trace,
236
+ trace: { observer: "studio", ...this.trace },
207
237
  startedAt: this.startedAt.toISOString(),
208
238
  endedAt: result.endedAt.toISOString(),
209
239
  durationMs: durationMs(this.startedAt, result.endedAt),
@@ -282,15 +312,14 @@ var ChildAgentToolTraceAccumulator = class {
282
312
  }
283
313
  if (child.type === "tool_call" && isRecord(child.toolCall)) {
284
314
  const toolCall = child.toolCall;
285
- const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
286
- const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
287
- 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;
288
317
  const start = {
289
318
  startedAt: /* @__PURE__ */ new Date(),
290
319
  agentId,
291
320
  childTurn,
292
321
  toolName,
293
- input: toJsonValue(toolCallFunction?.arguments ?? {}),
322
+ input: toJsonValue("input" in toolCall ? toolCall.input : {}),
294
323
  completed: false
295
324
  };
296
325
  if (agentName !== void 0) start.agentName = agentName;
@@ -443,14 +472,12 @@ function generationMetadata(args, endArgs) {
443
472
  const request = args.request;
444
473
  const response = endArgs?.response;
445
474
  const rawResponse = isRecord(response?.rawResponse) ? response.rawResponse : void 0;
446
- const effectiveModel = request.model ?? stringValue(rawResponse?.model) ?? args.modelInfo?.defaultModel ?? "default";
475
+ const modelId = args.modelInfo?.modelId ?? stringValue(rawResponse?.model) ?? "unknown";
447
476
  const providerResponse = providerResponseSummary(rawResponse);
448
477
  const usage = response?.usage;
449
478
  return compactJsonObject({
450
479
  provider: args.modelInfo?.provider,
451
- model: effectiveModel,
452
- requestedModel: request.model,
453
- defaultModel: args.modelInfo?.defaultModel,
480
+ modelId,
454
481
  messageId: response?.messageId,
455
482
  usage,
456
483
  toolCount: request.tools.length,
@@ -460,15 +487,13 @@ function generationMetadata(args, endArgs) {
460
487
  temperature: request.temperature,
461
488
  maxTokens: request.maxTokens,
462
489
  toolChoice: request.toolChoice,
463
- additionalParamKeys: isRecord(request.additionalParams) ? Object.keys(request.additionalParams).sort() : void 0,
490
+ providerOptionKeys: isRecord(request.providerOptions) ? Object.keys(request.providerOptions).sort() : void 0,
464
491
  hasOutputSchema: request.outputSchema !== void 0,
465
492
  firstDeltaMs: endArgs?.firstDeltaMs,
466
493
  providerResponse,
467
494
  modelInfo: compactJsonObject({
468
495
  provider: args.modelInfo?.provider,
469
- model: effectiveModel,
470
- requestedModel: request.model,
471
- defaultModel: args.modelInfo?.defaultModel,
496
+ modelId,
472
497
  capabilities: args.modelInfo?.capabilities
473
498
  }),
474
499
  modelCall: compactJsonObject({
@@ -494,7 +519,6 @@ function generationMetadata(args, endArgs) {
494
519
  }
495
520
  function completionRequestSummary(request) {
496
521
  return compactJsonObject({
497
- model: request.model,
498
522
  instructions: request.instructions === void 0 ? void 0 : { present: true },
499
523
  messageCount: request.chatHistory.length,
500
524
  documentCount: request.documents.length,
@@ -504,7 +528,7 @@ function completionRequestSummary(request) {
504
528
  temperature: request.temperature,
505
529
  maxTokens: request.maxTokens,
506
530
  toolChoice: request.toolChoice,
507
- additionalParamKeys: isRecord(request.additionalParams) ? Object.keys(request.additionalParams).sort() : void 0,
531
+ providerOptionKeys: isRecord(request.providerOptions) ? Object.keys(request.providerOptions).sort() : void 0,
508
532
  hasOutputSchema: request.outputSchema !== void 0
509
533
  });
510
534
  }
@@ -541,7 +565,6 @@ function toolMetadata(args, skipped, result) {
541
565
  argumentBytes: byteLength(args.args),
542
566
  resultBytes: result === void 0 ? void 0 : byteLength(result),
543
567
  hasCallSignature: args.toolCall.signature !== void 0,
544
- hasAdditionalParams: args.toolCall.additionalParams !== void 0,
545
568
  toolDescription: args.toolDefinition?.description,
546
569
  parameterKeys: Object.keys(properties).sort(),
547
570
  requiredParameterKeys: required,
@@ -559,8 +582,7 @@ function toolMetadata(args, skipped, result) {
559
582
  mcpServerName: args.toolMetadata?.mcpServerName,
560
583
  argumentBytes: byteLength(args.args),
561
584
  resultBytes: result === void 0 ? void 0 : byteLength(result),
562
- hasCallSignature: args.toolCall.signature !== void 0,
563
- hasAdditionalParams: args.toolCall.additionalParams !== void 0
585
+ hasCallSignature: args.toolCall.signature !== void 0
564
586
  })
565
587
  });
566
588
  }
@@ -577,6 +599,16 @@ function parseOrString(value) {
577
599
  return value;
578
600
  }
579
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
+ }
580
612
  function isRecord(value) {
581
613
  return typeof value === "object" && value !== null && !Array.isArray(value);
582
614
  }
@@ -877,17 +909,18 @@ function escapeHtml(value) {
877
909
  }
878
910
 
879
911
  // src/runtime/agent-context.ts
880
- import { isContextIndex } from "@anvia/core/agent";
912
+ import { isVectorContext } from "@anvia/core/agent";
881
913
  function staticContextDocuments(agent) {
882
- return agent.context.filter((input) => !isContextIndex(input));
914
+ return agent.context.filter((input) => !isVectorContext(input));
883
915
  }
884
- function contextIndexes(agent) {
885
- return agent.context.filter(isContextIndex);
916
+ function vectorContexts(agent) {
917
+ return agent.context.filter(isVectorContext);
886
918
  }
887
919
 
888
920
  // src/runtime/agent-runs.ts
889
- import { Message } from "@anvia/core/completion";
890
- import { withInternalAgentRunOptions } from "@anvia/core/internal/agent";
921
+ import {
922
+ withInternalAgentRunOptions
923
+ } from "@anvia/core/internal/agent";
891
924
 
892
925
  // src/runtime/agent-utils.ts
893
926
  import {
@@ -914,7 +947,8 @@ function serializeError(error) {
914
947
 
915
948
  // src/runtime/type-guards.ts
916
949
  import {
917
- isJsonValue as isCoreJsonValue
950
+ isJsonValue as isCoreJsonValue,
951
+ isMessage
918
952
  } from "@anvia/core/completion";
919
953
  function isObject(value) {
920
954
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -925,26 +959,16 @@ function isJsonObject(value) {
925
959
  function isJsonValue(value) {
926
960
  return isCoreJsonValue(value);
927
961
  }
928
- function isMessageInput(value) {
929
- return typeof value === "string" || isMessage(value);
930
- }
931
- function isMessage(value) {
932
- if (!isObject(value) || typeof value.role !== "string") {
933
- return false;
934
- }
935
- if (value.role === "system") {
936
- return typeof value.content === "string";
937
- }
938
- if (value.role === "user" || value.role === "assistant" || value.role === "tool") {
939
- return Array.isArray(value.content);
940
- }
941
- return false;
942
- }
943
962
  function isAgentTraceOptions(value) {
944
963
  if (!isObject(value)) {
945
964
  return false;
946
965
  }
947
- 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));
948
972
  }
949
973
  function isNonNegativeInteger(value) {
950
974
  return Number.isInteger(value) && typeof value === "number" && value >= 0;
@@ -955,15 +979,9 @@ function isPositiveInteger(value) {
955
979
  function optionalString(value) {
956
980
  return value === void 0 || typeof value === "string";
957
981
  }
958
- function optionalBoolean(value) {
959
- return value === void 0 || typeof value === "boolean";
960
- }
961
982
  function optionalStringArray(value) {
962
983
  return value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
963
984
  }
964
- function optionalObject(value) {
965
- return value === void 0 || isObject(value);
966
- }
967
985
 
968
986
  // src/runtime/http.ts
969
987
  function errorResponse(c, status, code, message, details) {
@@ -1013,8 +1031,8 @@ function createStudioModelRegistry(config) {
1013
1031
  id,
1014
1032
  staticModels
1015
1033
  };
1016
- if (provider.defaultModel !== void 0) {
1017
- runtimeProvider.defaultModel = normalizeModelId(provider.defaultModel);
1034
+ if (provider.defaultModelId !== void 0) {
1035
+ runtimeProvider.defaultModelId = normalizeModelId(provider.defaultModelId);
1018
1036
  }
1019
1037
  providers.set(id, runtimeProvider);
1020
1038
  }
@@ -1023,8 +1041,10 @@ function createStudioModelRegistry(config) {
1023
1041
  agentPolicies: normalizeAgentPolicies(config.agents ?? {}),
1024
1042
  modelCache: /* @__PURE__ */ new Map()
1025
1043
  };
1026
- if (config.default !== void 0) {
1027
- Object.assign(registry, { defaultModel: normalizeModelRef(config.default) });
1044
+ if (config.defaultModelRef !== void 0) {
1045
+ Object.assign(registry, {
1046
+ defaultModelRef: normalizeModelRef(config.defaultModelRef)
1047
+ });
1028
1048
  }
1029
1049
  return registry;
1030
1050
  }
@@ -1041,7 +1061,7 @@ function studioModelsConfig(registry, agents) {
1041
1061
  models
1042
1062
  };
1043
1063
  if (provider.name !== void 0) config2.name = provider.name;
1044
- if (provider.defaultModel !== void 0) config2.defaultModel = provider.defaultModel;
1064
+ if (provider.defaultModelId !== void 0) config2.defaultModelId = provider.defaultModelId;
1045
1065
  if (provider.metadata !== void 0) config2.metadata = provider.metadata;
1046
1066
  return config2;
1047
1067
  });
@@ -1053,7 +1073,7 @@ function studioModelsConfig(registry, agents) {
1053
1073
  providers,
1054
1074
  agents: agentsConfig
1055
1075
  };
1056
- if (registry.defaultModel !== void 0) config.default = registry.defaultModel;
1076
+ if (registry.defaultModelRef !== void 0) config.defaultModelRef = registry.defaultModelRef;
1057
1077
  return config;
1058
1078
  }
1059
1079
  function registerModelRoutes(app, props) {
@@ -1067,8 +1087,8 @@ function registerModelRoutes(app, props) {
1067
1087
  const response = {
1068
1088
  providers
1069
1089
  };
1070
- if (props.registry.defaultModel !== void 0) {
1071
- response.defaultModel = props.registry.defaultModel;
1090
+ if (props.registry.defaultModelRef !== void 0) {
1091
+ response.defaultModelRef = props.registry.defaultModelRef;
1072
1092
  }
1073
1093
  return c.json(response);
1074
1094
  });
@@ -1101,7 +1121,7 @@ function resolveStudioModel(registry, input) {
1101
1121
  if (registry === void 0) {
1102
1122
  return { warnings: [] };
1103
1123
  }
1104
- 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;
1105
1125
  if (selectedRef === void 0) {
1106
1126
  return { warnings: [] };
1107
1127
  }
@@ -1113,7 +1133,7 @@ function resolveStudioModel(registry, input) {
1113
1133
  }
1114
1134
  let model = registry.modelCache.get(selectedRef);
1115
1135
  if (model === void 0) {
1116
- model = provider.createCompletionModel(modelId);
1136
+ model = provider.createCompletionModel({ modelId });
1117
1137
  registry.modelCache.set(selectedRef, model);
1118
1138
  }
1119
1139
  const metadata = provider.staticModels.get(modelId);
@@ -1131,12 +1151,7 @@ function normalizeOptionalModelRef(ref) {
1131
1151
  return ref === void 0 ? void 0 : normalizeModelRef(ref);
1132
1152
  }
1133
1153
  function normalizeModelRef(ref) {
1134
- if (typeof ref === "string") {
1135
- const trimmed = ref.trim();
1136
- const parsed = parseModelRef(trimmed);
1137
- return `${parsed.providerId}:${parsed.modelId}`;
1138
- }
1139
- return `${normalizeProviderId(ref.provider)}:${normalizeModelId(ref.model)}`;
1154
+ return `${normalizeProviderId(ref.providerId)}:${normalizeModelId(ref.modelId)}`;
1140
1155
  }
1141
1156
  function parseModelRef(ref) {
1142
1157
  const index = ref.indexOf(":");
@@ -1179,12 +1194,12 @@ async function agentModelsCatalog(registry, agent) {
1179
1194
  return [];
1180
1195
  }
1181
1196
  });
1182
- const defaultModel = policy?.default ?? registry.defaultModel;
1197
+ const defaultModelRef = policy?.defaultModelRef ?? registry.defaultModelRef;
1183
1198
  const summary = {
1184
1199
  agentId: agent.id,
1185
1200
  models: [...models, ...exactPolicyModels]
1186
1201
  };
1187
- if (defaultModel !== void 0) summary.defaultModel = defaultModel;
1202
+ if (defaultModelRef !== void 0) summary.defaultModelRef = defaultModelRef;
1188
1203
  if (warnings.length > 0) summary.warnings = warnings;
1189
1204
  return summary;
1190
1205
  }
@@ -1222,7 +1237,7 @@ async function providerCatalog(provider) {
1222
1237
  models: [...models.values()].sort((left, right) => left.ref.localeCompare(right.ref))
1223
1238
  };
1224
1239
  if (provider.name !== void 0) catalog.name = provider.name;
1225
- if (provider.defaultModel !== void 0) catalog.defaultModel = provider.defaultModel;
1240
+ if (provider.defaultModelId !== void 0) catalog.defaultModelId = provider.defaultModelId;
1226
1241
  if (provider.metadata !== void 0) catalog.metadata = provider.metadata;
1227
1242
  if (warning !== void 0) catalog.warning = warning;
1228
1243
  return catalog;
@@ -1256,19 +1271,33 @@ function normalizeAgentPolicies(policies) {
1256
1271
  return new Map(
1257
1272
  Object.entries(policies).map(([agentId, policy]) => {
1258
1273
  const normalized = {};
1259
- if (policy.default !== void 0) normalized.default = normalizeModelRef(policy.default);
1274
+ if (policy.defaultModelRef !== void 0) {
1275
+ normalized.defaultModelRef = normalizeModelRef(policy.defaultModelRef);
1276
+ }
1260
1277
  if (policy.allowed !== void 0) {
1261
- normalized.allowed = policy.allowed.map(
1262
- (entry) => typeof entry === "string" && entry.trim().endsWith(":*") ? `${normalizeProviderId(entry.trim().slice(0, -2))}:*` : normalizeModelRef(entry)
1263
- );
1278
+ normalized.allowed = policy.allowed.map((entry) => {
1279
+ if (typeof entry === "string") {
1280
+ return normalizeWildcardModelRef(entry);
1281
+ }
1282
+ return normalizeModelRef(entry);
1283
+ });
1264
1284
  }
1265
1285
  return [agentId.trim(), normalized];
1266
1286
  })
1267
1287
  );
1268
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
+ }
1269
1296
  function publicPolicy(policy) {
1270
1297
  const config = {};
1271
- if (policy.default !== void 0) config.default = policy.default;
1298
+ if (policy.defaultModelRef !== void 0) {
1299
+ config.defaultModelRef = policy.defaultModelRef;
1300
+ }
1272
1301
  if (policy.allowed !== void 0) config.allowed = policy.allowed;
1273
1302
  return config;
1274
1303
  }
@@ -1299,15 +1328,13 @@ function modelWarnings(ref, model, request) {
1299
1328
  function requestModalities(request) {
1300
1329
  const modalities = /* @__PURE__ */ new Set(["text"]);
1301
1330
  for (const message of requestMessages(request)) {
1302
- if (typeof message === "string") {
1303
- continue;
1304
- }
1305
1331
  if (message.role === "user" || message.role === "assistant") {
1332
+ if (typeof message.content === "string") continue;
1306
1333
  for (const content of message.content) {
1307
1334
  if (content.type === "image") {
1308
1335
  modalities.add("image");
1309
1336
  }
1310
- if (content.type === "document") {
1337
+ if (content.type === "file") {
1311
1338
  modalities.add("document");
1312
1339
  }
1313
1340
  }
@@ -1316,7 +1343,7 @@ function requestModalities(request) {
1316
1343
  return modalities;
1317
1344
  }
1318
1345
  function requestMessages(request) {
1319
- return [...request.history ?? [], request.message];
1346
+ return request.type === "messages" ? request.messages : [];
1320
1347
  }
1321
1348
  function normalizeProviderId(id) {
1322
1349
  const trimmed = id.trim();
@@ -1334,7 +1361,7 @@ function normalizeModelId(id) {
1334
1361
  }
1335
1362
 
1336
1363
  // src/runtime/streams.ts
1337
- import { createEventStream } from "@anvia/server";
1364
+ import { createClientStreamResponse, createEventStreamResponse } from "@anvia/server";
1338
1365
  var studioJsonlHeaders = {
1339
1366
  "cache-control": "no-cache, no-transform",
1340
1367
  connection: "keep-alive",
@@ -1343,7 +1370,15 @@ var studioJsonlHeaders = {
1343
1370
  "x-accel-buffering": "no"
1344
1371
  };
1345
1372
  function streamStudioJsonl(events) {
1346
- 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,
1347
1382
  format: "jsonl",
1348
1383
  headers: studioJsonlHeaders
1349
1384
  });
@@ -1362,8 +1397,9 @@ function withStudioStreamErrors(events) {
1362
1397
  const next = await iterator.next();
1363
1398
  if (next.done === true) {
1364
1399
  done = true;
1400
+ return next;
1365
1401
  }
1366
- return next;
1402
+ return { done: false, value: serializeStreamError(next.value) };
1367
1403
  } catch (error) {
1368
1404
  done = true;
1369
1405
  return {
@@ -1381,6 +1417,12 @@ function withStudioStreamErrors(events) {
1381
1417
  }
1382
1418
  };
1383
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
+ }
1384
1426
  function studioStreamError(error) {
1385
1427
  return {
1386
1428
  type: "error",
@@ -1589,6 +1631,16 @@ function isEventType(value) {
1589
1631
  return value === "session_log" || value === "pipeline_log" || value === "trace";
1590
1632
  }
1591
1633
 
1634
+ // src/runtime/runs.ts
1635
+ import {
1636
+ customAgentEventsToClientStream
1637
+ } from "@anvia/client";
1638
+ import { parseAgentInteractionResponse } from "@anvia/core/agent";
1639
+ import {
1640
+ isJsonValue as isJsonValue2,
1641
+ parseMessages
1642
+ } from "@anvia/core/completion";
1643
+
1592
1644
  // src/runtime/transcript.ts
1593
1645
  function renumberTranscript(entries) {
1594
1646
  return entries.map((entry, entryId) => ({ ...entry, entryId }));
@@ -1602,7 +1654,8 @@ function transcriptFromMessages(messages) {
1602
1654
  if (message.role === "user") {
1603
1655
  const attachments = attachmentsFromMessage(message);
1604
1656
  let textEntryAdded = false;
1605
- 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) {
1606
1659
  if (content.type === "text") {
1607
1660
  const entry = {
1608
1661
  entryId: transcript.length,
@@ -1616,32 +1669,36 @@ function transcriptFromMessages(messages) {
1616
1669
  }
1617
1670
  }
1618
1671
  if (!textEntryAdded && attachments.length > 0) {
1619
- transcript.push({
1672
+ const entry = {
1620
1673
  entryId: transcript.length,
1621
1674
  kind: "message",
1622
1675
  role: "user",
1623
1676
  text: "",
1624
1677
  attachments
1625
- });
1678
+ };
1679
+ transcript.push(entry);
1626
1680
  }
1627
1681
  continue;
1628
1682
  }
1629
1683
  if (message.role === "tool") {
1630
1684
  for (const content of message.content) {
1631
- transcript.push({
1685
+ if (content.type !== "tool-result") {
1686
+ continue;
1687
+ }
1688
+ const entry = {
1632
1689
  entryId: transcript.length,
1633
1690
  kind: "tool",
1634
- toolName: "tool_result",
1635
- callId: content.callId ?? content.id,
1636
- result: content.content.map(
1637
- (item) => "text" in item ? item.text : `[image:${item.mediaType ?? "image/png"}]`
1638
- ).join("\n"),
1639
- structuredResult: content.content
1640
- });
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);
1641
1697
  }
1642
1698
  continue;
1643
1699
  }
1644
- 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) {
1645
1702
  if (content.type === "text") {
1646
1703
  appendAssistantTranscriptText(transcript, content.text);
1647
1704
  } else if (content.type === "reasoning") {
@@ -1652,13 +1709,13 @@ function transcriptFromMessages(messages) {
1652
1709
  };
1653
1710
  if (content.id !== void 0) entry.reasoningId = content.id;
1654
1711
  transcript.push(entry);
1655
- } else if (content.type === "tool_call") {
1712
+ } else if (content.type === "tool-call") {
1656
1713
  transcript.push({
1657
1714
  entryId: transcript.length,
1658
1715
  kind: "tool",
1659
- toolName: content.function.name,
1660
- callId: content.callId ?? content.id,
1661
- args: formatJson(content.function.arguments)
1716
+ toolName: content.toolName,
1717
+ callId: content.callId ?? content.toolCallId,
1718
+ args: formatJson(content.input)
1662
1719
  });
1663
1720
  }
1664
1721
  }
@@ -1669,28 +1726,37 @@ function attachmentsFromMessage(message) {
1669
1726
  if (message.role !== "user" && message.role !== "assistant") {
1670
1727
  return [];
1671
1728
  }
1729
+ if (typeof message.content === "string") return [];
1672
1730
  return message.content.flatMap((content) => {
1673
1731
  if (content.type === "image") {
1674
1732
  const attachment = { kind: "image" };
1675
- if (content.source.type === "base64") {
1676
- attachment.data = content.source.data;
1677
- 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;
1678
1736
  } else {
1679
- attachment.url = content.source.url;
1737
+ attachment.url = content.image.url;
1680
1738
  }
1681
1739
  return [attachment];
1682
1740
  }
1683
- if (content.type === "document") {
1741
+ if (content.type === "file") {
1684
1742
  const attachment = { kind: "document" };
1685
- if (content.source.filename !== void 0) attachment.name = content.source.filename;
1686
- if (content.source.mediaType !== void 0) attachment.mediaType = content.source.mediaType;
1687
- if (content.source.type === "base64") attachment.data = content.source.data;
1688
- 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;
1689
1747
  return [attachment];
1690
1748
  }
1691
1749
  return [];
1692
1750
  });
1693
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
+ }
1694
1760
  function appendAssistantTranscriptText(transcript, text) {
1695
1761
  const last = transcript.at(-1);
1696
1762
  if (last?.kind === "message" && last.role === "assistant") {
@@ -1738,48 +1804,43 @@ var AsyncEventQueue = class {
1738
1804
  return new Promise((resolve2) => this.resolvers.push(resolve2));
1739
1805
  }
1740
1806
  };
1741
- async function* mergeRunAndApprovalEvents(runEvents, approvalEvents) {
1742
- const runIterator = runEvents[Symbol.asyncIterator]();
1743
- let runDone = false;
1744
- let runNext = runIterator.next();
1745
- let approvalNext = approvalEvents.next();
1746
- try {
1747
- while (runNext !== void 0 || approvalNext !== void 0) {
1748
- const pending = [];
1749
- if (runNext !== void 0) {
1750
- pending.push(runNext.then((value) => ({ source: "run", value })));
1751
- }
1752
- if (approvalNext !== void 0) {
1753
- pending.push(approvalNext.then((value) => ({ source: "approval", value })));
1754
- }
1755
- const result = await Promise.race(pending);
1756
- if (result.source === "run") {
1757
- if (result.value.done === true) {
1758
- runDone = true;
1759
- runNext = void 0;
1760
- approvalEvents.close();
1761
- } else {
1762
- runNext = runIterator.next();
1763
- yield result.value.value;
1764
- }
1765
- continue;
1766
- }
1767
- if (result.value.done === true) {
1768
- approvalNext = void 0;
1769
- } else {
1770
- approvalNext = approvalEvents.next();
1771
- yield result.value.value;
1772
- }
1773
- }
1774
- } finally {
1775
- if (!runDone && runIterator.return !== void 0) {
1776
- await runIterator.return();
1777
- }
1778
- approvalEvents.close();
1779
- }
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
+ });
1780
1815
  }
1781
- function streamAgentRunEvents(_c, events, options = {}) {
1782
- 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;
1783
1844
  }
1784
1845
  function withAgentRunCancellation(events, onCancel) {
1785
1846
  const iterator = events[Symbol.asyncIterator]();
@@ -1889,7 +1950,7 @@ function createPersistedStreamingSessionTranscript(props) {
1889
1950
  if (event.type === "final" || event.type === "error") {
1890
1951
  assignTranscriptRunDuration(transcript, Date.now() - props.startedAt);
1891
1952
  }
1892
- 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";
1893
1954
  const saveInput = {
1894
1955
  id: props.session.id,
1895
1956
  runId: props.runId,
@@ -1899,10 +1960,10 @@ function createPersistedStreamingSessionTranscript(props) {
1899
1960
  };
1900
1961
  if (event.type === "error") saveInput.error = serializeError(event.error);
1901
1962
  await saveTranscript(saveInput);
1902
- const generatedMessages = event.type === "final" ? event.messages.slice(1) : [];
1963
+ const generatedMessages = event.type === "final" ? event.result.messages.slice(props.generatedMessagesStartIndex ?? 1) : [];
1903
1964
  if (props.persistGeneratedMessages === true && generatedMessages.length > 0) {
1904
1965
  await props.store.append({
1905
- context: { sessionId: props.session.id },
1966
+ scope: { sessionId: props.session.id },
1906
1967
  runId: props.runId,
1907
1968
  turn: 1,
1908
1969
  messages: generatedMessages
@@ -2004,9 +2065,9 @@ function acceptTranscriptStreamEvent(transcript, event) {
2004
2065
  transcript.push({
2005
2066
  entryId: transcript.length,
2006
2067
  kind: "tool",
2007
- toolName: event.toolCall.function.name,
2008
- callId: event.toolCall.callId ?? event.toolCall.id,
2009
- 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)
2010
2071
  });
2011
2072
  }
2012
2073
  if (event.type === "tool_result") {
@@ -2047,86 +2108,72 @@ function acceptTranscriptStreamEvent(transcript, event) {
2047
2108
  }
2048
2109
  appendChildAgentTranscriptEvent(matched, event);
2049
2110
  }
2050
- if (event.type === "tool_approval_request") {
2111
+ if (event.type === "final" && event.result.status === "suspended") {
2112
+ const interaction = event.result.interaction;
2051
2113
  const matched = findTranscriptToolEntry(
2052
2114
  transcript,
2053
- event.approval.toolName,
2054
- approvalCallId(event.approval)
2115
+ interaction.toolName,
2116
+ interaction.callId ?? interaction.toolCallId
2055
2117
  );
2056
- if (matched !== void 0) {
2057
- matched.approval = {
2058
- id: event.approval.id,
2059
- status: event.approval.status,
2060
- requestedAt: event.approval.requestedAt
2061
- };
2062
- }
2063
- }
2064
- if (event.type === "tool_approval_result") {
2065
- const matched = findTranscriptToolEntry(
2066
- transcript,
2067
- event.approval.toolName,
2068
- approvalCallId(event.approval)
2069
- );
2070
- if (matched !== void 0) {
2118
+ if (matched !== void 0 && interaction.type === "tool-approval") {
2071
2119
  const approval = {
2072
- id: event.approval.id,
2073
- status: event.approval.status,
2074
- requestedAt: event.approval.requestedAt
2120
+ id: interaction.id,
2121
+ status: "pending",
2122
+ requestedAt: (/* @__PURE__ */ new Date()).toISOString()
2075
2123
  };
2076
- if (event.approval.resolvedAt !== void 0) approval.resolvedAt = event.approval.resolvedAt;
2077
- if (event.approval.reason !== void 0) approval.reason = event.approval.reason;
2124
+ if (interaction.reason !== void 0) approval.reason = interaction.reason;
2078
2125
  matched.approval = approval;
2079
2126
  }
2080
- }
2081
- if (event.type === "tool_question_request") {
2082
- const matched = findTranscriptToolEntry(
2083
- transcript,
2084
- event.question.toolName,
2085
- questionCallId(event.question)
2086
- );
2087
- if (matched !== void 0) {
2127
+ if (matched !== void 0 && interaction.type === "tool-question") {
2088
2128
  matched.question = {
2089
- id: event.question.id,
2090
- status: event.question.status,
2091
- requestedAt: event.question.requestedAt,
2092
- 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
+ }))
2093
2138
  };
2094
2139
  }
2095
2140
  }
2096
- if (event.type === "tool_question_result") {
2141
+ if (event.type === "interaction_response") {
2142
+ const response = event.response;
2097
2143
  const matched = findTranscriptToolEntry(
2098
2144
  transcript,
2099
- event.question.toolName,
2100
- questionCallId(event.question)
2145
+ response.toolName,
2146
+ response.callId ?? response.toolCallId
2101
2147
  );
2102
- if (matched !== void 0) {
2103
- const question = {
2104
- id: event.question.id,
2105
- status: event.question.status,
2106
- requestedAt: event.question.requestedAt,
2107
- 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
+ }))
2108
2167
  };
2109
- if (event.question.answeredAt !== void 0) question.answeredAt = event.question.answeredAt;
2110
- if (event.question.cancelledAt !== void 0) {
2111
- question.cancelledAt = event.question.cancelledAt;
2112
- }
2113
- if (event.question.answers !== void 0) question.answers = event.question.answers;
2114
- matched.question = question;
2115
2168
  }
2116
2169
  }
2117
- if (event.type === "final" && event.trace?.traceId !== void 0) {
2118
- assignTranscriptTraceId(transcript, event.trace.traceId);
2170
+ if (event.type === "final" && event.result.trace?.traceId !== void 0) {
2171
+ assignTranscriptTraceId(transcript, event.result.trace.traceId);
2119
2172
  }
2120
2173
  if (event.type === "error") {
2121
2174
  appendTranscriptAssistantError(transcript, errorText(event.error));
2122
2175
  }
2123
2176
  }
2124
- function approvalCallId(approval) {
2125
- return approval.callId ?? approval.toolCallId;
2126
- }
2127
- function questionCallId(question) {
2128
- return question.callId ?? question.toolCallId;
2129
- }
2130
2177
  function appendChildAgentTranscriptEvent(entry, event) {
2131
2178
  const childEvent = childAgentTranscriptEvent(event);
2132
2179
  if (childEvent === void 0) {
@@ -2187,9 +2234,9 @@ function childAgentTranscriptEvent(event) {
2187
2234
  const entry = {
2188
2235
  kind: "tool",
2189
2236
  agentId: event.agentId,
2190
- toolName: child.toolCall.function.name,
2191
- callId: child.toolCall.callId ?? child.toolCall.id,
2192
- 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)
2193
2240
  };
2194
2241
  if (event.agentName !== void 0) entry.agentName = event.agentName;
2195
2242
  return entry;
@@ -2326,17 +2373,18 @@ function extractMessageText(message) {
2326
2373
  if (message.role === "system") {
2327
2374
  return message.content;
2328
2375
  }
2376
+ if (typeof message.content === "string") {
2377
+ return message.content;
2378
+ }
2329
2379
  return message.content.flatMap((item) => {
2330
2380
  if (item.type === "text" || item.type === "reasoning") {
2331
2381
  return [item.text];
2332
2382
  }
2333
- if (item.type === "tool_call") {
2334
- return [`${item.function.name}(${formatJson(item.function.arguments)})`];
2383
+ if (item.type === "tool-call") {
2384
+ return [`${item.toolName}(${formatJson(item.input)})`];
2335
2385
  }
2336
- if (item.type === "tool_result") {
2337
- return item.content.map(
2338
- (result) => "text" in result ? result.text : `[image:${result.mediaType ?? "image/png"}]`
2339
- );
2386
+ if (item.type === "tool-result") {
2387
+ return [toolResultOutputText(item.output)];
2340
2388
  }
2341
2389
  return [];
2342
2390
  }).join("\n");
@@ -2345,23 +2393,26 @@ function optionalTranscriptAttachments(message) {
2345
2393
  if (typeof message === "string" || message.role !== "user") {
2346
2394
  return {};
2347
2395
  }
2396
+ if (typeof message.content === "string") {
2397
+ return {};
2398
+ }
2348
2399
  const attachments = message.content.flatMap((content) => {
2349
2400
  if (content.type === "image") {
2350
2401
  const attachment = { kind: "image" };
2351
- if (content.source.type === "base64") {
2352
- attachment.data = content.source.data;
2353
- 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;
2354
2405
  } else {
2355
- attachment.url = content.source.url;
2406
+ attachment.url = content.image.url;
2356
2407
  }
2357
2408
  return [attachment];
2358
2409
  }
2359
- if (content.type === "document") {
2410
+ if (content.type === "file") {
2360
2411
  const attachment = { kind: "document" };
2361
- if (content.source.filename !== void 0) attachment.name = content.source.filename;
2362
- if (content.source.mediaType !== void 0) attachment.mediaType = content.source.mediaType;
2363
- if (content.source.type === "base64") attachment.data = content.source.data;
2364
- 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;
2365
2416
  return [attachment];
2366
2417
  }
2367
2418
  return [];
@@ -2378,32 +2429,63 @@ async function parseRunRequest(c) {
2378
2429
  if (!isObject(body)) {
2379
2430
  return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
2380
2431
  }
2381
- 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);
2382
2443
  if ("error" in request) {
2383
2444
  return request;
2384
2445
  }
2385
2446
  return parseRunRequestOptions(c, body, request);
2386
2447
  }
2387
- function parseLegacyRunRequestBody(c, body) {
2388
- 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") {
2389
2476
  return {
2390
- 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
+ )
2391
2483
  };
2392
2484
  }
2393
- const request = {
2394
- message: typeof body.message === "string" ? body.message : body.message
2395
- };
2396
- if ("history" in body) {
2397
- if (!Array.isArray(body.history) || !body.history.every(isMessage)) {
2398
- return { error: errorResponse(c, 400, "bad_request", "history must be a Message array") };
2399
- }
2400
- request.history = body.history;
2401
- }
2402
- return request;
2403
- }
2404
- function parseUiRunRequestBody(c, body) {
2405
- const messages = body.messages;
2406
- if (!Array.isArray(messages) || !messages.every(isMessage)) {
2485
+ let messages;
2486
+ try {
2487
+ messages = parseMessages(body.messages);
2488
+ } catch {
2407
2489
  return {
2408
2490
  error: errorResponse(c, 400, "bad_request", "messages must be a non-empty Message array")
2409
2491
  };
@@ -2419,19 +2501,21 @@ function parseUiRunRequestBody(c, body) {
2419
2501
  error: errorResponse(c, 400, "bad_request", "messages must be a non-empty Message array")
2420
2502
  };
2421
2503
  }
2422
- const history = messages.slice(0, -1);
2423
- const request = { message };
2424
- if (history.length > 0) request.history = history;
2425
- 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 };
2426
2510
  }
2427
2511
  function parseRunRequestOptions(c, body, request) {
2428
- if ("sessionId" in body) {
2512
+ if (request.type === "messages" && "sessionId" in body) {
2429
2513
  if (typeof body.sessionId !== "string" || body.sessionId.trim().length === 0) {
2430
2514
  return { error: errorResponse(c, 400, "bad_request", "sessionId must be a string") };
2431
2515
  }
2432
- if (request.history !== void 0) {
2516
+ if (request.messages.length !== 1 || request.messages[0]?.role !== "user") {
2433
2517
  return {
2434
- 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")
2435
2519
  };
2436
2520
  }
2437
2521
  request.sessionId = body.sessionId;
@@ -2442,7 +2526,7 @@ function parseRunRequestOptions(c, body, request) {
2442
2526
  }
2443
2527
  request.stream = body.stream;
2444
2528
  }
2445
- if ("maxTurns" in body) {
2529
+ if (request.type === "messages" && "maxTurns" in body) {
2446
2530
  if (!isNonNegativeInteger(body.maxTurns)) {
2447
2531
  return {
2448
2532
  error: errorResponse(c, 400, "bad_request", "maxTurns must be a non-negative integer")
@@ -2450,7 +2534,7 @@ function parseRunRequestOptions(c, body, request) {
2450
2534
  }
2451
2535
  request.maxTurns = body.maxTurns;
2452
2536
  }
2453
- if ("toolConcurrency" in body) {
2537
+ if (request.type === "messages" && "toolConcurrency" in body) {
2454
2538
  if (!isPositiveInteger(body.toolConcurrency)) {
2455
2539
  return {
2456
2540
  error: errorResponse(c, 400, "bad_request", "toolConcurrency must be a positive integer")
@@ -2458,13 +2542,11 @@ function parseRunRequestOptions(c, body, request) {
2458
2542
  }
2459
2543
  request.toolConcurrency = body.toolConcurrency;
2460
2544
  }
2461
- if ("model" in body) {
2462
- if (typeof body.model === "string") {
2463
- request.model = body.model;
2464
- } 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") {
2465
2547
  request.model = {
2466
- provider: body.model.provider,
2467
- model: body.model.model
2548
+ providerId: body.model.providerId,
2549
+ modelId: body.model.modelId
2468
2550
  };
2469
2551
  } else {
2470
2552
  return {
@@ -2472,7 +2554,7 @@ function parseRunRequestOptions(c, body, request) {
2472
2554
  c,
2473
2555
  400,
2474
2556
  "bad_request",
2475
- "model must be a provider:model string or { provider, model } object"
2557
+ "model must be a { providerId, modelId } object"
2476
2558
  )
2477
2559
  };
2478
2560
  }
@@ -2493,6 +2575,14 @@ function parseRunRequestOptions(c, body, request) {
2493
2575
  }
2494
2576
  return request;
2495
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
+ }
2496
2586
 
2497
2587
  // src/runtime/session-logs.ts
2498
2588
  async function appendSessionLog(store, input) {
@@ -2627,6 +2717,25 @@ function memorySavedLog(props) {
2627
2717
  metadata
2628
2718
  };
2629
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
+ }
2630
2739
  function runFailedLog(sessionId, runId, error, startedAt) {
2631
2740
  return {
2632
2741
  sessionId,
@@ -2643,6 +2752,9 @@ function runFailedLog(sessionId, runId, error, startedAt) {
2643
2752
  }
2644
2753
  function logsFromStreamEvent(props) {
2645
2754
  const { event, sessionId, runId } = props;
2755
+ if (event.type === "memory_compaction") {
2756
+ return [memoryCompactedLog({ sessionId, runId, ...event })];
2757
+ }
2646
2758
  if (event.type === "turn_start") {
2647
2759
  return [
2648
2760
  {
@@ -2668,12 +2780,12 @@ function logsFromStreamEvent(props) {
2668
2780
  level: "info",
2669
2781
  category: "tool",
2670
2782
  event: "tool.called",
2671
- message: `Tool ${event.toolCall.function.name} called`,
2783
+ message: `Tool ${event.toolCall.toolName} called`,
2672
2784
  metadata: {
2673
2785
  turn: event.turn,
2674
- toolName: event.toolCall.function.name,
2675
- callId: event.toolCall.callId ?? event.toolCall.id,
2676
- 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))
2677
2789
  }
2678
2790
  }
2679
2791
  ];
@@ -2722,102 +2834,79 @@ function logsFromStreamEvent(props) {
2722
2834
  ];
2723
2835
  }
2724
2836
  if (event.type === "final") {
2725
- return [
2726
- runCompletedLog({
2727
- sessionId,
2728
- runId,
2729
- durationMs: Date.now() - props.startedAt,
2730
- usage: event.usage,
2731
- output: event.output,
2732
- messageCount: event.messages.length
2733
- }),
2734
- memorySavedLog({ sessionId, runId, messageCount: event.messages.length })
2735
- ];
2736
- }
2737
- if (event.type === "error") {
2738
- return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
2739
- }
2740
- if (event.type === "tool_approval_request") {
2741
- const metadata = {
2742
- approvalId: event.approval.id,
2743
- toolName: event.approval.toolName,
2744
- status: event.approval.status,
2745
- hasReason: event.approval.reason !== void 0,
2746
- argumentBytes: byteLength2(event.approval.args)
2747
- };
2748
- if (event.approval.callId !== void 0) metadata.callId = event.approval.callId;
2749
- return [
2750
- {
2837
+ const result = event.result;
2838
+ const logs = [];
2839
+ if (result.status === "suspended") {
2840
+ logs.push({
2751
2841
  sessionId,
2752
2842
  runId,
2753
2843
  level: "info",
2754
- category: "approval",
2755
- event: "approval.requested",
2756
- message: `Approval requested for ${event.approval.toolName}`,
2757
- metadata
2758
- }
2759
- ];
2760
- }
2761
- if (event.type === "tool_approval_result") {
2762
- const metadata = {
2763
- approvalId: event.approval.id,
2764
- toolName: event.approval.toolName,
2765
- status: event.approval.status,
2766
- hasReason: event.approval.reason !== void 0
2767
- };
2768
- if (event.approval.callId !== void 0) metadata.callId = event.approval.callId;
2769
- return [
2770
- {
2771
- sessionId,
2772
- runId,
2773
- level: event.approval.status === "approved" ? "info" : "warn",
2774
- category: "approval",
2775
- event: "approval.resolved",
2776
- message: `Approval ${event.approval.status} for ${event.approval.toolName}`,
2777
- metadata
2778
- }
2779
- ];
2780
- }
2781
- if (event.type === "tool_question_request") {
2782
- const metadata = {
2783
- questionId: event.question.id,
2784
- toolName: event.question.toolName,
2785
- status: event.question.status,
2786
- questionCount: event.question.questions.length,
2787
- argumentBytes: byteLength2(event.question.args)
2788
- };
2789
- if (event.question.callId !== void 0) metadata.callId = event.question.callId;
2790
- return [
2791
- {
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({
2792
2868
  sessionId,
2793
2869
  runId,
2794
2870
  level: "info",
2795
- category: "question",
2796
- event: "question.requested",
2797
- message: `Question requested by ${event.question.toolName}`,
2798
- metadata
2799
- }
2800
- ];
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;
2801
2883
  }
2802
- if (event.type === "tool_question_result") {
2803
- 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;
2804
2889
  const metadata = {
2805
- questionId: event.question.id,
2806
- toolName: event.question.toolName,
2807
- status: event.question.status,
2808
- questionCount: event.question.questions.length,
2809
- argumentBytes: byteLength2(event.question.args),
2810
- 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
2811
2894
  };
2812
- 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
+ }
2813
2902
  return [
2814
2903
  {
2815
2904
  sessionId,
2816
2905
  runId,
2817
2906
  level: "info",
2818
- category: "question",
2819
- event: cancelled ? "question.cancelled" : "question.answered",
2820
- 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}`,
2821
2910
  metadata
2822
2911
  }
2823
2912
  ];
@@ -2843,16 +2932,16 @@ function childAgentLog(event, sessionId, runId) {
2843
2932
  level: "debug",
2844
2933
  category: "tool",
2845
2934
  event: "child_tool.called",
2846
- message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.function.name}`,
2935
+ message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.toolName}`,
2847
2936
  metadata: {
2848
2937
  parentToolName: event.toolName,
2849
2938
  agentId: event.agentId,
2850
2939
  hasAgentName: event.agentName !== void 0,
2851
2940
  turn: event.turn,
2852
2941
  childTurn: child.turn,
2853
- toolName: child.toolCall.function.name,
2854
- callId: child.toolCall.callId ?? child.toolCall.id,
2855
- 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))
2856
2945
  }
2857
2946
  }
2858
2947
  ];
@@ -2903,14 +2992,16 @@ function childAgentLog(event, sessionId, runId) {
2903
2992
  ];
2904
2993
  }
2905
2994
  if (child.type === "final") {
2995
+ const result = child.result;
2906
2996
  const metadata = {
2907
2997
  parentToolName: event.toolName,
2908
2998
  agentId: event.agentId,
2909
2999
  hasAgentName: event.agentName !== void 0,
2910
- outputBytes: byteLength2(child.output),
2911
- messageCount: child.messages.length
3000
+ outputBytes: byteLength2(result.text),
3001
+ messageCount: result.messages.length,
3002
+ status: result.status
2912
3003
  };
2913
- const usage = usageSummary(child.usage);
3004
+ const usage = usageSummary(result.usage);
2914
3005
  if (usage !== void 0) metadata.usage = usage;
2915
3006
  return [
2916
3007
  {
@@ -3010,6 +3101,56 @@ async function prepareAgentRun(c, props) {
3010
3101
  if ("error" in body) {
3011
3102
  return body.error;
3012
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
+ }
3013
3154
  const session = await resolveRunSession(c, body, agentId, props.stores);
3014
3155
  if (session instanceof Response) {
3015
3156
  return session;
@@ -3036,12 +3177,12 @@ async function prepareAgentRun(c, props) {
3036
3177
  store: props.stores.sessions
3037
3178
  });
3038
3179
  const memoryMetadata = runMemoryMetadata(agentId, body, selectedModel, runId);
3039
- const promptMessage = normalizePromptMessage(body.message);
3180
+ const promptMessage = body.messages.at(-1);
3040
3181
  const sessionStore = props.stores.sessions;
3041
3182
  const shouldPersistSessionMessages = session !== void 0 && sessionStore !== void 0 && !usesStoreAsAgentMemory(runAgent, sessionStore);
3042
3183
  if (shouldPersistSessionMessages) {
3043
3184
  await sessionStore.append({
3044
- context: { sessionId: session.id, metadata: memoryMetadata },
3185
+ scope: { sessionId: session.id, metadata: memoryMetadata },
3045
3186
  runId,
3046
3187
  turn: 1,
3047
3188
  messages: [promptMessage]
@@ -3061,10 +3202,13 @@ async function prepareAgentRun(c, props) {
3061
3202
  body,
3062
3203
  memoryMetadata,
3063
3204
  execution,
3064
- options: createRunOptions(body, agentId, session),
3205
+ failureMessages: void 0,
3206
+ memoryCompactionLogged: false,
3207
+ options: createRunOptions(body, agentId, session, c.req.raw.signal),
3065
3208
  runAgent,
3066
3209
  runId,
3067
3210
  runStartedAt,
3211
+ generatedMessagesStartIndex: 1,
3068
3212
  selectedModel,
3069
3213
  session,
3070
3214
  sessionStore,
@@ -3106,7 +3250,7 @@ async function recordRunReceived(props) {
3106
3250
  sessionId: props.session.id,
3107
3251
  runId: props.runId,
3108
3252
  agentId: props.agentId,
3109
- message: props.body.message,
3253
+ message: props.body.messages.at(-1),
3110
3254
  stream: props.body.stream === true,
3111
3255
  hasTrace: props.body.trace !== void 0
3112
3256
  };
@@ -3154,23 +3298,33 @@ function runMemoryMetadata(agentId, body, selectedModel, runId) {
3154
3298
  return metadata;
3155
3299
  }
3156
3300
  function createRunExecution(props) {
3157
- if (props.session !== void 0 && props.runAgent.memory !== void 0) {
3158
- const session = props.runAgent.session(props.session.id, { metadata: props.memoryMetadata });
3301
+ const session = props.session;
3302
+ if (session !== void 0 && props.runAgent.memory !== void 0) {
3159
3303
  return {
3160
- generate: (options) => session.generate(props.body.message, options),
3161
- stream: (options) => session.stream(props.body.message, options)
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
+ })
3162
3314
  };
3163
3315
  }
3164
- const input = props.session !== void 0 ? [...props.session.messages, props.promptMessage] : props.body.history !== void 0 ? [...props.body.history, props.promptMessage] : props.body.message;
3316
+ const transcript = session !== void 0 ? [...session.messages, props.promptMessage] : props.body.messages;
3165
3317
  return {
3166
- generate: (options) => props.runAgent.generate(input, options),
3167
- stream: (options) => props.runAgent.stream(input, options)
3318
+ generate: (options) => props.runAgent.generate({ messages: transcript, ...options }),
3319
+ stream: (options) => props.runAgent.stream({ messages: transcript, ...options })
3168
3320
  };
3169
3321
  }
3170
- function createRunOptions(body, agentId, session) {
3171
- const options = { retries: {} };
3172
- if (body.maxTurns !== void 0) options.maxTurns = body.maxTurns;
3173
- if (body.toolConcurrency !== void 0) options.toolConcurrency = body.toolConcurrency;
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;
3327
+ }
3174
3328
  if (body.trace !== void 0) {
3175
3329
  options.trace = traceForRun(body.trace, agentId, session);
3176
3330
  } else if (session !== void 0) {
@@ -3179,34 +3333,11 @@ function createRunOptions(body, agentId, session) {
3179
3333
  return options;
3180
3334
  }
3181
3335
  function handleStreamingAgentRun(c, run, props) {
3182
- const runtimeEvents = new AsyncEventQueue();
3183
- const approvalContext = {
3184
- runId: run.runId,
3185
- agentId: run.agentId,
3186
- emit: (event) => runtimeEvents.push(event)
3187
- };
3188
- if (run.session !== void 0) approvalContext.sessionId = run.session.id;
3189
- if (run.body.metadata !== void 0) approvalContext.metadata = run.body.metadata;
3190
- const questionContext = {
3191
- runId: run.runId,
3192
- agentId: run.agentId,
3193
- emit: (event) => runtimeEvents.push(event)
3194
- };
3195
- if (run.session !== void 0) questionContext.sessionId = run.session.id;
3196
- if (run.body.metadata !== void 0) questionContext.metadata = run.body.metadata;
3197
- const effectiveHook = props.questionRuntime.createHook(questionContext);
3198
- const streamOptions = withInternalAgentRunOptions(
3199
- { ...run.options },
3200
- { hook: effectiveHook, runId: run.runId }
3201
- );
3202
- const runStream = mergeRunAndApprovalEvents(
3203
- resumeStreamingApprovals(
3204
- run.runAgent,
3205
- run.execution.stream(streamOptions),
3206
- props.approvalRuntime,
3207
- approvalContext
3208
- ),
3209
- runtimeEvents
3336
+ const streamOptions = withInternalAgentRunOptions({ ...run.options }, { runId: run.runId });
3337
+ const runStream = registerStreamingContinuation(
3338
+ run.execution.stream(streamOptions),
3339
+ props.continuationRegistry,
3340
+ run
3210
3341
  );
3211
3342
  let stream = runStream;
3212
3343
  let persistedRun;
@@ -3221,17 +3352,17 @@ function handleStreamingAgentRun(c, run, props) {
3221
3352
  }),
3222
3353
  store: run.sessionStore,
3223
3354
  session: run.session,
3224
- message: run.body.message,
3355
+ message: run.body.messages.at(-1),
3225
3356
  runId: run.runId,
3226
3357
  startedAt: run.runStartedAt,
3227
- persistGeneratedMessages: run.shouldPersistSessionMessages
3358
+ persistGeneratedMessages: run.shouldPersistSessionMessages,
3359
+ generatedMessagesStartIndex: run.generatedMessagesStartIndex
3228
3360
  });
3229
3361
  stream = persistedRun.events;
3230
3362
  }
3231
3363
  return streamAgentRunEvents(c, stream, {
3364
+ runId: run.runId,
3232
3365
  onCancel: async () => {
3233
- props.approvalRuntime.cancelRun(run.runId);
3234
- props.questionRuntime.cancelRun(run.runId);
3235
3366
  const persistence = [];
3236
3367
  if (persistedRun !== void 0) {
3237
3368
  persistence.push(persistedRun.cancel());
@@ -3250,59 +3381,57 @@ function handleStreamingAgentRun(c, run, props) {
3250
3381
  }
3251
3382
  async function handleBufferedAgentRun(c, run, props) {
3252
3383
  try {
3253
- const runtimeOptions = await startBufferedSessionRun(run, props);
3254
- let result = await run.execution.generate(runtimeOptions);
3255
- const approvalContext = createApprovalContext(run);
3256
- while (result.status === "approval_required") {
3257
- const decision = await props.approvalRuntime.request(approvalContext, result.approval);
3258
- result = await run.runAgent.resume(result, decision);
3259
- }
3260
- const response = result;
3261
- await completeBufferedSessionRun(run, response);
3262
- 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));
3263
3391
  } catch (error) {
3264
3392
  await failBufferedSessionRun(run, error);
3265
3393
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError(error));
3266
3394
  }
3267
3395
  }
3268
- 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) {
3269
3402
  if (run.session !== void 0) {
3270
3403
  await appendSessionLog(run.sessionStore, runStartedLog(run.session, run.runId));
3271
3404
  await appendSessionLog(run.sessionStore, memoryLoadedLog(run.session, run.runId));
3272
3405
  }
3273
- const questionContext = {
3274
- runId: run.runId,
3275
- agentId: run.agentId
3406
+ const internalOptions = {
3407
+ onFailure: ({ messages }) => {
3408
+ run.failureMessages = structuredClone(messages);
3409
+ },
3410
+ runId: run.runId
3276
3411
  };
3277
- if (run.session !== void 0) questionContext.sessionId = run.session.id;
3278
- if (run.body.metadata !== void 0) questionContext.metadata = run.body.metadata;
3279
- const effectiveHook = props.questionRuntime.createHook(questionContext);
3280
- return withInternalAgentRunOptions({ ...run.options }, { hook: effectiveHook, runId: run.runId });
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);
3281
3428
  }
3282
- function createApprovalContext(run, emit) {
3283
- const context = {
3284
- runId: run.runId,
3285
- agentId: run.agentId
3286
- };
3287
- if (run.session !== void 0) context.sessionId = run.session.id;
3288
- if (run.body.metadata !== void 0) context.metadata = run.body.metadata;
3289
- if (emit !== void 0) context.emit = emit;
3290
- return context;
3291
- }
3292
- async function* resumeStreamingApprovals(agent, initialStream, approvalRuntime, context) {
3293
- let stream = initialStream;
3294
- while (true) {
3295
- let pending;
3296
- for await (const event of stream) {
3297
- if (event.type === "approval_required") {
3298
- pending = event;
3299
- break;
3300
- }
3301
- yield event;
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 });
3302
3433
  }
3303
- if (pending === void 0) return;
3304
- const decision = await approvalRuntime.request(context, pending.approval);
3305
- stream = agent.resume(pending, decision);
3434
+ yield event;
3306
3435
  }
3307
3436
  }
3308
3437
  async function completeBufferedSessionRun(run, response) {
@@ -3310,10 +3439,10 @@ async function completeBufferedSessionRun(run, response) {
3310
3439
  return;
3311
3440
  }
3312
3441
  if (run.shouldPersistSessionMessages) {
3313
- const generatedMessages = response.messages.slice(1);
3442
+ const generatedMessages = response.messages.slice(run.generatedMessagesStartIndex);
3314
3443
  if (generatedMessages.length > 0) {
3315
3444
  await run.sessionStore.append({
3316
- context: { sessionId: run.session.id, metadata: run.memoryMetadata },
3445
+ scope: { sessionId: run.session.id, metadata: run.memoryMetadata },
3317
3446
  runId: run.runId,
3318
3447
  turn: 1,
3319
3448
  messages: generatedMessages
@@ -3326,21 +3455,85 @@ async function completeBufferedSessionRun(run, response) {
3326
3455
  await run.sessionStore.saveSessionRunTranscript({
3327
3456
  id: run.session.id,
3328
3457
  runId: run.runId,
3329
- ...optionalTitle(run.body.message),
3458
+ ...optionalTitle(run.body.messages.at(-1)),
3330
3459
  transcript,
3331
- status: "success"
3460
+ status: response.status === "suspended" ? "suspended" : "success"
3332
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
+ }
3333
3473
  await appendSessionLog(
3334
3474
  run.sessionStore,
3335
- 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({
3336
3489
  sessionId: run.session.id,
3337
3490
  runId: run.runId,
3338
3491
  durationMs: durationMs2,
3339
3492
  usage: response.usage,
3340
- output: response.output,
3493
+ output: response.text,
3341
3494
  messageCount: response.messages.length
3342
3495
  })
3343
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
+ }
3344
3537
  await appendSessionLog(
3345
3538
  run.sessionStore,
3346
3539
  memorySavedLog({
@@ -3354,17 +3547,14 @@ async function failBufferedSessionRun(run, error) {
3354
3547
  if (run.session === void 0 || run.sessionStore === void 0) {
3355
3548
  return;
3356
3549
  }
3357
- const messages = await run.sessionStore.load({
3358
- sessionId: run.session.id,
3359
- metadata: run.memoryMetadata
3360
- });
3361
3550
  const durationMs2 = Date.now() - run.runStartedAt;
3362
- const transcript = transcriptFromMessages(messages.slice(run.session.messageCount));
3551
+ const messages = run.failureMessages ?? [run.body.messages.at(-1)];
3552
+ const transcript = transcriptFromMessages([...messages]);
3363
3553
  assignTranscriptRunDuration(transcript, durationMs2);
3364
3554
  await run.sessionStore.saveSessionRunTranscript({
3365
3555
  id: run.session.id,
3366
3556
  runId: run.runId,
3367
- ...optionalTitle(run.body.message),
3557
+ ...optionalTitle(run.body.messages.at(-1)),
3368
3558
  transcript,
3369
3559
  status: "error",
3370
3560
  error: serializeError(error)
@@ -3374,254 +3564,13 @@ async function failBufferedSessionRun(run, error) {
3374
3564
  runFailedLog(run.session.id, run.runId, error, run.runStartedAt)
3375
3565
  );
3376
3566
  }
3377
- function normalizePromptMessage(message) {
3378
- return typeof message === "string" ? Message.user(message) : message;
3379
- }
3380
3567
  function usesStoreAsAgentMemory(agent, store) {
3381
3568
  const memoryStore = agent.memory?.store;
3382
3569
  return memoryStore !== void 0 && rawObservedStore(memoryStore) === rawObservedStore(store);
3383
3570
  }
3384
3571
 
3385
- // src/runtime/approvals.ts
3386
- import { getAgentApprovalRequestDetails } from "@anvia/core/internal/agent";
3387
-
3388
- // src/runtime/query.ts
3389
- function optionalQueryString(value) {
3390
- const trimmed = value?.trim();
3391
- return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
3392
- }
3393
- function parseLimit(value, defaultMax = 50, max = 100) {
3394
- if (value === void 0 || value.trim().length === 0) {
3395
- return defaultMax;
3396
- }
3397
- const limit = Number(value);
3398
- if (!Number.isInteger(limit) || limit <= 0) {
3399
- return void 0;
3400
- }
3401
- return Math.min(limit, max);
3402
- }
3403
- function parseTraceStatus(value) {
3404
- const status = optionalQueryString(value);
3405
- if (status === void 0) {
3406
- return void 0;
3407
- }
3408
- return status === "running" || status === "success" || status === "error" ? status : false;
3409
- }
3410
- function parseAfter(value) {
3411
- if (value === void 0 || value.trim().length === 0) {
3412
- return void 0;
3413
- }
3414
- const after = Number(value);
3415
- if (!Number.isInteger(after) || after < 0) {
3416
- return false;
3417
- }
3418
- return after;
3419
- }
3420
-
3421
- // src/runtime/approvals.ts
3422
- function registerApprovalRoutes(app, approvals) {
3423
- app.get("/approvals", (c) => {
3424
- const status = parseApprovalStatus(c.req.query("status"));
3425
- if (status === false) {
3426
- return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
3427
- }
3428
- const options = {};
3429
- const runId = optionalQueryString(c.req.query("runId"));
3430
- const agentId = optionalQueryString(c.req.query("agentId"));
3431
- const sessionId = optionalQueryString(c.req.query("sessionId"));
3432
- if (status !== void 0) {
3433
- options.status = status;
3434
- }
3435
- if (runId !== void 0) {
3436
- options.runId = runId;
3437
- }
3438
- if (agentId !== void 0) {
3439
- options.agentId = agentId;
3440
- }
3441
- if (sessionId !== void 0) {
3442
- options.sessionId = sessionId;
3443
- }
3444
- return c.json({
3445
- approvals: approvals.list(options)
3446
- });
3447
- });
3448
- app.post("/approvals/:approvalId/decision", async (c) => {
3449
- const body = await parseApprovalDecisionRequest(c);
3450
- if ("error" in body) {
3451
- return body.error;
3452
- }
3453
- const result = approvals.decide(c.req.param("approvalId"), body);
3454
- if (result === "missing") {
3455
- return errorResponse(c, 404, "not_found", "Approval not found");
3456
- }
3457
- if (result === "resolved") {
3458
- return errorResponse(c, 409, "conflict", "Approval is already resolved");
3459
- }
3460
- return c.json(result);
3461
- });
3462
- }
3463
- function parseApprovalStatus(value) {
3464
- const status = optionalQueryString(value);
3465
- if (status === void 0) {
3466
- return void 0;
3467
- }
3468
- return status === "pending" || status === "resolved" ? status : false;
3469
- }
3470
- async function parseApprovalDecisionRequest(c) {
3471
- let body;
3472
- try {
3473
- body = await c.req.json();
3474
- } catch {
3475
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
3476
- }
3477
- if (!isObject(body)) {
3478
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
3479
- }
3480
- if (typeof body.approved !== "boolean") {
3481
- return { error: errorResponse(c, 400, "bad_request", "approved must be a boolean") };
3482
- }
3483
- if ("reason" in body && typeof body.reason !== "string") {
3484
- return { error: errorResponse(c, 400, "bad_request", "reason must be a string") };
3485
- }
3486
- const reason = typeof body.reason === "string" && body.reason.trim().length > 0 ? body.reason.trim() : void 0;
3487
- return approvalDecision(body.approved, reason);
3488
- }
3489
- function createApprovalRuntime() {
3490
- const approvals = /* @__PURE__ */ new Map();
3491
- return {
3492
- approvals,
3493
- async request(context, approval) {
3494
- const decision = await requestApproval(approvals, context, approval);
3495
- return decision.approved ? { approved: true, ...decision.reason === void 0 ? {} : { reason: decision.reason } } : {
3496
- approved: false,
3497
- ...decision.reason === void 0 ? {} : { reason: decision.reason }
3498
- };
3499
- },
3500
- list(options) {
3501
- return [...approvals.values()].filter((approval) => {
3502
- if (options.status === "pending" && approval.status !== "pending") {
3503
- return false;
3504
- }
3505
- if (options.status === "resolved" && approval.status === "pending") {
3506
- return false;
3507
- }
3508
- if (options.runId !== void 0 && approval.runId !== options.runId) {
3509
- return false;
3510
- }
3511
- if (options.agentId !== void 0 && approval.agentId !== options.agentId) {
3512
- return false;
3513
- }
3514
- if (options.sessionId !== void 0 && approval.sessionId !== options.sessionId) {
3515
- return false;
3516
- }
3517
- return true;
3518
- }).map(publicApproval);
3519
- },
3520
- cancelRun(runId) {
3521
- const cancelled = [];
3522
- for (const approval of approvals.values()) {
3523
- if (!isPendingApproval(approval) || approval.runId !== runId) {
3524
- continue;
3525
- }
3526
- const resolved = resolveApproval(approval, "cancelled", {
3527
- reason: "Run cancelled in Anvia Studio."
3528
- });
3529
- approvals.set(approval.id, resolved);
3530
- approval.emit?.({ type: "tool_approval_result", approval: resolved });
3531
- approval.resolve(approvalDecision(false, resolved.reason));
3532
- cancelled.push(resolved);
3533
- }
3534
- return cancelled;
3535
- },
3536
- decide(id, decision) {
3537
- const approval = approvals.get(id);
3538
- if (approval === void 0) {
3539
- return "missing";
3540
- }
3541
- if (!isPendingApproval(approval)) {
3542
- return "resolved";
3543
- }
3544
- const reason = decision.approved ? decision.reason : decision.reason ?? approval.rejectMessage ?? "Rejected in Anvia Studio.";
3545
- const resolved = resolveApproval(
3546
- approval,
3547
- decision.approved ? "approved" : "rejected",
3548
- reason === void 0 ? {} : { reason }
3549
- );
3550
- approvals.set(id, resolved);
3551
- approval.emit?.({ type: "tool_approval_result", approval: resolved });
3552
- approval.resolve(approvalDecision(decision.approved, reason));
3553
- return publicApproval(resolved);
3554
- }
3555
- };
3556
- }
3557
- async function requestApproval(approvals, context, request) {
3558
- const id = request.id;
3559
- const details = getAgentApprovalRequestDetails(request);
3560
- const approval = {
3561
- id,
3562
- runId: context.runId,
3563
- agentId: context.agentId,
3564
- toolName: request.toolName,
3565
- internalCallId: details?.internalCallId ?? request.id,
3566
- args: details?.rawArgs ?? JSON.stringify(request.input) ?? "null",
3567
- status: "pending",
3568
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
3569
- resolve: () => {
3570
- }
3571
- };
3572
- if (context.sessionId !== void 0) approval.sessionId = context.sessionId;
3573
- if (request.toolCallId !== void 0) approval.callId = request.toolCallId;
3574
- if (request.reason !== void 0) approval.reason = request.reason;
3575
- if (details?.rejectMessage !== void 0) approval.rejectMessage = details.rejectMessage;
3576
- if (context.emit !== void 0) approval.emit = context.emit;
3577
- const decision = new Promise((resolve2) => {
3578
- approval.resolve = (decision2) => {
3579
- const current = approvals.get(id);
3580
- if (!isPendingApproval(current)) {
3581
- if (current !== void 0) {
3582
- resolve2(approvalDecision(current.status === "approved", current.reason));
3583
- }
3584
- return;
3585
- }
3586
- const reason = decision2.approved ? decision2.reason : decision2.reason ?? details?.rejectMessage ?? "Rejected in Anvia Studio.";
3587
- const resolved = resolveApproval(
3588
- current,
3589
- decision2.approved ? "approved" : "rejected",
3590
- reason === void 0 ? {} : { reason }
3591
- );
3592
- approvals.set(id, resolved);
3593
- context.emit?.({ type: "tool_approval_result", approval: resolved });
3594
- resolve2(approvalDecision(decision2.approved, reason));
3595
- };
3596
- });
3597
- approvals.set(id, approval);
3598
- context.emit?.({ type: "tool_approval_request", approval: publicApproval(approval) });
3599
- return decision;
3600
- }
3601
- function isPendingApproval(approval) {
3602
- return approval !== void 0 && approval.status === "pending" && "resolve" in approval;
3603
- }
3604
- function resolveApproval(approval, status, options = {}) {
3605
- const resolved = {
3606
- ...approval,
3607
- status,
3608
- resolvedAt: (/* @__PURE__ */ new Date()).toISOString()
3609
- };
3610
- if (options.reason !== void 0) resolved.reason = options.reason;
3611
- return publicApproval(resolved);
3612
- }
3613
- function approvalDecision(approved, reason) {
3614
- const decision = { approved };
3615
- if (reason !== void 0) decision.reason = reason;
3616
- return decision;
3617
- }
3618
- function publicApproval(approval) {
3619
- const { emit, rejectMessage, resolve: resolve2, ...rest } = approval;
3620
- void emit;
3621
- void rejectMessage;
3622
- void resolve2;
3623
- return rest;
3624
- }
3572
+ // src/runtime/config.ts
3573
+ import { isQuestionTool } from "@anvia/core/tool";
3625
3574
 
3626
3575
  // src/runtime/eval-config.ts
3627
3576
  var CASE_PREVIEW_LIMIT = 5;
@@ -3703,6 +3652,41 @@ function isRecord2(value) {
3703
3652
 
3704
3653
  // src/runtime/knowledge.ts
3705
3654
  import { getAgentToolState } from "@anvia/core/internal/agent";
3655
+
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;
3660
+ }
3661
+ function parseLimit(value, defaultMax = 50, max = 100) {
3662
+ if (value === void 0 || value.trim().length === 0) {
3663
+ return defaultMax;
3664
+ }
3665
+ const limit = Number(value);
3666
+ if (!Number.isInteger(limit) || limit <= 0) {
3667
+ return void 0;
3668
+ }
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;
3675
+ }
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;
3687
+ }
3688
+
3689
+ // src/runtime/knowledge.ts
3706
3690
  function registerKnowledgeRoutes(app, props) {
3707
3691
  app.get("/knowledge", async (c) => {
3708
3692
  const limit = parseLimit(c.req.query("limit"));
@@ -3763,7 +3747,7 @@ async function agentKnowledgeConfig(agent) {
3763
3747
  async function knowledgeSources(agent) {
3764
3748
  const toolIndexes = getAgentToolState(agent.agent).toolIndexes;
3765
3749
  const staticContext = staticContextDocuments(agent.agent);
3766
- const indexedContext = contextIndexes(agent.agent);
3750
+ const indexedContext = vectorContexts(agent.agent);
3767
3751
  const sources = [
3768
3752
  {
3769
3753
  sourceId: staticSourceId(),
@@ -3775,20 +3759,20 @@ async function knowledgeSources(agent) {
3775
3759
  }
3776
3760
  ];
3777
3761
  const dynamicContextSources = await Promise.all(
3778
- indexedContext.map(async (contextIndex, index) => {
3779
- const inspect = inspectFn(contextIndex.index);
3780
- const count = await inspectableCount(inspect, contextIndex.filter);
3762
+ indexedContext.map(async (vectorContext, index) => {
3763
+ const inspect = inspectFn(vectorContext.store);
3764
+ const count = await inspectableCount(inspect, vectorContext.filter);
3781
3765
  const source = {
3782
3766
  sourceId: dynamicContextSourceId(index),
3783
3767
  kind: "dynamic_context",
3784
3768
  label: `Dynamic context ${index + 1}`,
3785
3769
  count: 1,
3786
3770
  registrationIndex: index,
3787
- topK: contextIndex.topK,
3771
+ topK: vectorContext.topK,
3788
3772
  inspectable: inspect !== void 0
3789
3773
  };
3790
- if (contextIndex.threshold !== void 0) {
3791
- source.threshold = contextIndex.threshold;
3774
+ if (vectorContext.minScore !== void 0) {
3775
+ source.minScore = vectorContext.minScore;
3792
3776
  }
3793
3777
  if (count !== void 0) source.itemCount = count;
3794
3778
  return source;
@@ -3807,8 +3791,8 @@ async function knowledgeSources(agent) {
3807
3791
  topK: toolIndex.topK,
3808
3792
  inspectable: inspect !== void 0
3809
3793
  };
3810
- if (toolIndex.threshold !== void 0) {
3811
- source.threshold = toolIndex.threshold;
3794
+ if (toolIndex.minScore !== void 0) {
3795
+ source.minScore = toolIndex.minScore;
3812
3796
  }
3813
3797
  if (count !== void 0) source.itemCount = count;
3814
3798
  return source;
@@ -3827,20 +3811,20 @@ async function knowledgeItemsPage(agent, sourceId, request) {
3827
3811
  if (sourceId === staticSourceId()) {
3828
3812
  return staticKnowledgeItemsPage(agent, request);
3829
3813
  }
3830
- const dynamicContextIndex = dynamicSourceIndex(sourceId, "dynamic_context");
3831
- if (dynamicContextIndex !== void 0) {
3832
- const contextIndex = contextIndexes(agent.agent)[dynamicContextIndex];
3833
- if (contextIndex === 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) {
3834
3818
  return void 0;
3835
3819
  }
3836
- const inspect = inspectFn(contextIndex.index);
3820
+ const inspect = inspectFn(vectorContext.store);
3837
3821
  if (inspect === void 0) {
3838
3822
  return nonInspectablePage(agent.id, sourceId, "dynamic_context");
3839
3823
  }
3840
3824
  const page = await inspect({
3841
3825
  limit: request.limit,
3842
3826
  cursor: request.cursor,
3843
- filter: contextIndex.filter
3827
+ filter: vectorContext.filter
3844
3828
  });
3845
3829
  const result = {
3846
3830
  agentId: agent.id,
@@ -3881,12 +3865,12 @@ async function knowledgeItemsPage(agent, sourceId, request) {
3881
3865
  }
3882
3866
  return void 0;
3883
3867
  }
3884
- function inspectFn(index) {
3885
- if (!isRecord3(index) || typeof index.inspect !== "function") {
3868
+ function inspectFn(store) {
3869
+ if (!isRecord3(store) || typeof store.inspect !== "function") {
3886
3870
  return void 0;
3887
3871
  }
3888
- const inspect = index.inspect;
3889
- return (request) => inspect.call(index, request);
3872
+ const inspect = store.inspect;
3873
+ return (request) => inspect.call(store, request);
3890
3874
  }
3891
3875
  function staticKnowledgeItemsPage(agent, request) {
3892
3876
  const staticContext = staticContextDocuments(agent.agent);
@@ -4102,7 +4086,7 @@ function isRecord3(value) {
4102
4086
 
4103
4087
  // src/runtime/tool-metadata.ts
4104
4088
  import { getAgentToolState as getAgentToolState2 } from "@anvia/core/internal/agent";
4105
- var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
4089
+ import { isMcpTool } from "@anvia/core/mcp";
4106
4090
  function agentToolItems(agent) {
4107
4091
  const state = getAgentToolState2(agent.agent);
4108
4092
  const items = state.staticTools.map((tool) => ({ tool, source: "static" }));
@@ -4135,15 +4119,10 @@ function toolRequiresApproval(tool) {
4135
4119
  return approval !== void 0 && approval !== false;
4136
4120
  }
4137
4121
  function mcpServerName(tool) {
4138
- const metadata = tool[MCP_TOOL_METADATA_KEY];
4139
- if (typeof metadata !== "object" || metadata === null) {
4140
- return void 0;
4141
- }
4142
- const serverName = metadata.serverName;
4143
- return typeof serverName === "string" && serverName.length > 0 ? serverName : void 0;
4122
+ return isMcpTool(tool) ? tool.mcp.serverName : void 0;
4144
4123
  }
4145
- function agentHasMcpTools(agent) {
4146
- return agentToolItems(agent).some(({ tool }) => mcpServerName(tool) !== void 0);
4124
+ function agentHasMcpServers(agent) {
4125
+ return agent.agent.mcpServers.length > 0;
4147
4126
  }
4148
4127
 
4149
4128
  // src/runtime/config.ts
@@ -4165,7 +4144,7 @@ function agentConfig(agent) {
4165
4144
  function agentRuntimeSummary(agent) {
4166
4145
  const tools = agentToolItems(agent);
4167
4146
  const staticContext = staticContextDocuments(agent.agent);
4168
- const indexedContext = contextIndexes(agent.agent);
4147
+ const indexedContext = vectorContexts(agent.agent);
4169
4148
  const name = agent.name ?? agent.agent.name;
4170
4149
  const description = agent.description ?? agent.agent.description;
4171
4150
  const summary = {
@@ -4178,7 +4157,7 @@ function agentRuntimeSummary(agent) {
4178
4157
  mcpToolCount: tools.filter((item) => mcpServerName(item.tool) !== void 0).length,
4179
4158
  staticContextCount: staticContext.length,
4180
4159
  dynamicContextCount: indexedContext.length,
4181
- observerCount: agent.agent.observers.length,
4160
+ observerCount: Object.keys(agent.agent.observability?.observers ?? {}).length,
4182
4161
  hasMemory: agent.agent.memory !== void 0,
4183
4162
  hasLifecycle: agent.agent.lifecycle !== void 0,
4184
4163
  hasOutputSchema: agent.agent.outputSchema !== void 0
@@ -4250,14 +4229,16 @@ function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0)
4250
4229
  if (agents.some((agent) => agent.agent.tools.length > 0)) {
4251
4230
  capabilities.tools = { enabled: true };
4252
4231
  }
4253
- if (agents.some(agentHasMcpTools)) {
4232
+ if (agents.some(agentHasMcpServers)) {
4254
4233
  capabilities.mcps = { enabled: true };
4255
4234
  }
4256
4235
  if (sandboxCount > 0) {
4257
4236
  capabilities.sandboxes = { enabled: true };
4258
4237
  }
4259
- if (agents.some((agent) => agent.agent.tools.some(toolRequiresApproval))) {
4260
- capabilities.approvals = { enabled: true };
4238
+ if (agents.some(
4239
+ (agent) => agent.agent.tools.some((tool) => toolRequiresApproval(tool) || isQuestionTool(tool))
4240
+ )) {
4241
+ capabilities.interactions = { enabled: true };
4261
4242
  }
4262
4243
  if (agents.some(agentHasKnowledge)) {
4263
4244
  capabilities.knowledge = { enabled: true };
@@ -4339,6 +4320,47 @@ async function parseEvalRunRequest(c) {
4339
4320
  return request;
4340
4321
  }
4341
4322
 
4323
+ // src/runtime/interactions.ts
4324
+ import {
4325
+ assertAgentInteractionResponse
4326
+ } from "@anvia/core/agent";
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
+
4342
4364
  // src/runtime/mcps.ts
4343
4365
  function registerMcpRoutes(app, props) {
4344
4366
  app.get("/agents/:agentId/mcps", async (c) => {
@@ -4354,43 +4376,29 @@ function registerMcpRoutes(app, props) {
4354
4376
  });
4355
4377
  }
4356
4378
  async function agentMcpMetadata(agent) {
4357
- const servers = /* @__PURE__ */ new Map();
4358
- const seen = /* @__PURE__ */ new Set();
4359
- for (const { tool, source } of agentToolItems(agent)) {
4360
- const serverName = mcpServerName(tool);
4361
- if (serverName === void 0) {
4362
- continue;
4363
- }
4364
- const definition = await tool.definition("");
4365
- const key = `${serverName}:${source}:${definition.name}`;
4366
- if (seen.has(key)) {
4367
- continue;
4368
- }
4369
- seen.add(key);
4370
- const tools = servers.get(serverName) ?? [];
4371
- tools.push({
4372
- name: definition.name,
4373
- description: definition.description,
4374
- parameters: definition.parameters,
4375
- source,
4376
- approval: approvalMetadata(tool)
4377
- });
4378
- servers.set(serverName, tools);
4379
- }
4380
- return [...servers.entries()].map(([name, tools]) => {
4381
- const sortedTools = tools.sort((left, right) => {
4382
- if (left.source !== right.source) {
4383
- return left.source === "static" ? -1 : 1;
4384
- }
4385
- return left.name.localeCompare(right.name);
4386
- });
4387
- return {
4388
- agentId: agent.id,
4389
- name,
4390
- toolCount: sortedTools.length,
4391
- tools: sortedTools
4392
- };
4393
- }).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)));
4394
4402
  }
4395
4403
 
4396
4404
  // src/runtime/memory.ts
@@ -4612,7 +4620,7 @@ function agentMemorySource(ref, label, agentId, storeKindValue, inspector) {
4612
4620
  ).slice(0, options.limit);
4613
4621
  },
4614
4622
  async getConversation(conversationRef) {
4615
- const conversation = await inspector.getConversation(conversationRef);
4623
+ const conversation = await inspector.getConversation({ ref: conversationRef });
4616
4624
  return conversation === void 0 ? void 0 : inspectorConversationDetail(conversation, summary.agentIds);
4617
4625
  }
4618
4626
  };
@@ -4835,12 +4843,12 @@ function pipelineRunFailedLog(pipelineId, runId, error, startedAt) {
4835
4843
  }
4836
4844
  };
4837
4845
  }
4838
- function pipelineStageLog(pipelineId, runId, event) {
4846
+ function pipelineStageLog(event, studioPipelineId = event.pipelineId) {
4839
4847
  const category = stageCategory(event.node);
4840
4848
  if (event.type === "stage_started") {
4841
4849
  return {
4842
- pipelineId,
4843
- runId,
4850
+ pipelineId: studioPipelineId,
4851
+ runId: event.runId,
4844
4852
  level: "debug",
4845
4853
  category,
4846
4854
  event: `${event.node.kind}.started`,
@@ -4850,8 +4858,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4850
4858
  }
4851
4859
  if (event.type === "stage_completed") {
4852
4860
  return {
4853
- pipelineId,
4854
- runId,
4861
+ pipelineId: studioPipelineId,
4862
+ runId: event.runId,
4855
4863
  level: "debug",
4856
4864
  category,
4857
4865
  event: `${event.node.kind}.completed`,
@@ -4863,8 +4871,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4863
4871
  };
4864
4872
  }
4865
4873
  return {
4866
- pipelineId,
4867
- runId,
4874
+ pipelineId: studioPipelineId,
4875
+ runId: event.runId,
4868
4876
  level: "error",
4869
4877
  category,
4870
4878
  event: `${event.node.kind}.failed`,
@@ -4891,6 +4899,7 @@ function stageCategory(node) {
4891
4899
  function nodeMetadata(node) {
4892
4900
  const metadata = {
4893
4901
  nodeId: node.id,
4902
+ nodePath: [...node.path],
4894
4903
  kind: node.kind,
4895
4904
  label: node.label
4896
4905
  };
@@ -5052,17 +5061,24 @@ async function executePipelineRun(c, props, pipeline, body) {
5052
5061
  if (body.metadata !== void 0) streamOptions.metadata = body.metadata;
5053
5062
  if (props.logStore !== void 0) streamOptions.logStore = props.logStore;
5054
5063
  if (props.runStore !== void 0) streamOptions.runStore = props.runStore;
5064
+ streamOptions.abortSignal = c.req.raw.signal;
5055
5065
  return streamPipelineRun(c, streamOptions);
5056
5066
  }
5057
5067
  try {
5058
5068
  await appendPipelineLog(props.logStore, pipelineRunStartedLog(pipeline, runId));
5059
- 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,
5060
5074
  observer: {
5061
5075
  async onEvent(event) {
5062
- await appendPipelineLog(props.logStore, pipelineStageLog(pipeline.id, runId, event));
5076
+ await appendPipelineLog(props.logStore, pipelineStageLog(event, pipeline.id));
5063
5077
  }
5064
- }
5078
+ },
5079
+ failOnObserverError: true
5065
5080
  });
5081
+ const output = result.output;
5066
5082
  const jsonOutput = toJsonValue(output);
5067
5083
  const endedAt = Date.now();
5068
5084
  const successRecord = {
@@ -5127,19 +5143,24 @@ function streamPipelineRun(_c, props) {
5127
5143
  async function* pipelineRunEvents(props) {
5128
5144
  yield* emitPipelineLog(props.logStore, pipelineRunStartedLog(props.pipeline, props.runId));
5129
5145
  const events = new AsyncEventQueue();
5130
- 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,
5131
5151
  observer: {
5132
5152
  async onEvent(event) {
5133
5153
  const log = await appendPipelineLog(
5134
5154
  props.logStore,
5135
- pipelineStageLog(props.pipeline.id, props.runId, event)
5155
+ pipelineStageLog(event, props.pipeline.id)
5136
5156
  );
5137
5157
  if (log !== void 0) {
5138
5158
  events.push({ type: "pipeline_log", log });
5139
5159
  }
5140
5160
  }
5141
- }
5142
- }).then(async (output) => {
5161
+ },
5162
+ failOnObserverError: true
5163
+ }).then(async ({ output }) => {
5143
5164
  const jsonOutput = toJsonValue(output);
5144
5165
  const endedAt = Date.now();
5145
5166
  const successRecord = {
@@ -5292,272 +5313,284 @@ function parsePipelineLogAfter(value) {
5292
5313
  return after;
5293
5314
  }
5294
5315
 
5295
- // src/runtime/questions.ts
5296
- import { createHook } from "@anvia/core/internal/agent";
5297
- import { parseToolArgs } from "@anvia/core/tool";
5298
- function registerQuestionRoutes(app, questions) {
5299
- app.get("/questions", (c) => {
5300
- const status = parseQuestionStatus(c.req.query("status"));
5301
- if (status === false) {
5302
- return errorResponse(c, 400, "bad_request", "status must be pending or resolved");
5303
- }
5304
- const options = {};
5305
- const runId = optionalQueryString(c.req.query("runId"));
5306
- const agentId = optionalQueryString(c.req.query("agentId"));
5307
- const sessionId = optionalQueryString(c.req.query("sessionId"));
5308
- if (status !== void 0) {
5309
- options.status = status;
5310
- }
5311
- if (runId !== void 0) {
5312
- options.runId = runId;
5313
- }
5314
- if (agentId !== void 0) {
5315
- options.agentId = agentId;
5316
- }
5317
- if (sessionId !== void 0) {
5318
- options.sessionId = sessionId;
5319
- }
5320
- 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());
5321
5331
  });
5322
- app.post("/questions/:questionId/answer", async (c) => {
5323
- const body = await parseQuestionAnswerRequest(c);
5324
- if ("error" in body) {
5325
- return body.error;
5326
- }
5327
- const result = questions.answer(c.req.param("questionId"), body.answers);
5328
- if (result === "missing") {
5329
- return errorResponse(c, 404, "not_found", "Question not found");
5330
- }
5331
- if (result === "resolved") {
5332
- return errorResponse(c, 409, "conflict", "Question is already answered");
5333
- }
5334
- 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);
5335
5344
  });
5336
- }
5337
- function parseQuestionStatus(value) {
5338
- const status = optionalQueryString(value);
5339
- if (status === void 0) {
5340
- return void 0;
5341
- }
5342
- return status === "pending" || status === "resolved" ? status : false;
5343
- }
5344
- async function parseQuestionAnswerRequest(c) {
5345
- let body;
5346
- try {
5347
- body = await c.req.json();
5348
- } catch {
5349
- return { error: errorResponse(c, 400, "bad_request", "Request body must be JSON") };
5350
- }
5351
- if (!isObject(body)) {
5352
- return { error: errorResponse(c, 400, "bad_request", "Request body must be an object") };
5353
- }
5354
- if (!Array.isArray(body.answers)) {
5355
- return { error: errorResponse(c, 400, "bad_request", "answers must be an array") };
5356
- }
5357
- const answers = [];
5358
- for (const answer of body.answers) {
5359
- if (!isObject(answer)) {
5360
- 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");
5361
5354
  }
5362
- if (typeof answer.questionId !== "string" || answer.questionId.trim().length === 0) {
5363
- return { error: errorResponse(c, 400, "bad_request", "questionId must be a string") };
5364
- }
5365
- if (typeof answer.answer !== "string" || answer.answer.trim().length === 0) {
5366
- 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);
5367
5365
  }
5368
- if ("choice" in answer && typeof answer.choice !== "string") {
5369
- 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");
5370
5377
  }
5371
- if ("custom" in answer && typeof answer.custom !== "boolean") {
5372
- 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);
5373
5384
  }
5374
- const normalized = {
5375
- questionId: answer.questionId.trim(),
5376
- answer: answer.answer.trim()
5377
- };
5378
- if (typeof answer.choice === "string") normalized.choice = answer.choice;
5379
- if (typeof answer.custom === "boolean") normalized.custom = answer.custom;
5380
- answers.push(normalized);
5381
- }
5382
- 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
+ };
5383
5414
  }
5384
- function createQuestionRuntime() {
5385
- const questions = /* @__PURE__ */ new Map();
5415
+ function createProxyEvents(upstreamUrl, sockets) {
5416
+ let upstream;
5417
+ let pendingBytes = 0;
5418
+ const pending = [];
5386
5419
  return {
5387
- questions,
5388
- createHook(context) {
5389
- return createHook({
5390
- async onToolCall({ toolName, toolCallId, internalCallId, args, tool: control }) {
5391
- if (toolName !== "ask_question") {
5392
- return control.run();
5393
- }
5394
- const prompts = normalizeQuestionPrompts(parseToolArgs(args));
5395
- if ("error" in prompts) {
5396
- return control.skip(prompts.error);
5397
- }
5398
- const request = {
5399
- toolName,
5400
- internalCallId,
5401
- args,
5402
- questions: prompts.questions
5403
- };
5404
- if (toolCallId !== void 0) request.toolCallId = toolCallId;
5405
- const answers = await requestQuestion(questions, context, request);
5406
- return control.skip(JSON.stringify({ answers }));
5407
- }
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);
5408
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"));
5409
5441
  },
5410
- list(options) {
5411
- return [...questions.values()].filter((question) => {
5412
- if (options.status === "pending" && question.status !== "pending") {
5413
- return false;
5414
- }
5415
- if (options.status === "resolved" && question.status === "pending") {
5416
- return false;
5417
- }
5418
- if (options.runId !== void 0 && question.runId !== options.runId) {
5419
- return false;
5420
- }
5421
- if (options.agentId !== void 0 && question.agentId !== options.agentId) {
5422
- 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;
5423
5447
  }
5424
- if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
5425
- 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;
5426
5452
  }
5427
- return true;
5428
- }).map(publicQuestion);
5453
+ pending.push(message);
5454
+ }).catch(() => downstream.close(1003, "Unsupported noVNC message"));
5429
5455
  },
5430
- cancelRun(runId) {
5431
- const cancelled = [];
5432
- for (const question of questions.values()) {
5433
- if (!isPendingQuestion(question) || question.runId !== runId) {
5434
- continue;
5435
- }
5436
- const resolved = cancelQuestion(question);
5437
- questions.set(question.id, resolved);
5438
- question.emit?.({ type: "tool_question_result", question: resolved });
5439
- question.resolve([]);
5440
- cancelled.push(resolved);
5441
- }
5442
- return cancelled;
5456
+ onClose() {
5457
+ upstream?.close(1e3, "Viewer disconnected");
5458
+ if (upstream !== void 0) sockets.delete(upstream);
5443
5459
  },
5444
- answer(id, answers) {
5445
- const question = questions.get(id);
5446
- if (question === void 0) {
5447
- return "missing";
5448
- }
5449
- if (!isPendingQuestion(question)) {
5450
- return "resolved";
5451
- }
5452
- const resolved = resolveQuestion(question, answers);
5453
- questions.set(id, resolved);
5454
- question.emit?.({ type: "tool_question_result", question: resolved });
5455
- question.resolve(answers);
5456
- return publicQuestion(resolved);
5460
+ onError() {
5461
+ upstream?.close(1011, "Viewer connection failed");
5457
5462
  }
5458
5463
  };
5459
5464
  }
5460
- async function requestQuestion(questions, context, request) {
5461
- const id = globalThis.crypto.randomUUID();
5462
- const question = {
5463
- id,
5464
- runId: context.runId,
5465
- agentId: context.agentId,
5466
- toolName: request.toolName,
5467
- internalCallId: request.internalCallId,
5468
- args: request.args,
5469
- questions: request.questions,
5470
- status: "pending",
5471
- requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
5472
- resolve: () => {
5473
- }
5474
- };
5475
- if (context.sessionId !== void 0) question.sessionId = context.sessionId;
5476
- if (request.toolCallId !== void 0) question.callId = request.toolCallId;
5477
- if (context.emit !== void 0) question.emit = context.emit;
5478
- const answer = new Promise((resolve2) => {
5479
- question.resolve = (answers) => {
5480
- resolve2(answers);
5481
- };
5482
- });
5483
- questions.set(id, question);
5484
- context.emit?.({ type: "tool_question_request", question: publicQuestion(question) });
5485
- return answer;
5486
- }
5487
- function normalizeQuestionPrompts(args) {
5488
- if (!isObject(args)) {
5489
- return { error: "ask_question requires a JSON object with questions." };
5490
- }
5491
- const rawQuestions = Array.isArray(args.questions) ? args.questions : [args];
5492
- if (rawQuestions.length === 0) {
5493
- return { error: "ask_question requires at least one question." };
5494
- }
5495
- const questions = [];
5496
- for (const [index, question] of rawQuestions.entries()) {
5497
- const normalized = normalizeQuestionPrompt(question, index);
5498
- if (normalized === void 0) {
5499
- return {
5500
- error: "ask_question requires every question to include text and at least one choice."
5501
- };
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;
5502
5491
  }
5503
- questions.push(normalized);
5504
5492
  }
5505
- 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";
5506
5505
  }
5507
- function normalizeQuestionPrompt(value, index) {
5508
- if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
5509
- 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");
5510
5511
  }
5511
- const choices = Array.isArray(value.choices) ? value.choices.map(normalizeQuestionChoice).filter((choice) => choice !== void 0) : [];
5512
- if (choices.length === 0) {
5513
- return void 0;
5512
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5513
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5514
5514
  }
5515
- return {
5516
- id: typeof value.id === "string" && value.id.trim().length > 0 ? value.id.trim() : `question_${index + 1}`,
5517
- question: value.question.trim(),
5518
- choices
5519
- };
5515
+ return { ownerId: body.ownerId, leaseTimeoutMs: body.leaseTimeoutMs };
5520
5516
  }
5521
- function normalizeQuestionChoice(value) {
5522
- if (typeof value === "string" && value.trim().length > 0) {
5523
- 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");
5524
5522
  }
5525
- if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
5526
- return void 0;
5523
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5524
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5527
5525
  }
5528
- return {
5529
- label: value.label.trim(),
5530
- value: typeof value.value === "string" && value.value.trim().length > 0 ? value.value.trim() : value.label.trim()
5531
- };
5526
+ return { leaseId: body.leaseId, leaseTimeoutMs: body.leaseTimeoutMs };
5532
5527
  }
5533
- function isPendingQuestion(question) {
5534
- 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 };
5535
5535
  }
5536
- function resolveQuestion(question, answers) {
5537
- return publicQuestion({
5538
- ...question,
5539
- status: "answered",
5540
- answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
5541
- answers
5542
- });
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");
5543
5544
  }
5544
- function cancelQuestion(question) {
5545
- return publicQuestion({
5546
- ...question,
5547
- status: "cancelled",
5548
- cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
5549
- });
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;
5550
5587
  }
5551
- function publicQuestion(question) {
5552
- const { emit, resolve: resolve2, ...rest } = question;
5553
- void emit;
5554
- void resolve2;
5555
- return rest;
5588
+ function isRecord4(value) {
5589
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5556
5590
  }
5557
5591
 
5558
5592
  // src/runtime/sandboxes.ts
5559
5593
  import path from "path";
5560
- var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
5561
5594
  var maxFileResponseBytes = 10 * 1024 * 1024;
5562
5595
  var defaultProcessLogBytes = 64 * 1024;
5563
5596
  var maxProcessLogBytes = 1024 * 1024;
@@ -5576,51 +5609,41 @@ var StudioSandboxRegistry = class {
5576
5609
  return this.entries.get(ref);
5577
5610
  }
5578
5611
  };
5579
- function createStudioSandboxRegistry(agents) {
5580
- const drafts = /* @__PURE__ */ new Map();
5581
- for (const agent of agents) {
5582
- for (const { tool } of agentToolItems(agent)) {
5583
- const session = sandboxSessionFromTool(tool);
5584
- if (session === void 0) {
5585
- continue;
5586
- }
5587
- const draft = drafts.get(session) ?? {
5588
- session,
5589
- agentIds: /* @__PURE__ */ new Set(),
5590
- toolNames: /* @__PURE__ */ new Set()
5591
- };
5592
- draft.agentIds.add(agent.id);
5593
- draft.toolNames.add(tool.name);
5594
- drafts.set(session, draft);
5595
- }
5596
- }
5597
- const refCounts = /* @__PURE__ */ new Map();
5598
- const entries = [...drafts.values()].sort(
5599
- (left, right) => `${left.session.provider}\0${left.session.id}`.localeCompare(
5600
- `${right.session.provider}\0${right.session.id}`
5601
- )
5602
- ).map((draft) => {
5603
- const baseRef = sandboxRef(draft.session.provider, draft.session.id);
5604
- const occurrence = (refCounts.get(baseRef) ?? 0) + 1;
5605
- refCounts.set(baseRef, occurrence);
5606
- 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]));
5607
5626
  return {
5608
- session: draft.session,
5627
+ inspector,
5628
+ views,
5609
5629
  summary: {
5610
5630
  ref,
5611
- id: draft.session.id,
5612
- provider: draft.session.provider,
5613
- workdir: draft.session.workdir,
5614
- agentIds: [...draft.agentIds].sort(),
5615
- 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)),
5616
5637
  capabilities: {
5617
- files: true,
5618
- ports: supportsPorts(draft.session),
5619
- processes: supportsProcesses(draft.session)
5638
+ files: supportsFiles(inspector),
5639
+ ports: supportsPorts(inspector),
5640
+ processes: supportsProcesses(inspector),
5641
+ views: views.size > 0
5620
5642
  }
5621
5643
  }
5622
5644
  };
5623
5645
  });
5646
+ entries.sort((left, right) => left.summary.ref.localeCompare(right.summary.ref));
5624
5647
  return new StudioSandboxRegistry(entries);
5625
5648
  }
5626
5649
  function registerSandboxRoutes(app, registry) {
@@ -5629,24 +5652,19 @@ function registerSandboxRoutes(app, registry) {
5629
5652
  app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
5630
5653
  app.get("/sandboxes/:sandboxRef", (c) => {
5631
5654
  const entry = registry.get(c.req.param("sandboxRef"));
5632
- if (entry === void 0) {
5633
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5634
- }
5635
- return c.json(copySummary(entry.summary));
5655
+ return entry === void 0 ? errorResponse(c, 404, "not_found", "Sandbox not found") : c.json(copySummary(entry.summary));
5636
5656
  });
5637
5657
  app.get("/sandboxes/:sandboxRef/files", async (c) => {
5638
5658
  const entry = registry.get(c.req.param("sandboxRef"));
5639
- if (entry === void 0) {
5640
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5641
- }
5659
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5660
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files");
5642
5661
  try {
5643
5662
  const requestedPath = c.req.query("path") ?? ".";
5644
5663
  const normalizedPath = normalizeSandboxPath(requestedPath, true);
5645
- const entries = await listSandboxFiles(entry.session, normalizedPath);
5646
5664
  return c.json({
5647
5665
  sandboxRef: entry.summary.ref,
5648
5666
  path: normalizedPath,
5649
- entries
5667
+ entries: await listSandboxFiles(entry.inspector, normalizedPath, c.req.raw.signal)
5650
5668
  });
5651
5669
  } catch (error) {
5652
5670
  return sandboxErrorResponse(c, error);
@@ -5654,9 +5672,8 @@ function registerSandboxRoutes(app, registry) {
5654
5672
  });
5655
5673
  app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
5656
5674
  const entry = registry.get(c.req.param("sandboxRef"));
5657
- if (entry === void 0) {
5658
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5659
- }
5675
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5676
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files.content");
5660
5677
  try {
5661
5678
  const requestedPath = c.req.query("path");
5662
5679
  if (requestedPath === void 0) {
@@ -5667,8 +5684,11 @@ function registerSandboxRoutes(app, registry) {
5667
5684
  throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
5668
5685
  }
5669
5686
  const normalizedPath = normalizeSandboxPath(requestedPath, false);
5670
- const parentPath = path.posix.dirname(normalizedPath);
5671
- 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
+ );
5672
5692
  const file = siblings.find((candidate) => candidate.path === normalizedPath);
5673
5693
  if (file === void 0) {
5674
5694
  throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
@@ -5680,25 +5700,24 @@ function registerSandboxRoutes(app, registry) {
5680
5700
  "Sandbox content path must refer to a regular file"
5681
5701
  );
5682
5702
  }
5683
- if (file.size !== void 0 && file.size > maxFileResponseBytes) {
5703
+ if (file.size !== void 0 && file.size > maxFileResponseBytes)
5684
5704
  throw fileTooLarge(file.size);
5685
- }
5686
- const rawBytes = await entry.session.readFile(normalizedPath);
5705
+ const rawBytes = await entry.inspector.readFile({
5706
+ path: normalizedPath,
5707
+ abortSignal: c.req.raw.signal
5708
+ });
5687
5709
  if (!(rawBytes instanceof Uint8Array)) {
5688
5710
  throw new TypeError("Sandbox readFile returned an invalid byte payload");
5689
5711
  }
5690
- if (rawBytes.byteLength > maxFileResponseBytes) {
5691
- throw fileTooLarge(rawBytes.byteLength);
5692
- }
5693
- const bytes = new Uint8Array(rawBytes.byteLength);
5694
- bytes.set(rawBytes);
5712
+ if (rawBytes.byteLength > maxFileResponseBytes) throw fileTooLarge(rawBytes.byteLength);
5713
+ const bytes = rawBytes.slice();
5695
5714
  const disposition = download === "1" ? "attachment" : "inline";
5696
5715
  const filename = encodeURIComponent(path.posix.basename(normalizedPath));
5697
5716
  return new Response(bytes, {
5698
5717
  headers: {
5699
5718
  "cache-control": "no-store",
5700
5719
  "content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
5701
- "content-length": String(bytes.byteLength),
5720
+ "content-length": `${bytes.byteLength}`,
5702
5721
  "content-type": "application/octet-stream",
5703
5722
  "x-content-type-options": "nosniff"
5704
5723
  }
@@ -5709,16 +5728,12 @@ function registerSandboxRoutes(app, registry) {
5709
5728
  });
5710
5729
  app.get("/sandboxes/:sandboxRef/ports", (c) => {
5711
5730
  const entry = registry.get(c.req.param("sandboxRef"));
5712
- if (entry === void 0) {
5713
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5714
- }
5715
- if (!supportsPorts(entry.session)) {
5716
- return unsupportedSandboxOperation(c, "ports");
5717
- }
5731
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5732
+ if (!supportsPorts(entry.inspector)) return unsupportedSandboxOperation(c, "ports");
5718
5733
  try {
5719
5734
  return c.json({
5720
5735
  sandboxRef: entry.summary.ref,
5721
- ports: normalizePorts(entry.session.publishedPorts)
5736
+ ports: normalizePorts(entry.inspector.publishedPorts)
5722
5737
  });
5723
5738
  } catch (error) {
5724
5739
  return sandboxErrorResponse(c, error);
@@ -5726,16 +5741,16 @@ function registerSandboxRoutes(app, registry) {
5726
5741
  });
5727
5742
  app.get("/sandboxes/:sandboxRef/processes", async (c) => {
5728
5743
  const entry = registry.get(c.req.param("sandboxRef"));
5729
- if (entry === void 0) {
5730
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5731
- }
5732
- if (!supportsProcesses(entry.session)) {
5744
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5745
+ if (!supportsProcesses(entry.inspector)) {
5733
5746
  return unsupportedSandboxOperation(c, "processes");
5734
5747
  }
5735
5748
  try {
5736
5749
  return c.json({
5737
5750
  sandboxRef: entry.summary.ref,
5738
- processes: normalizeProcesses(await entry.session.listProcesses())
5751
+ processes: normalizeProcesses(
5752
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5753
+ )
5739
5754
  });
5740
5755
  } catch (error) {
5741
5756
  return sandboxErrorResponse(c, error);
@@ -5743,21 +5758,25 @@ function registerSandboxRoutes(app, registry) {
5743
5758
  });
5744
5759
  app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
5745
5760
  const entry = registry.get(c.req.param("sandboxRef"));
5746
- if (entry === void 0) {
5747
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5748
- }
5749
- if (!supportsProcesses(entry.session)) {
5761
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5762
+ if (!supportsProcesses(entry.inspector)) {
5750
5763
  return unsupportedSandboxOperation(c, "processes.logs");
5751
5764
  }
5752
5765
  try {
5753
5766
  const tailBytes = parseTailBytes(c.req.query("tailBytes"));
5754
5767
  const processId = c.req.param("processId");
5755
- const processes = normalizeProcesses(await entry.session.listProcesses());
5768
+ const processes = normalizeProcesses(
5769
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5770
+ );
5756
5771
  if (!processes.some((process2) => process2.id === processId)) {
5757
5772
  throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
5758
5773
  }
5759
5774
  const logs = normalizeProcessLogs(
5760
- await entry.session.readProcessLogs(processId, { tailBytes }),
5775
+ await entry.inspector.readProcessLogs({
5776
+ processId,
5777
+ tailBytes,
5778
+ abortSignal: c.req.raw.signal
5779
+ }),
5761
5780
  tailBytes
5762
5781
  );
5763
5782
  return c.json({
@@ -5774,21 +5793,86 @@ async function sandboxNoStore(c, next) {
5774
5793
  c.header("cache-control", "no-store");
5775
5794
  await next();
5776
5795
  }
5777
- function sandboxSessionFromTool(tool) {
5778
- const metadata = tool[sandboxToolMetadataKey];
5779
- if (!isRecord4(metadata)) {
5780
- 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
+ }
5781
5863
  }
5782
- return isSandboxSession(metadata.session) ? metadata.session : void 0;
5783
5864
  }
5784
- function isSandboxSession(value) {
5785
- 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;
5786
5867
  }
5787
- function supportsPorts(session) {
5788
- return Array.isArray(session.publishedPorts);
5868
+ function supportsFiles(inspector) {
5869
+ return typeof inspector.listFiles === "function" && typeof inspector.readFile === "function";
5789
5870
  }
5790
- function supportsProcesses(session) {
5791
- 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";
5792
5876
  }
5793
5877
  function sandboxRef(provider, id) {
5794
5878
  return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
@@ -5798,22 +5882,26 @@ function copySummary(summary) {
5798
5882
  ...summary,
5799
5883
  agentIds: [...summary.agentIds],
5800
5884
  toolNames: [...summary.toolNames],
5885
+ views: summary.views.map((view) => ({ ...view })),
5801
5886
  capabilities: { ...summary.capabilities }
5802
5887
  };
5803
5888
  }
5804
- async function listSandboxFiles(session, filePath) {
5805
- return normalizeFileEntries(await session.listFiles(filePath)).sort((left, right) => {
5806
- if (left.type === "directory" && right.type !== "directory") return -1;
5807
- if (left.type !== "directory" && right.type === "directory") return 1;
5808
- return left.path.localeCompare(right.path);
5809
- });
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
+ );
5810
5900
  }
5811
5901
  function normalizeFileEntries(value) {
5812
- if (!Array.isArray(value)) {
5813
- throw new TypeError("Sandbox listFiles returned an invalid payload");
5814
- }
5902
+ if (!Array.isArray(value)) throw new TypeError("Sandbox listFiles returned an invalid payload");
5815
5903
  return value.map((item) => {
5816
- if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5904
+ if (!isRecord5(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5817
5905
  throw new TypeError("Sandbox listFiles returned an invalid entry");
5818
5906
  }
5819
5907
  let entryPath;
@@ -5824,7 +5912,7 @@ function normalizeFileEntries(value) {
5824
5912
  }
5825
5913
  const entry = { path: entryPath, type: item.type };
5826
5914
  if (item.size !== void 0) {
5827
- if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
5915
+ if (!Number.isSafeInteger(item.size) || Number(item.size) < 0) {
5828
5916
  throw new TypeError("Sandbox listFiles returned an invalid file size");
5829
5917
  }
5830
5918
  entry.size = item.size;
@@ -5833,11 +5921,10 @@ function normalizeFileEntries(value) {
5833
5921
  });
5834
5922
  }
5835
5923
  function normalizePorts(value) {
5836
- if (!Array.isArray(value)) {
5924
+ if (!Array.isArray(value))
5837
5925
  throw new TypeError("Sandbox publishedPorts returned an invalid payload");
5838
- }
5839
5926
  return value.map((item) => {
5840
- 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") {
5841
5928
  throw new TypeError("Sandbox publishedPorts returned an invalid entry");
5842
5929
  }
5843
5930
  return {
@@ -5849,17 +5936,16 @@ function normalizePorts(value) {
5849
5936
  });
5850
5937
  }
5851
5938
  function normalizeProcesses(value) {
5852
- if (!Array.isArray(value)) {
5939
+ if (!Array.isArray(value))
5853
5940
  throw new TypeError("Sandbox listProcesses returned an invalid payload");
5854
- }
5855
5941
  return value.map((item) => {
5856
- 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") {
5857
5943
  throw new TypeError("Sandbox listProcesses returned an invalid entry");
5858
5944
  }
5859
5945
  const process2 = {
5860
5946
  id: item.id,
5861
5947
  command: item.command,
5862
- args: item.args,
5948
+ args: [...item.args],
5863
5949
  status: item.status,
5864
5950
  startedAt: item.startedAt
5865
5951
  };
@@ -5870,11 +5956,11 @@ function normalizeProcesses(value) {
5870
5956
  });
5871
5957
  }
5872
5958
  function normalizeProcessLogs(value, maxBytes) {
5873
- 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") {
5874
5960
  throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
5875
5961
  }
5876
- const stdout = boundedLogText(value.stdout, maxBytes);
5877
- const stderr = boundedLogText(value.stderr, maxBytes);
5962
+ const stdout = boundedLogBytes(value.stdout, maxBytes);
5963
+ const stderr = boundedLogBytes(value.stderr, maxBytes);
5878
5964
  return {
5879
5965
  stdout: stdout.text,
5880
5966
  stderr: stderr.text,
@@ -5882,21 +5968,13 @@ function normalizeProcessLogs(value, maxBytes) {
5882
5968
  stderrTruncated: value.stderrTruncated || stderr.truncated
5883
5969
  };
5884
5970
  }
5885
- function boundedLogText(value, maxBytes) {
5886
- const bytes = new TextEncoder().encode(value);
5887
- if (bytes.byteLength <= maxBytes) {
5888
- return { text: value, truncated: false };
5889
- }
5890
- if (maxBytes === 0) {
5891
- return { text: "", truncated: true };
5892
- }
5893
- let start = bytes.byteLength - maxBytes;
5894
- while (start < bytes.byteLength && (bytes[start] ?? 0) >> 6 === 2) {
5895
- start += 1;
5896
- }
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;
5897
5975
  return {
5898
- text: new TextDecoder().decode(bytes.subarray(start)),
5899
- truncated: true
5976
+ text: new TextDecoder("utf-8", { fatal: true }).decode(value.subarray(start)),
5977
+ truncated: start > 0
5900
5978
  };
5901
5979
  }
5902
5980
  function normalizeSandboxPath(input, allowRoot) {
@@ -5919,9 +5997,7 @@ function normalizeSandboxPath(input, allowRoot) {
5919
5997
  return normalized;
5920
5998
  }
5921
5999
  function parseTailBytes(value) {
5922
- if (value === void 0) {
5923
- return defaultProcessLogBytes;
5924
- }
6000
+ if (value === void 0) return defaultProcessLogBytes;
5925
6001
  const parsed = Number(value);
5926
6002
  if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
5927
6003
  throw new SandboxRouteError(
@@ -5942,15 +6018,13 @@ function sandboxErrorResponse(c, error) {
5942
6018
  if (error instanceof SandboxRouteError) {
5943
6019
  return errorResponse(c, error.status, error.code, error.message);
5944
6020
  }
5945
- const name = errorName(error);
5946
- if (name === "SandboxPathError") {
5947
- return errorResponse(c, 400, "bad_request", errorMessage(error));
5948
- }
5949
- 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") {
5950
6024
  return errorResponse(c, 413, "payload_too_large", errorMessage(error));
5951
6025
  }
5952
- if (name === "SandboxSessionDestroyedError") {
5953
- 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");
5954
6028
  }
5955
6029
  return errorResponse(
5956
6030
  c,
@@ -5976,6 +6050,18 @@ var SandboxRouteError = class extends Error {
5976
6050
  status;
5977
6051
  code;
5978
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
+ }
5979
6065
  function isFileType(value) {
5980
6066
  return value === "file" || value === "directory" || value === "symlink" || value === "other";
5981
6067
  }
@@ -5985,14 +6071,14 @@ function isProcessStatus(value) {
5985
6071
  function isPort(value) {
5986
6072
  return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
5987
6073
  }
5988
- function isRecord4(value) {
5989
- return typeof value === "object" && value !== null;
6074
+ function isRecord5(value) {
6075
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5990
6076
  }
5991
- function errorName(error) {
5992
- 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;
5993
6079
  }
5994
6080
  function errorMessage(error) {
5995
- return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
6081
+ return isRecord5(error) && typeof error.message === "string" ? error.message : "Sandbox failed";
5996
6082
  }
5997
6083
 
5998
6084
  // src/runtime/sessions.ts
@@ -6143,6 +6229,16 @@ function createInMemoryStudioStore() {
6143
6229
  }
6144
6230
  var InMemoryStudioStore = class {
6145
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
+ };
6146
6242
  sessions = /* @__PURE__ */ new Map();
6147
6243
  traces = /* @__PURE__ */ new Map();
6148
6244
  pipelineLogs = /* @__PURE__ */ new Map();
@@ -6184,20 +6280,20 @@ var InMemoryStudioStore = class {
6184
6280
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6185
6281
  return materializeSession(session);
6186
6282
  }
6187
- load(context) {
6188
- return Promise.resolve(this.sessions.get(context.sessionId)?.messages ?? []);
6283
+ load({ scope }) {
6284
+ return Promise.resolve(cloneMessages(this.sessions.get(scope.sessionId)?.messages ?? []));
6189
6285
  }
6190
6286
  append(input) {
6191
- const session = this.sessions.get(input.context.sessionId);
6287
+ const session = this.sessions.get(input.scope.sessionId);
6192
6288
  if (session !== void 0) {
6193
- session.messages.push(...input.messages);
6289
+ session.messages.push(...cloneMessages(input.messages));
6194
6290
  session.messageCount = session.messages.length;
6195
6291
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6196
6292
  }
6197
6293
  return Promise.resolve();
6198
6294
  }
6199
- clear(context) {
6200
- const session = this.sessions.get(context.sessionId);
6295
+ clear({ scope }) {
6296
+ const session = this.sessions.get(scope.sessionId);
6201
6297
  if (session !== void 0) {
6202
6298
  session.messages = [];
6203
6299
  session.runs = [];
@@ -6208,13 +6304,26 @@ var InMemoryStudioStore = class {
6208
6304
  }
6209
6305
  async recordError(input) {
6210
6306
  await this.saveSessionRunTranscript({
6211
- id: input.context.sessionId,
6212
- runId: studioRunId(input.context) ?? input.runId,
6307
+ id: input.scope.sessionId,
6308
+ runId: studioRunId(input.scope) ?? input.runId,
6213
6309
  transcript: transcriptFromMessages(input.messages),
6214
6310
  status: "error",
6215
6311
  error: serializeJsonError(input.error)
6216
6312
  });
6217
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
+ }
6218
6327
  saveSessionRunTranscript(input) {
6219
6328
  const session = this.sessions.get(input.id);
6220
6329
  if (session === void 0) {
@@ -6340,18 +6449,24 @@ function sessionSummary(session) {
6340
6449
  function materializeSession(session) {
6341
6450
  return {
6342
6451
  ...sessionSummary(session),
6343
- messages: [...session.messages],
6452
+ messages: cloneMessages(session.messages),
6344
6453
  transcript: renumberTranscript(session.runs.flatMap((run) => run.transcript))
6345
6454
  };
6346
6455
  }
6456
+ function cloneMessages(messages) {
6457
+ return structuredClone([...messages]);
6458
+ }
6347
6459
  function traceAgentId(trace) {
6348
6460
  const nestedMetadata = trace.metadata?.metadata;
6349
6461
  return isJsonObject(nestedMetadata) && typeof nestedMetadata.agentId === "string" ? nestedMetadata.agentId : void 0;
6350
6462
  }
6351
- function studioRunId(context) {
6352
- const value = context.metadata?.studioRunId;
6463
+ function studioRunId(scope) {
6464
+ const value = scope.metadata?.studioRunId;
6353
6465
  return typeof value === "string" && value.length > 0 ? value : void 0;
6354
6466
  }
6467
+ function memoryRevision(messages) {
6468
+ return JSON.stringify(messages);
6469
+ }
6355
6470
  function serializeJsonError(error) {
6356
6471
  if (error instanceof Error) {
6357
6472
  return {
@@ -6687,6 +6802,7 @@ var Studio = class {
6687
6802
  options;
6688
6803
  studio;
6689
6804
  server;
6805
+ websocketServer;
6690
6806
  sigintHandler;
6691
6807
  constructor(targets = [], options = {}) {
6692
6808
  this.options = studioOptionsFromTargets(targets, options);
@@ -6711,9 +6827,15 @@ var Studio = class {
6711
6827
  this.studio = createStudioApp(this.options);
6712
6828
  const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
6713
6829
  const serverOptions = {
6714
- fetch: (request) => this.fetch(request),
6830
+ fetch: (request, env) => this.studio.app.fetch(request, env),
6715
6831
  port
6716
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
+ };
6717
6839
  if (serveOptions.hostname !== void 0) serverOptions.hostname = serveOptions.hostname;
6718
6840
  this.server = serve(serverOptions);
6719
6841
  const log = serveOptions.log ?? true;
@@ -6758,6 +6880,8 @@ var Studio = class {
6758
6880
  }
6759
6881
  this.server?.close();
6760
6882
  this.server = void 0;
6883
+ this.websocketServer?.close();
6884
+ this.websocketServer = void 0;
6761
6885
  this.studio.close();
6762
6886
  }
6763
6887
  logAddress(host, port) {
@@ -6836,6 +6960,21 @@ function studioOptionsFromTargets(targets, options) {
6836
6960
  if (options.models !== void 0) runtimeOptions.models = options.models;
6837
6961
  if (options.stores !== void 0) runtimeOptions.stores = options.stores;
6838
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
+ }
6839
6978
  return runtimeOptions;
6840
6979
  }
6841
6980
  function inferStudioAgents(agents, quickPrompts) {
@@ -6877,11 +7016,11 @@ function uniqueAgentId(baseId, ids) {
6877
7016
  function agentMetadata(agent) {
6878
7017
  const metadata = {
6879
7018
  staticContextCount: staticContextDocuments(agent).length,
6880
- dynamicContextCount: contextIndexes(agent).length,
7019
+ dynamicContextCount: vectorContexts(agent).length,
6881
7020
  dynamicToolCount: getAgentToolState3(agent).toolIndexes.length,
6882
7021
  hasLifecycle: agent.lifecycle !== void 0,
6883
7022
  hasOutputSchema: agent.outputSchema !== void 0,
6884
- observerCount: agent.observers.length,
7023
+ observerCount: Object.keys(agent.observability?.observers ?? {}).length,
6885
7024
  approvalToolCount: agent.tools.filter(toolRequiresApproval).length
6886
7025
  };
6887
7026
  if (agent.defaultMaxTurns !== void 0) metadata.defaultMaxTurns = agent.defaultMaxTurns;
@@ -6898,9 +7037,8 @@ function createStudioApp(options) {
6898
7037
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
6899
7038
  const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
6900
7039
  const evalMap = new Map(options.evals.map((suite) => [suite.id ?? suite.name, suite]));
6901
- const approvalRuntime = createApprovalRuntime();
6902
- const questionRuntime = createQuestionRuntime();
6903
- const sandboxRegistry = createStudioSandboxRegistry(agents);
7040
+ const continuationRegistry = createStudioContinuationRegistry();
7041
+ const sandboxRegistry = createStudioSandboxRegistry(agents, options.sandboxes ?? []);
6904
7042
  const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
6905
7043
  const app = new HonoApp();
6906
7044
  const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
@@ -6951,8 +7089,7 @@ function createStudioApp(options) {
6951
7089
  registerMcpRoutes(app, { agentMap });
6952
7090
  registerToolRoutes(app, { agentMap });
6953
7091
  registerSandboxRoutes(app, sandboxRegistry);
6954
- registerApprovalRoutes(app, approvalRuntime);
6955
- registerQuestionRoutes(app, questionRuntime);
7092
+ const closeSandboxViews = registerSandboxViewRoutes(app, sandboxRegistry);
6956
7093
  registerObservabilityRoutes(app, observabilityHub);
6957
7094
  registerEvalRoutes(app, {
6958
7095
  evals: options.evals,
@@ -6972,8 +7109,7 @@ function createStudioApp(options) {
6972
7109
  agentMap,
6973
7110
  stores,
6974
7111
  modelRegistry,
6975
- approvalRuntime,
6976
- questionRuntime
7112
+ continuationRegistry
6977
7113
  });
6978
7114
  if (memorySources.size > 0 || stores.sessions !== void 0) {
6979
7115
  registerMemoryRoutes(app, {
@@ -7005,6 +7141,8 @@ function createStudioApp(options) {
7005
7141
  return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
7006
7142
  },
7007
7143
  close() {
7144
+ continuationRegistry.clear();
7145
+ closeSandboxViews();
7008
7146
  }
7009
7147
  };
7010
7148
  if (stores.sessions !== void 0) Object.assign(studio, { sessionStore: stores.sessions });
@@ -7012,30 +7150,37 @@ function createStudioApp(options) {
7012
7150
  return studio;
7013
7151
  }
7014
7152
  function withStudioTraceObserver(studioAgent, traceStore) {
7015
- if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
7016
- 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
+ }
7017
7163
  }
7018
7164
  return {
7019
7165
  ...studioAgent,
7020
7166
  agent: cloneAgent(studioAgent.agent, {
7021
- observers: [
7022
- ...studioAgent.agent.observers,
7023
- { observer: new StudioTraceObserver({ store: traceStore }) }
7024
- ]
7167
+ observability: {
7168
+ observers: {
7169
+ ...existing?.observers ?? {},
7170
+ studio: studioObserver ?? new StudioTraceObserver({ store: traceStore })
7171
+ },
7172
+ primaryTrace: "studio",
7173
+ errorPolicy: existing?.errorPolicy ?? "ignore"
7174
+ }
7025
7175
  })
7026
7176
  };
7027
7177
  }
7028
- function hasStudioTraceObserver(agent) {
7029
- return agent.observers.some(
7030
- (registration) => registration.observer instanceof StudioTraceObserver
7031
- );
7032
- }
7033
7178
 
7034
7179
  // src/storage/sqlite-store.ts
7035
7180
  import { mkdirSync } from "fs";
7036
7181
  import { createRequire } from "module";
7037
7182
  import { dirname, resolve } from "path";
7038
- import { isJsonValue as isJsonValue2 } from "@anvia/core/completion";
7183
+ import { isJsonValue as isJsonValue3 } from "@anvia/core/completion";
7039
7184
  var DatabaseSync;
7040
7185
  function createSqliteSessionStore(options = {}) {
7041
7186
  return new SqliteSessionStore(options.path ?? ":memory:");
@@ -7046,6 +7191,13 @@ var SqliteSessionStore = class {
7046
7191
  }
7047
7192
  path;
7048
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
+ };
7049
7201
  db;
7050
7202
  listSessions(options) {
7051
7203
  const db = this.database();
@@ -7117,8 +7269,8 @@ var SqliteSessionStore = class {
7117
7269
  }
7118
7270
  return this.getSession(id);
7119
7271
  }
7120
- load(context) {
7121
- const session = this.getSession(context.sessionId);
7272
+ load({ scope }) {
7273
+ const session = this.getSession(scope.sessionId);
7122
7274
  return Promise.resolve(session?.messages ?? []);
7123
7275
  }
7124
7276
  append(input) {
@@ -7129,20 +7281,20 @@ var SqliteSessionStore = class {
7129
7281
  `SELECT id, agent_id, title, metadata_json, created_at, updated_at
7130
7282
  FROM anvia_studio_sessions
7131
7283
  WHERE id = $id`
7132
- ).get({ $id: input.context.sessionId });
7284
+ ).get({ $id: input.scope.sessionId });
7133
7285
  if (row === void 0) {
7134
7286
  db.exec("ROLLBACK");
7135
7287
  return Promise.resolve();
7136
7288
  }
7137
7289
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7138
- const nextIndex = this.nextMessageIndex(input.context.sessionId);
7139
- 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);
7140
7292
  db.prepare(
7141
7293
  `UPDATE anvia_studio_sessions
7142
7294
  SET updated_at = $updatedAt
7143
7295
  WHERE id = $id`
7144
7296
  ).run({
7145
- $id: input.context.sessionId,
7297
+ $id: input.scope.sessionId,
7146
7298
  $updatedAt: updatedAt
7147
7299
  });
7148
7300
  db.exec("COMMIT");
@@ -7154,7 +7306,7 @@ var SqliteSessionStore = class {
7154
7306
  throw error;
7155
7307
  }
7156
7308
  }
7157
- clear(context) {
7309
+ clear({ scope }) {
7158
7310
  const db = this.database();
7159
7311
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7160
7312
  try {
@@ -7164,14 +7316,14 @@ var SqliteSessionStore = class {
7164
7316
  SET updated_at = $updatedAt
7165
7317
  WHERE id = $id`
7166
7318
  ).run({
7167
- $id: context.sessionId,
7319
+ $id: scope.sessionId,
7168
7320
  $updatedAt: updatedAt
7169
7321
  });
7170
7322
  db.prepare("DELETE FROM anvia_studio_session_messages WHERE session_id = $id").run({
7171
- $id: context.sessionId
7323
+ $id: scope.sessionId
7172
7324
  });
7173
7325
  db.prepare("DELETE FROM anvia_studio_session_runs WHERE session_id = $id").run({
7174
- $id: context.sessionId
7326
+ $id: scope.sessionId
7175
7327
  });
7176
7328
  db.exec("COMMIT");
7177
7329
  return Promise.resolve();
@@ -7183,17 +7335,51 @@ var SqliteSessionStore = class {
7183
7335
  }
7184
7336
  }
7185
7337
  async recordError(input) {
7186
- const runId = studioRunId2(input.context) ?? input.runId;
7187
- 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);
7188
7340
  const transcript = existing === void 0 || parseJsonArray(existing.transcript_json).length === 0 ? transcriptFromMessages(input.messages) : parseJsonArray(existing.transcript_json);
7189
7341
  await this.saveSessionRunTranscript({
7190
- id: input.context.sessionId,
7342
+ id: input.scope.sessionId,
7191
7343
  runId,
7192
7344
  transcript,
7193
7345
  status: "error",
7194
7346
  error: serializeJsonError2(input.error)
7195
7347
  });
7196
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
+ }
7197
7383
  saveSessionRunTranscript(input) {
7198
7384
  const db = this.database();
7199
7385
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7889,7 +8075,7 @@ var SqliteSessionStore = class {
7889
8075
  );
7890
8076
  messages.forEach((message, messageOffset) => {
7891
8077
  const messageIndex = startIndex + messageOffset;
7892
- if (message.metadata !== void 0 && !isJsonValue2(message.metadata)) {
8078
+ if (message.metadata !== void 0 && !isJsonValue3(message.metadata)) {
7893
8079
  throw new TypeError("Studio message metadata must be a strict JSON value.");
7894
8080
  }
7895
8081
  insertMessage.run({
@@ -7990,6 +8176,9 @@ function messageParts(message) {
7990
8176
  if (message.role === "system") {
7991
8177
  return [{ type: "text", value: { type: "text", text: message.content } }];
7992
8178
  }
8179
+ if (typeof message.content === "string") {
8180
+ return [{ type: "text", value: { type: "text", text: message.content } }];
8181
+ }
7993
8182
  return message.content.map((content) => ({
7994
8183
  type: content.type,
7995
8184
  value: content
@@ -7998,40 +8187,40 @@ function messageParts(message) {
7998
8187
  function messageFromRows(row, partRows) {
7999
8188
  const parts = partRows.map((partRow) => JSON.parse(partRow.part_json));
8000
8189
  const metadata = parseJsonValue(row.metadata_json);
8001
- if (metadata !== void 0 && !isJsonValue2(metadata)) {
8002
- 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.");
8003
8192
  }
8004
8193
  if (row.role === "system") {
8005
- const message = {
8194
+ let message = {
8006
8195
  role: "system",
8007
8196
  content: systemContentFromParts(parts)
8008
8197
  };
8009
- if (metadata !== void 0) message.metadata = metadata;
8198
+ if (metadata !== void 0) message = { ...message, metadata };
8010
8199
  return message;
8011
8200
  }
8012
8201
  if (row.role === "user") {
8013
- const message = {
8202
+ let message = {
8014
8203
  role: "user",
8015
8204
  content: parts
8016
8205
  };
8017
- if (metadata !== void 0) message.metadata = metadata;
8206
+ if (metadata !== void 0) message = { ...message, metadata };
8018
8207
  return message;
8019
8208
  }
8020
8209
  if (row.role === "assistant") {
8021
- const message = {
8210
+ let message = {
8022
8211
  role: "assistant",
8023
8212
  content: parts
8024
8213
  };
8025
- if (row.message_id !== null) message.id = row.message_id;
8026
- 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 };
8027
8216
  return message;
8028
8217
  }
8029
8218
  if (row.role === "tool") {
8030
- const message = {
8219
+ let message = {
8031
8220
  role: "tool",
8032
8221
  content: parts
8033
8222
  };
8034
- if (metadata !== void 0) message.metadata = metadata;
8223
+ if (metadata !== void 0) message = { ...message, metadata };
8035
8224
  return message;
8036
8225
  }
8037
8226
  throw new Error(`Unsupported stored message role: ${row.role}`);
@@ -8116,10 +8305,13 @@ function parseJsonValue(value) {
8116
8305
  }
8117
8306
  return JSON.parse(value);
8118
8307
  }
8119
- function studioRunId2(context) {
8120
- const value = context.metadata?.studioRunId;
8308
+ function studioRunId2(scope) {
8309
+ const value = scope.metadata?.studioRunId;
8121
8310
  return typeof value === "string" && value.length > 0 ? value : void 0;
8122
8311
  }
8312
+ function memoryRevision2(messages) {
8313
+ return JSON.stringify(messages);
8314
+ }
8123
8315
  function serializeJsonError2(error) {
8124
8316
  if (error instanceof Error) {
8125
8317
  return {