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

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 (98) hide show
  1. package/README.md +56 -10
  2. package/dist/index.d.ts +177 -116
  3. package/dist/index.js +1418 -1219
  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-QGX73TSQ-D9QAGlAY.js +65 -0
  20. package/dist/ui/assets/chunk-QGX73TSQ-D9QAGlAY.js.map +1 -0
  21. package/dist/ui/assets/dialog-CNNQ3hoF.js +2 -0
  22. package/dist/ui/assets/dialog-CNNQ3hoF.js.map +1 -0
  23. package/dist/ui/assets/dist-CqMdRzwk.js +2 -0
  24. package/dist/ui/assets/dist-CqMdRzwk.js.map +1 -0
  25. package/dist/ui/assets/dist-DWqJwN1i.js +2 -0
  26. package/dist/ui/assets/dist-DWqJwN1i.js.map +1 -0
  27. package/dist/ui/assets/es2015-DsZq7PFO.js +42 -0
  28. package/dist/ui/assets/es2015-DsZq7PFO.js.map +1 -0
  29. package/dist/ui/assets/icon-CqkMSRbt.js +2 -0
  30. package/dist/ui/assets/icon-CqkMSRbt.js.map +1 -0
  31. package/dist/ui/assets/index-BtvoCaW7.js +12 -0
  32. package/dist/ui/assets/index-BtvoCaW7.js.map +1 -0
  33. package/dist/ui/assets/index-CFbK-pX6.css +2 -0
  34. package/dist/ui/assets/{input-BOUv3rgO.js → input-BiI6gsxA.js} +1 -1
  35. package/dist/ui/assets/{input-BOUv3rgO.js.map → input-BiI6gsxA.js.map} +1 -1
  36. package/dist/ui/assets/knowledge-model-S51n2rKh.js +2 -0
  37. package/dist/ui/assets/knowledge-model-S51n2rKh.js.map +1 -0
  38. package/dist/ui/assets/knowledge-page-DZeLh92t.js +2 -0
  39. package/dist/ui/assets/{knowledge-page-vwFOGhkh.js.map → knowledge-page-DZeLh92t.js.map} +1 -1
  40. package/dist/ui/assets/mcps-page-CDPMO2x9.js +2 -0
  41. package/dist/ui/assets/{mcps-page-Bsu_-w7A.js.map → mcps-page-CDPMO2x9.js.map} +1 -1
  42. package/dist/ui/assets/memory-page-D3UhjpTb.js +4 -0
  43. package/dist/ui/assets/memory-page-D3UhjpTb.js.map +1 -0
  44. package/dist/ui/assets/pipelines-page-BP8R6NI7.js +8 -0
  45. package/dist/ui/assets/pipelines-page-BP8R6NI7.js.map +1 -0
  46. package/dist/ui/assets/{renderers-5kT5HxmZ.js → renderers-DHMEAblV.js} +3 -3
  47. package/dist/ui/assets/{renderers-5kT5HxmZ.js.map → renderers-DHMEAblV.js.map} +1 -1
  48. package/dist/ui/assets/request-7oUPS8zY.js +4 -0
  49. package/dist/ui/assets/request-7oUPS8zY.js.map +1 -0
  50. package/dist/ui/assets/rfb-DiW3PDuT.js +6 -0
  51. package/dist/ui/assets/rfb-DiW3PDuT.js.map +1 -0
  52. package/dist/ui/assets/sandboxes-page-C6kokYwc.js +2 -0
  53. package/dist/ui/assets/sandboxes-page-C6kokYwc.js.map +1 -0
  54. package/dist/ui/assets/select-BOYfrfck.js +2 -0
  55. package/dist/ui/assets/select-BOYfrfck.js.map +1 -0
  56. package/dist/ui/assets/sessions-page-ClXzRM9K.js +2 -0
  57. package/dist/ui/assets/{sessions-page-BLGrHrQT.js.map → sessions-page-ClXzRM9K.js.map} +1 -1
  58. package/dist/ui/assets/status-page-eA8uRLZQ.js +2 -0
  59. package/dist/ui/assets/{status-page-D-BUKV_r.js.map → status-page-eA8uRLZQ.js.map} +1 -1
  60. package/dist/ui/assets/studio-CeTsQ4gf.js +2 -0
  61. package/dist/ui/assets/studio-CeTsQ4gf.js.map +1 -0
  62. package/dist/ui/assets/textarea-DESKg_ZK.js +2 -0
  63. package/dist/ui/assets/textarea-DESKg_ZK.js.map +1 -0
  64. package/dist/ui/assets/tool-runner-D3-SlGqQ.js +2 -0
  65. package/dist/ui/assets/{tool-runner-6-N-OOCI.js.map → tool-runner-D3-SlGqQ.js.map} +1 -1
  66. package/dist/ui/assets/tools-page-D3gnQf1_.js +2 -0
  67. package/dist/ui/assets/{tools-page-BEQJgFX0.js.map → tools-page-D3gnQf1_.js.map} +1 -1
  68. package/dist/ui/assets/trace-browser-D93H7Xxs.js +10 -0
  69. package/dist/ui/assets/trace-browser-D93H7Xxs.js.map +1 -0
  70. package/dist/ui/assets/transcript-item-BmjdZLbS.js +3 -0
  71. package/dist/ui/assets/transcript-item-BmjdZLbS.js.map +1 -0
  72. package/dist/ui/assets/with-selector-CRZWaZzC.js +2 -0
  73. package/dist/ui/assets/with-selector-CRZWaZzC.js.map +1 -0
  74. package/dist/ui/index.html +19 -3
  75. package/package.json +16 -11
  76. package/dist/ui/assets/ArrowClockwise.es-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 +0 -4
  86. package/dist/ui/assets/memory-page-DJ_nq7Ip.js.map +0 -1
  87. package/dist/ui/assets/pipelines-page-BsHeSdbO.js +0 -8
  88. package/dist/ui/assets/pipelines-page-BsHeSdbO.js.map +0 -1
  89. package/dist/ui/assets/sandboxes-page-BQuQepXk.js +0 -2
  90. package/dist/ui/assets/sandboxes-page-BQuQepXk.js.map +0 -1
  91. package/dist/ui/assets/sessions-page-BLGrHrQT.js +0 -2
  92. package/dist/ui/assets/status-page-D-BUKV_r.js +0 -2
  93. package/dist/ui/assets/tool-runner-6-N-OOCI.js +0 -2
  94. package/dist/ui/assets/tools-page-BEQJgFX0.js +0 -2
  95. package/dist/ui/assets/trace-browser-DdGcJyNX.js +0 -10
  96. package/dist/ui/assets/trace-browser-DdGcJyNX.js.map +0 -1
  97. package/dist/ui/assets/transcript-item-Bbf77TZa.js +0 -3
  98. 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/interactions";
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]();
@@ -1809,7 +1870,7 @@ function withAgentRunCancellation(events, onCancel) {
1809
1870
  }
1810
1871
  return next;
1811
1872
  }
1812
- if (next.value.type === "final" || next.value.type === "error") {
1873
+ if (isTerminalAgentEvent(next.value)) {
1813
1874
  terminal = true;
1814
1875
  }
1815
1876
  return next;
@@ -1886,10 +1947,10 @@ function createPersistedStreamingSessionTranscript(props) {
1886
1947
  return;
1887
1948
  }
1888
1949
  acceptTranscriptStreamEvent(transcript, event);
1889
- if (event.type === "final" || event.type === "error") {
1950
+ if (isTerminalAgentEvent(event)) {
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 === "response" || event.type === "interaction" || event.type === "blocked" ? event.type === "interaction" ? "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 === "response" || event.type === "interaction" || event.type === "blocked" ? event.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 === "interaction") {
2112
+ const interaction = event.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) {
2170
+ if ((event.type === "response" || event.type === "interaction" || event.type === "blocked") && event.trace?.traceId !== void 0) {
2118
2171
  assignTranscriptTraceId(transcript, event.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,17 @@ 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
+ }
2586
+ function isTerminalAgentEvent(event) {
2587
+ return event.type === "response" || event.type === "interaction" || event.type === "blocked" || event.type === "error";
2588
+ }
2496
2589
 
2497
2590
  // src/runtime/session-logs.ts
2498
2591
  async function appendSessionLog(store, input) {
@@ -2627,6 +2720,29 @@ function memorySavedLog(props) {
2627
2720
  metadata
2628
2721
  };
2629
2722
  }
2723
+ function memoryCompactedLog(props) {
2724
+ const metadata = {
2725
+ originalMessageCount: props.originalMessageCount,
2726
+ compactedMessageCount: props.compactedMessageCount,
2727
+ retainedMessageCount: props.retainedMessageCount,
2728
+ originalTokenCount: props.originalTokenCount,
2729
+ compactedTokenCount: props.compactedTokenCount,
2730
+ retainedTokenCount: props.retainedTokenCount,
2731
+ resultTokenCount: props.resultTokenCount,
2732
+ attempts: props.attempts
2733
+ };
2734
+ const usage = usageSummary(props.usage);
2735
+ if (usage !== void 0) metadata.usage = usage;
2736
+ return {
2737
+ sessionId: props.sessionId,
2738
+ runId: props.runId,
2739
+ level: "info",
2740
+ category: "memory",
2741
+ event: "memory.compacted",
2742
+ message: "Session memory compacted",
2743
+ metadata
2744
+ };
2745
+ }
2630
2746
  function runFailedLog(sessionId, runId, error, startedAt) {
2631
2747
  return {
2632
2748
  sessionId,
@@ -2643,6 +2759,9 @@ function runFailedLog(sessionId, runId, error, startedAt) {
2643
2759
  }
2644
2760
  function logsFromStreamEvent(props) {
2645
2761
  const { event, sessionId, runId } = props;
2762
+ if (event.type === "memory_compaction") {
2763
+ return [memoryCompactedLog({ sessionId, runId, ...event })];
2764
+ }
2646
2765
  if (event.type === "turn_start") {
2647
2766
  return [
2648
2767
  {
@@ -2668,12 +2787,12 @@ function logsFromStreamEvent(props) {
2668
2787
  level: "info",
2669
2788
  category: "tool",
2670
2789
  event: "tool.called",
2671
- message: `Tool ${event.toolCall.function.name} called`,
2790
+ message: `Tool ${event.toolCall.toolName} called`,
2672
2791
  metadata: {
2673
2792
  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))
2793
+ toolName: event.toolCall.toolName,
2794
+ callId: event.toolCall.callId ?? event.toolCall.toolCallId,
2795
+ argumentBytes: byteLength2(formatUnknown(event.toolCall.input))
2677
2796
  }
2678
2797
  }
2679
2798
  ];
@@ -2721,103 +2840,80 @@ function logsFromStreamEvent(props) {
2721
2840
  }
2722
2841
  ];
2723
2842
  }
2724
- 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
- {
2843
+ if (event.type === "response" || event.type === "interaction" || event.type === "blocked") {
2844
+ const result = event;
2845
+ const logs = [];
2846
+ if (result.type === "interaction") {
2847
+ logs.push({
2751
2848
  sessionId,
2752
2849
  runId,
2753
2850
  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
- {
2851
+ category: "run",
2852
+ event: "run.suspended",
2853
+ message: `Run suspended for ${result.interaction.type}`,
2854
+ metadata: {
2855
+ interactionId: result.interaction.id,
2856
+ interactionType: result.interaction.type,
2857
+ toolName: result.interaction.toolName,
2858
+ sourceRunId: result.continuation.sourceRunId
2859
+ }
2860
+ });
2861
+ } else {
2862
+ logs.push(
2863
+ runCompletedLog({
2864
+ sessionId,
2865
+ runId,
2866
+ durationMs: Date.now() - props.startedAt,
2867
+ usage: result.usage,
2868
+ output: result.text,
2869
+ messageCount: result.messages.length
2870
+ })
2871
+ );
2872
+ }
2873
+ if (result.resumedFrom !== void 0) {
2874
+ logs.push({
2792
2875
  sessionId,
2793
2876
  runId,
2794
2877
  level: "info",
2795
- category: "question",
2796
- event: "question.requested",
2797
- message: `Question requested by ${event.question.toolName}`,
2798
- metadata
2799
- }
2800
- ];
2878
+ category: "run",
2879
+ event: "interaction.resumed",
2880
+ message: "Interaction resumed in a linked run",
2881
+ metadata: {
2882
+ interactionId: result.resumedFrom.interactionId,
2883
+ sourceRunId: result.resumedFrom.runId,
2884
+ resumedRunId: result.runId
2885
+ }
2886
+ });
2887
+ }
2888
+ logs.push(memorySavedLog({ sessionId, runId, messageCount: result.messages.length }));
2889
+ return logs;
2801
2890
  }
2802
- if (event.type === "tool_question_result") {
2803
- const cancelled = event.question.status === "cancelled";
2891
+ if (event.type === "error") {
2892
+ return [runFailedLog(sessionId, runId, event.error, props.startedAt)];
2893
+ }
2894
+ if (event.type === "interaction_response") {
2895
+ const response = event.response;
2804
2896
  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
2897
+ interactionId: response.interactionId,
2898
+ interactionType: response.type === "tool-approval-response" ? "tool-approval" : "tool-question",
2899
+ toolName: response.toolName,
2900
+ sourceRunId: event.sourceRunId
2811
2901
  };
2812
- if (event.question.callId !== void 0) metadata.callId = event.question.callId;
2902
+ if (response.callId !== void 0) metadata.callId = response.callId;
2903
+ if (response.type === "tool-approval-response") {
2904
+ metadata.approved = response.approved;
2905
+ metadata.hasReason = response.reason !== void 0;
2906
+ } else {
2907
+ metadata.answerCount = response.answers.length;
2908
+ }
2813
2909
  return [
2814
2910
  {
2815
2911
  sessionId,
2816
2912
  runId,
2817
2913
  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}`,
2914
+ category: "run",
2915
+ event: "interaction.responded",
2916
+ message: `Interaction response received for ${response.toolName}`,
2821
2917
  metadata
2822
2918
  }
2823
2919
  ];
@@ -2843,16 +2939,16 @@ function childAgentLog(event, sessionId, runId) {
2843
2939
  level: "debug",
2844
2940
  category: "tool",
2845
2941
  event: "child_tool.called",
2846
- message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.function.name}`,
2942
+ message: `Child agent ${event.agentName ?? event.agentId} called ${child.toolCall.toolName}`,
2847
2943
  metadata: {
2848
2944
  parentToolName: event.toolName,
2849
2945
  agentId: event.agentId,
2850
2946
  hasAgentName: event.agentName !== void 0,
2851
2947
  turn: event.turn,
2852
2948
  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))
2949
+ toolName: child.toolCall.toolName,
2950
+ callId: child.toolCall.callId ?? child.toolCall.toolCallId,
2951
+ argumentBytes: byteLength2(formatUnknown(child.toolCall.input))
2856
2952
  }
2857
2953
  }
2858
2954
  ];
@@ -2902,15 +2998,17 @@ function childAgentLog(event, sessionId, runId) {
2902
2998
  }
2903
2999
  ];
2904
3000
  }
2905
- if (child.type === "final") {
3001
+ if (child.type === "response" || child.type === "interaction" || child.type === "blocked") {
3002
+ const result = child;
2906
3003
  const metadata = {
2907
3004
  parentToolName: event.toolName,
2908
3005
  agentId: event.agentId,
2909
3006
  hasAgentName: event.agentName !== void 0,
2910
- outputBytes: byteLength2(child.output),
2911
- messageCount: child.messages.length
3007
+ outputBytes: byteLength2(result.text),
3008
+ messageCount: result.messages.length,
3009
+ status: result.type
2912
3010
  };
2913
- const usage = usageSummary(child.usage);
3011
+ const usage = usageSummary(result.usage);
2914
3012
  if (usage !== void 0) metadata.usage = usage;
2915
3013
  return [
2916
3014
  {
@@ -3010,6 +3108,56 @@ async function prepareAgentRun(c, props) {
3010
3108
  if ("error" in body) {
3011
3109
  return body.error;
3012
3110
  }
3111
+ if (body.type === "interaction_response") {
3112
+ const registration = props.continuationRegistry.take(
3113
+ body.interactionId,
3114
+ agentId,
3115
+ body.response
3116
+ );
3117
+ if (registration.status === "missing") {
3118
+ return errorResponse(c, 404, "not_found", "Interaction continuation is unavailable");
3119
+ }
3120
+ if (registration.status === "claimed") {
3121
+ return errorResponse(c, 409, "conflict", "Interaction response was already claimed");
3122
+ }
3123
+ if (registration.status === "invalid") {
3124
+ return errorResponse(c, 400, "bad_request", registration.error.message);
3125
+ }
3126
+ const { registration: claimed } = registration;
3127
+ const source = claimed.context;
3128
+ const runId2 = globalThis.crypto.randomUUID();
3129
+ let resumedBody = {
3130
+ ...source.body
3131
+ };
3132
+ if (body.stream !== void 0) resumedBody = { ...resumedBody, stream: body.stream };
3133
+ if (body.metadata !== void 0) resumedBody = { ...resumedBody, metadata: body.metadata };
3134
+ if (body.trace !== void 0) resumedBody = { ...resumedBody, trace: body.trace };
3135
+ const runOptions = createRunOptions(resumedBody, agentId, source.session, c.req.raw.signal);
3136
+ const execution2 = {
3137
+ generate: (options) => source.runAgent.generate({
3138
+ continuation: claimed.continuation,
3139
+ response: body.response,
3140
+ ...options
3141
+ }),
3142
+ stream: (options) => source.runAgent.stream({
3143
+ continuation: claimed.continuation,
3144
+ response: body.response,
3145
+ ...options
3146
+ })
3147
+ };
3148
+ return {
3149
+ ...source,
3150
+ body: resumedBody,
3151
+ execution: execution2,
3152
+ failureMessages: void 0,
3153
+ memoryCompactionLogged: false,
3154
+ options: runOptions,
3155
+ runId: runId2,
3156
+ runStartedAt: Date.now(),
3157
+ generatedMessagesStartIndex: 0,
3158
+ shouldPersistSessionMessages: source.shouldPersistSessionMessages
3159
+ };
3160
+ }
3013
3161
  const session = await resolveRunSession(c, body, agentId, props.stores);
3014
3162
  if (session instanceof Response) {
3015
3163
  return session;
@@ -3036,12 +3184,12 @@ async function prepareAgentRun(c, props) {
3036
3184
  store: props.stores.sessions
3037
3185
  });
3038
3186
  const memoryMetadata = runMemoryMetadata(agentId, body, selectedModel, runId);
3039
- const promptMessage = normalizePromptMessage(body.message);
3187
+ const promptMessage = body.messages.at(-1);
3040
3188
  const sessionStore = props.stores.sessions;
3041
3189
  const shouldPersistSessionMessages = session !== void 0 && sessionStore !== void 0 && !usesStoreAsAgentMemory(runAgent, sessionStore);
3042
3190
  if (shouldPersistSessionMessages) {
3043
3191
  await sessionStore.append({
3044
- context: { sessionId: session.id, metadata: memoryMetadata },
3192
+ scope: { sessionId: session.id, metadata: memoryMetadata },
3045
3193
  runId,
3046
3194
  turn: 1,
3047
3195
  messages: [promptMessage]
@@ -3061,10 +3209,13 @@ async function prepareAgentRun(c, props) {
3061
3209
  body,
3062
3210
  memoryMetadata,
3063
3211
  execution,
3064
- options: createRunOptions(body, agentId, session),
3212
+ failureMessages: void 0,
3213
+ memoryCompactionLogged: false,
3214
+ options: createRunOptions(body, agentId, session, c.req.raw.signal),
3065
3215
  runAgent,
3066
3216
  runId,
3067
3217
  runStartedAt,
3218
+ generatedMessagesStartIndex: 1,
3068
3219
  selectedModel,
3069
3220
  session,
3070
3221
  sessionStore,
@@ -3106,7 +3257,7 @@ async function recordRunReceived(props) {
3106
3257
  sessionId: props.session.id,
3107
3258
  runId: props.runId,
3108
3259
  agentId: props.agentId,
3109
- message: props.body.message,
3260
+ message: props.body.messages.at(-1),
3110
3261
  stream: props.body.stream === true,
3111
3262
  hasTrace: props.body.trace !== void 0
3112
3263
  };
@@ -3154,23 +3305,33 @@ function runMemoryMetadata(agentId, body, selectedModel, runId) {
3154
3305
  return metadata;
3155
3306
  }
3156
3307
  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 });
3308
+ const session = props.session;
3309
+ if (session !== void 0 && props.runAgent.memory !== void 0) {
3159
3310
  return {
3160
- generate: (options) => session.generate(props.body.message, options),
3161
- stream: (options) => session.stream(props.body.message, options)
3311
+ generate: (options) => props.runAgent.generate({
3312
+ prompt: props.promptMessage,
3313
+ session: { sessionId: session.id, metadata: props.memoryMetadata },
3314
+ ...options
3315
+ }),
3316
+ stream: (options) => props.runAgent.stream({
3317
+ prompt: props.promptMessage,
3318
+ session: { sessionId: session.id, metadata: props.memoryMetadata },
3319
+ ...options
3320
+ })
3162
3321
  };
3163
3322
  }
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;
3323
+ const transcript = session !== void 0 ? [...session.messages, props.promptMessage] : props.body.messages;
3165
3324
  return {
3166
- generate: (options) => props.runAgent.generate(input, options),
3167
- stream: (options) => props.runAgent.stream(input, options)
3325
+ generate: (options) => props.runAgent.generate({ messages: transcript, ...options }),
3326
+ stream: (options) => props.runAgent.stream({ messages: transcript, ...options })
3168
3327
  };
3169
3328
  }
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;
3329
+ function createRunOptions(body, agentId, session, abortSignal) {
3330
+ const options = { abortSignal };
3331
+ if (body.type === "messages" && body.maxTurns !== void 0) options.maxTurns = body.maxTurns;
3332
+ if (body.type === "messages" && body.toolConcurrency !== void 0) {
3333
+ options.toolConcurrency = body.toolConcurrency;
3334
+ }
3174
3335
  if (body.trace !== void 0) {
3175
3336
  options.trace = traceForRun(body.trace, agentId, session);
3176
3337
  } else if (session !== void 0) {
@@ -3179,34 +3340,11 @@ function createRunOptions(body, agentId, session) {
3179
3340
  return options;
3180
3341
  }
3181
3342
  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
3343
+ const streamOptions = withInternalAgentRunOptions({ ...run.options }, { runId: run.runId });
3344
+ const runStream = registerStreamingContinuation(
3345
+ run.execution.stream(streamOptions),
3346
+ props.continuationRegistry,
3347
+ run
3210
3348
  );
3211
3349
  let stream = runStream;
3212
3350
  let persistedRun;
@@ -3221,17 +3359,17 @@ function handleStreamingAgentRun(c, run, props) {
3221
3359
  }),
3222
3360
  store: run.sessionStore,
3223
3361
  session: run.session,
3224
- message: run.body.message,
3362
+ message: run.body.messages.at(-1),
3225
3363
  runId: run.runId,
3226
3364
  startedAt: run.runStartedAt,
3227
- persistGeneratedMessages: run.shouldPersistSessionMessages
3365
+ persistGeneratedMessages: run.shouldPersistSessionMessages,
3366
+ generatedMessagesStartIndex: run.generatedMessagesStartIndex
3228
3367
  });
3229
3368
  stream = persistedRun.events;
3230
3369
  }
3231
3370
  return streamAgentRunEvents(c, stream, {
3371
+ runId: run.runId,
3232
3372
  onCancel: async () => {
3233
- props.approvalRuntime.cancelRun(run.runId);
3234
- props.questionRuntime.cancelRun(run.runId);
3235
3373
  const persistence = [];
3236
3374
  if (persistedRun !== void 0) {
3237
3375
  persistence.push(persistedRun.cancel());
@@ -3250,59 +3388,57 @@ function handleStreamingAgentRun(c, run, props) {
3250
3388
  }
3251
3389
  async function handleBufferedAgentRun(c, run, props) {
3252
3390
  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);
3391
+ const runtimeOptions = await startBufferedSessionRun(run);
3392
+ const result = await run.execution.generate(runtimeOptions);
3393
+ if (result.type === "interaction") {
3394
+ props.continuationRegistry.register({ continuation: result.continuation, context: run });
3395
+ }
3396
+ await completeBufferedSessionRun(run, result);
3397
+ return c.json(agentRunResponse(result));
3263
3398
  } catch (error) {
3264
3399
  await failBufferedSessionRun(run, error);
3265
3400
  return errorResponse(c, 500, "internal_error", "Agent run failed", serializeError(error));
3266
3401
  }
3267
3402
  }
3268
- async function startBufferedSessionRun(run, props) {
3403
+ function agentRunResponse(result) {
3404
+ if (result.type !== "interaction") return result;
3405
+ const { continuation: _continuation, messages: _messages, ...response } = result;
3406
+ return response;
3407
+ }
3408
+ async function startBufferedSessionRun(run) {
3269
3409
  if (run.session !== void 0) {
3270
3410
  await appendSessionLog(run.sessionStore, runStartedLog(run.session, run.runId));
3271
3411
  await appendSessionLog(run.sessionStore, memoryLoadedLog(run.session, run.runId));
3272
3412
  }
3273
- const questionContext = {
3274
- runId: run.runId,
3275
- agentId: run.agentId
3413
+ const internalOptions = {
3414
+ onFailure: ({ messages }) => {
3415
+ run.failureMessages = structuredClone(messages);
3416
+ },
3417
+ runId: run.runId
3276
3418
  };
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 });
3419
+ if (run.session !== void 0 && run.sessionStore !== void 0) {
3420
+ const sessionId = run.session.id;
3421
+ const sessionStore = run.sessionStore;
3422
+ internalOptions.onMemoryCompaction = async (compaction) => {
3423
+ await appendSessionLog(
3424
+ sessionStore,
3425
+ memoryCompactedLog({
3426
+ sessionId,
3427
+ runId: run.runId,
3428
+ ...compaction
3429
+ })
3430
+ );
3431
+ run.memoryCompactionLogged = true;
3432
+ };
3433
+ }
3434
+ return withInternalAgentRunOptions({ ...run.options }, internalOptions);
3281
3435
  }
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;
3436
+ async function* registerStreamingContinuation(stream, registry, run) {
3437
+ for await (const event of stream) {
3438
+ if (event.type === "interaction") {
3439
+ registry.register({ continuation: event.continuation, context: run });
3302
3440
  }
3303
- if (pending === void 0) return;
3304
- const decision = await approvalRuntime.request(context, pending.approval);
3305
- stream = agent.resume(pending, decision);
3441
+ yield event;
3306
3442
  }
3307
3443
  }
3308
3444
  async function completeBufferedSessionRun(run, response) {
@@ -3310,10 +3446,10 @@ async function completeBufferedSessionRun(run, response) {
3310
3446
  return;
3311
3447
  }
3312
3448
  if (run.shouldPersistSessionMessages) {
3313
- const generatedMessages = response.messages.slice(1);
3449
+ const generatedMessages = response.messages.slice(run.generatedMessagesStartIndex);
3314
3450
  if (generatedMessages.length > 0) {
3315
3451
  await run.sessionStore.append({
3316
- context: { sessionId: run.session.id, metadata: run.memoryMetadata },
3452
+ scope: { sessionId: run.session.id, metadata: run.memoryMetadata },
3317
3453
  runId: run.runId,
3318
3454
  turn: 1,
3319
3455
  messages: generatedMessages
@@ -3326,21 +3462,85 @@ async function completeBufferedSessionRun(run, response) {
3326
3462
  await run.sessionStore.saveSessionRunTranscript({
3327
3463
  id: run.session.id,
3328
3464
  runId: run.runId,
3329
- ...optionalTitle(run.body.message),
3465
+ ...optionalTitle(run.body.messages.at(-1)),
3330
3466
  transcript,
3331
- status: "success"
3467
+ status: response.type === "interaction" ? "suspended" : "success"
3332
3468
  });
3469
+ if (response.memoryCompaction !== void 0 && !run.memoryCompactionLogged) {
3470
+ await appendSessionLog(
3471
+ run.sessionStore,
3472
+ memoryCompactedLog({
3473
+ sessionId: run.session.id,
3474
+ runId: run.runId,
3475
+ ...response.memoryCompaction
3476
+ })
3477
+ );
3478
+ run.memoryCompactionLogged = true;
3479
+ }
3333
3480
  await appendSessionLog(
3334
3481
  run.sessionStore,
3335
- runCompletedLog({
3482
+ response.type === "interaction" ? {
3483
+ sessionId: run.session.id,
3484
+ runId: run.runId,
3485
+ level: "info",
3486
+ category: "run",
3487
+ event: "run.suspended",
3488
+ message: `Run suspended for ${response.interaction.type}`,
3489
+ metadata: {
3490
+ interactionId: response.interaction.id,
3491
+ interactionType: response.interaction.type,
3492
+ toolName: response.interaction.toolName,
3493
+ sourceRunId: response.continuation.sourceRunId
3494
+ }
3495
+ } : runCompletedLog({
3336
3496
  sessionId: run.session.id,
3337
3497
  runId: run.runId,
3338
3498
  durationMs: durationMs2,
3339
3499
  usage: response.usage,
3340
- output: response.output,
3500
+ output: response.text,
3341
3501
  messageCount: response.messages.length
3342
3502
  })
3343
3503
  );
3504
+ if (response.resumedFrom !== void 0) {
3505
+ const responseMessage = response.messages[0];
3506
+ const responsePart = responseMessage?.role === "tool" ? responseMessage.content[0] : void 0;
3507
+ if (responsePart !== void 0 && responsePart.type !== "tool-result") {
3508
+ const metadata = {
3509
+ interactionId: responsePart.interactionId,
3510
+ interactionType: responsePart.type === "tool-approval-response" ? "tool-approval" : "tool-question",
3511
+ toolName: responsePart.toolName,
3512
+ sourceRunId: response.resumedFrom.runId
3513
+ };
3514
+ if (responsePart.type === "tool-approval-response") {
3515
+ metadata.approved = responsePart.approved;
3516
+ metadata.hasReason = responsePart.reason !== void 0;
3517
+ } else {
3518
+ metadata.answerCount = responsePart.answers.length;
3519
+ }
3520
+ await appendSessionLog(run.sessionStore, {
3521
+ sessionId: run.session.id,
3522
+ runId: run.runId,
3523
+ level: "info",
3524
+ category: "run",
3525
+ event: "interaction.responded",
3526
+ message: `Interaction response received for ${responsePart.toolName}`,
3527
+ metadata
3528
+ });
3529
+ }
3530
+ await appendSessionLog(run.sessionStore, {
3531
+ sessionId: run.session.id,
3532
+ runId: run.runId,
3533
+ level: "info",
3534
+ category: "run",
3535
+ event: "interaction.resumed",
3536
+ message: "Interaction resumed in a linked run",
3537
+ metadata: {
3538
+ interactionId: response.resumedFrom.interactionId,
3539
+ sourceRunId: response.resumedFrom.runId,
3540
+ resumedRunId: response.runId
3541
+ }
3542
+ });
3543
+ }
3344
3544
  await appendSessionLog(
3345
3545
  run.sessionStore,
3346
3546
  memorySavedLog({
@@ -3354,17 +3554,14 @@ async function failBufferedSessionRun(run, error) {
3354
3554
  if (run.session === void 0 || run.sessionStore === void 0) {
3355
3555
  return;
3356
3556
  }
3357
- const messages = await run.sessionStore.load({
3358
- sessionId: run.session.id,
3359
- metadata: run.memoryMetadata
3360
- });
3361
3557
  const durationMs2 = Date.now() - run.runStartedAt;
3362
- const transcript = transcriptFromMessages(messages.slice(run.session.messageCount));
3558
+ const messages = run.failureMessages ?? [run.body.messages.at(-1)];
3559
+ const transcript = transcriptFromMessages([...messages]);
3363
3560
  assignTranscriptRunDuration(transcript, durationMs2);
3364
3561
  await run.sessionStore.saveSessionRunTranscript({
3365
3562
  id: run.session.id,
3366
3563
  runId: run.runId,
3367
- ...optionalTitle(run.body.message),
3564
+ ...optionalTitle(run.body.messages.at(-1)),
3368
3565
  transcript,
3369
3566
  status: "error",
3370
3567
  error: serializeError(error)
@@ -3374,254 +3571,13 @@ async function failBufferedSessionRun(run, error) {
3374
3571
  runFailedLog(run.session.id, run.runId, error, run.runStartedAt)
3375
3572
  );
3376
3573
  }
3377
- function normalizePromptMessage(message) {
3378
- return typeof message === "string" ? Message.user(message) : message;
3379
- }
3380
3574
  function usesStoreAsAgentMemory(agent, store) {
3381
3575
  const memoryStore = agent.memory?.store;
3382
3576
  return memoryStore !== void 0 && rawObservedStore(memoryStore) === rawObservedStore(store);
3383
3577
  }
3384
3578
 
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
- }
3579
+ // src/runtime/config.ts
3580
+ import { isQuestionTool } from "@anvia/core/tool";
3625
3581
 
3626
3582
  // src/runtime/eval-config.ts
3627
3583
  var CASE_PREVIEW_LIMIT = 5;
@@ -3703,6 +3659,41 @@ function isRecord2(value) {
3703
3659
 
3704
3660
  // src/runtime/knowledge.ts
3705
3661
  import { getAgentToolState } from "@anvia/core/internal/agent";
3662
+
3663
+ // src/runtime/query.ts
3664
+ function optionalQueryString(value) {
3665
+ const trimmed = value?.trim();
3666
+ return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
3667
+ }
3668
+ function parseLimit(value, defaultMax = 50, max = 100) {
3669
+ if (value === void 0 || value.trim().length === 0) {
3670
+ return defaultMax;
3671
+ }
3672
+ const limit = Number(value);
3673
+ if (!Number.isInteger(limit) || limit <= 0) {
3674
+ return void 0;
3675
+ }
3676
+ return Math.min(limit, max);
3677
+ }
3678
+ function parseTraceStatus(value) {
3679
+ const status = optionalQueryString(value);
3680
+ if (status === void 0) {
3681
+ return void 0;
3682
+ }
3683
+ return status === "running" || status === "success" || status === "suspended" || status === "error" ? status : false;
3684
+ }
3685
+ function parseAfter(value) {
3686
+ if (value === void 0 || value.trim().length === 0) {
3687
+ return void 0;
3688
+ }
3689
+ const after = Number(value);
3690
+ if (!Number.isInteger(after) || after < 0) {
3691
+ return false;
3692
+ }
3693
+ return after;
3694
+ }
3695
+
3696
+ // src/runtime/knowledge.ts
3706
3697
  function registerKnowledgeRoutes(app, props) {
3707
3698
  app.get("/knowledge", async (c) => {
3708
3699
  const limit = parseLimit(c.req.query("limit"));
@@ -3763,7 +3754,7 @@ async function agentKnowledgeConfig(agent) {
3763
3754
  async function knowledgeSources(agent) {
3764
3755
  const toolIndexes = getAgentToolState(agent.agent).toolIndexes;
3765
3756
  const staticContext = staticContextDocuments(agent.agent);
3766
- const indexedContext = contextIndexes(agent.agent);
3757
+ const indexedContext = vectorContexts(agent.agent);
3767
3758
  const sources = [
3768
3759
  {
3769
3760
  sourceId: staticSourceId(),
@@ -3775,20 +3766,20 @@ async function knowledgeSources(agent) {
3775
3766
  }
3776
3767
  ];
3777
3768
  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);
3769
+ indexedContext.map(async (vectorContext, index) => {
3770
+ const inspect = inspectFn(vectorContext.store);
3771
+ const count = await inspectableCount(inspect, vectorContext.filter);
3781
3772
  const source = {
3782
3773
  sourceId: dynamicContextSourceId(index),
3783
3774
  kind: "dynamic_context",
3784
3775
  label: `Dynamic context ${index + 1}`,
3785
3776
  count: 1,
3786
3777
  registrationIndex: index,
3787
- topK: contextIndex.topK,
3778
+ topK: vectorContext.topK,
3788
3779
  inspectable: inspect !== void 0
3789
3780
  };
3790
- if (contextIndex.threshold !== void 0) {
3791
- source.threshold = contextIndex.threshold;
3781
+ if (vectorContext.minScore !== void 0) {
3782
+ source.minScore = vectorContext.minScore;
3792
3783
  }
3793
3784
  if (count !== void 0) source.itemCount = count;
3794
3785
  return source;
@@ -3807,8 +3798,8 @@ async function knowledgeSources(agent) {
3807
3798
  topK: toolIndex.topK,
3808
3799
  inspectable: inspect !== void 0
3809
3800
  };
3810
- if (toolIndex.threshold !== void 0) {
3811
- source.threshold = toolIndex.threshold;
3801
+ if (toolIndex.minScore !== void 0) {
3802
+ source.minScore = toolIndex.minScore;
3812
3803
  }
3813
3804
  if (count !== void 0) source.itemCount = count;
3814
3805
  return source;
@@ -3827,20 +3818,20 @@ async function knowledgeItemsPage(agent, sourceId, request) {
3827
3818
  if (sourceId === staticSourceId()) {
3828
3819
  return staticKnowledgeItemsPage(agent, request);
3829
3820
  }
3830
- const dynamicContextIndex = dynamicSourceIndex(sourceId, "dynamic_context");
3831
- if (dynamicContextIndex !== void 0) {
3832
- const contextIndex = contextIndexes(agent.agent)[dynamicContextIndex];
3833
- if (contextIndex === void 0) {
3821
+ const vectorContextIndex = dynamicSourceIndex(sourceId, "dynamic_context");
3822
+ if (vectorContextIndex !== void 0) {
3823
+ const vectorContext = vectorContexts(agent.agent)[vectorContextIndex];
3824
+ if (vectorContext === void 0) {
3834
3825
  return void 0;
3835
3826
  }
3836
- const inspect = inspectFn(contextIndex.index);
3827
+ const inspect = inspectFn(vectorContext.store);
3837
3828
  if (inspect === void 0) {
3838
3829
  return nonInspectablePage(agent.id, sourceId, "dynamic_context");
3839
3830
  }
3840
3831
  const page = await inspect({
3841
3832
  limit: request.limit,
3842
3833
  cursor: request.cursor,
3843
- filter: contextIndex.filter
3834
+ filter: vectorContext.filter
3844
3835
  });
3845
3836
  const result = {
3846
3837
  agentId: agent.id,
@@ -3881,12 +3872,12 @@ async function knowledgeItemsPage(agent, sourceId, request) {
3881
3872
  }
3882
3873
  return void 0;
3883
3874
  }
3884
- function inspectFn(index) {
3885
- if (!isRecord3(index) || typeof index.inspect !== "function") {
3875
+ function inspectFn(store) {
3876
+ if (!isRecord3(store) || typeof store.inspect !== "function") {
3886
3877
  return void 0;
3887
3878
  }
3888
- const inspect = index.inspect;
3889
- return (request) => inspect.call(index, request);
3879
+ const inspect = store.inspect;
3880
+ return (request) => inspect.call(store, request);
3890
3881
  }
3891
3882
  function staticKnowledgeItemsPage(agent, request) {
3892
3883
  const staticContext = staticContextDocuments(agent.agent);
@@ -4102,7 +4093,7 @@ function isRecord3(value) {
4102
4093
 
4103
4094
  // src/runtime/tool-metadata.ts
4104
4095
  import { getAgentToolState as getAgentToolState2 } from "@anvia/core/internal/agent";
4105
- var MCP_TOOL_METADATA_KEY = /* @__PURE__ */ Symbol.for("anvia.mcp.tool.metadata");
4096
+ import { isMcpTool } from "@anvia/core/mcp";
4106
4097
  function agentToolItems(agent) {
4107
4098
  const state = getAgentToolState2(agent.agent);
4108
4099
  const items = state.staticTools.map((tool) => ({ tool, source: "static" }));
@@ -4135,15 +4126,10 @@ function toolRequiresApproval(tool) {
4135
4126
  return approval !== void 0 && approval !== false;
4136
4127
  }
4137
4128
  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;
4129
+ return isMcpTool(tool) ? tool.mcp.serverName : void 0;
4144
4130
  }
4145
- function agentHasMcpTools(agent) {
4146
- return agentToolItems(agent).some(({ tool }) => mcpServerName(tool) !== void 0);
4131
+ function agentHasMcpServers(agent) {
4132
+ return agent.agent.mcpServers.length > 0;
4147
4133
  }
4148
4134
 
4149
4135
  // src/runtime/config.ts
@@ -4165,7 +4151,7 @@ function agentConfig(agent) {
4165
4151
  function agentRuntimeSummary(agent) {
4166
4152
  const tools = agentToolItems(agent);
4167
4153
  const staticContext = staticContextDocuments(agent.agent);
4168
- const indexedContext = contextIndexes(agent.agent);
4154
+ const indexedContext = vectorContexts(agent.agent);
4169
4155
  const name = agent.name ?? agent.agent.name;
4170
4156
  const description = agent.description ?? agent.agent.description;
4171
4157
  const summary = {
@@ -4178,7 +4164,7 @@ function agentRuntimeSummary(agent) {
4178
4164
  mcpToolCount: tools.filter((item) => mcpServerName(item.tool) !== void 0).length,
4179
4165
  staticContextCount: staticContext.length,
4180
4166
  dynamicContextCount: indexedContext.length,
4181
- observerCount: agent.agent.observers.length,
4167
+ observerCount: Object.keys(agent.agent.observability?.observers ?? {}).length,
4182
4168
  hasMemory: agent.agent.memory !== void 0,
4183
4169
  hasLifecycle: agent.agent.lifecycle !== void 0,
4184
4170
  hasOutputSchema: agent.agent.outputSchema !== void 0
@@ -4250,14 +4236,16 @@ function capabilityConfig(_options, agents, pipelines, stores, sandboxCount = 0)
4250
4236
  if (agents.some((agent) => agent.agent.tools.length > 0)) {
4251
4237
  capabilities.tools = { enabled: true };
4252
4238
  }
4253
- if (agents.some(agentHasMcpTools)) {
4239
+ if (agents.some(agentHasMcpServers)) {
4254
4240
  capabilities.mcps = { enabled: true };
4255
4241
  }
4256
4242
  if (sandboxCount > 0) {
4257
4243
  capabilities.sandboxes = { enabled: true };
4258
4244
  }
4259
- if (agents.some((agent) => agent.agent.tools.some(toolRequiresApproval))) {
4260
- capabilities.approvals = { enabled: true };
4245
+ if (agents.some(
4246
+ (agent) => agent.agent.tools.some((tool) => toolRequiresApproval(tool) || isQuestionTool(tool))
4247
+ )) {
4248
+ capabilities.interactions = { enabled: true };
4261
4249
  }
4262
4250
  if (agents.some(agentHasKnowledge)) {
4263
4251
  capabilities.knowledge = { enabled: true };
@@ -4339,6 +4327,47 @@ async function parseEvalRunRequest(c) {
4339
4327
  return request;
4340
4328
  }
4341
4329
 
4330
+ // src/runtime/interactions.ts
4331
+ import {
4332
+ assertAgentInteractionResponse
4333
+ } from "@anvia/core/agent/interactions";
4334
+ function createStudioContinuationRegistry() {
4335
+ const pending = /* @__PURE__ */ new Map();
4336
+ const claimed = /* @__PURE__ */ new Set();
4337
+ return {
4338
+ register(registration) {
4339
+ const id = registration.continuation.interaction.id;
4340
+ if (pending.has(id) || claimed.has(id)) {
4341
+ throw new TypeError(`Duplicate Studio interaction ID "${id}".`);
4342
+ }
4343
+ pending.set(id, registration);
4344
+ },
4345
+ take(interactionId, agentId, response) {
4346
+ if (claimed.has(interactionId)) return { status: "claimed" };
4347
+ const registration = pending.get(interactionId);
4348
+ if (registration === void 0 || registration.continuation.agentId !== agentId) {
4349
+ return { status: "missing" };
4350
+ }
4351
+ const request = registration.continuation.interaction;
4352
+ try {
4353
+ assertAgentInteractionResponse(request, response);
4354
+ } catch (error) {
4355
+ return {
4356
+ status: "invalid",
4357
+ error: error instanceof TypeError ? error : new TypeError(String(error))
4358
+ };
4359
+ }
4360
+ pending.delete(interactionId);
4361
+ claimed.add(interactionId);
4362
+ return { status: "ready", registration };
4363
+ },
4364
+ clear() {
4365
+ pending.clear();
4366
+ claimed.clear();
4367
+ }
4368
+ };
4369
+ }
4370
+
4342
4371
  // src/runtime/mcps.ts
4343
4372
  function registerMcpRoutes(app, props) {
4344
4373
  app.get("/agents/:agentId/mcps", async (c) => {
@@ -4354,43 +4383,29 @@ function registerMcpRoutes(app, props) {
4354
4383
  });
4355
4384
  }
4356
4385
  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));
4386
+ return Promise.all(
4387
+ agent.agent.mcpServers.map(async (server) => {
4388
+ const tools = await Promise.all(
4389
+ server.tools.map(async (tool) => {
4390
+ const definition = await tool.definition("");
4391
+ return {
4392
+ name: definition.name,
4393
+ description: definition.description,
4394
+ parameters: definition.parameters,
4395
+ source: "static",
4396
+ approval: approvalMetadata(tool)
4397
+ };
4398
+ })
4399
+ );
4400
+ const sortedTools = tools.sort((left, right) => left.name.localeCompare(right.name));
4401
+ return {
4402
+ agentId: agent.id,
4403
+ name: server.name,
4404
+ toolCount: sortedTools.length,
4405
+ tools: sortedTools
4406
+ };
4407
+ })
4408
+ ).then((servers) => servers.sort((left, right) => left.name.localeCompare(right.name)));
4394
4409
  }
4395
4410
 
4396
4411
  // src/runtime/memory.ts
@@ -4612,7 +4627,7 @@ function agentMemorySource(ref, label, agentId, storeKindValue, inspector) {
4612
4627
  ).slice(0, options.limit);
4613
4628
  },
4614
4629
  async getConversation(conversationRef) {
4615
- const conversation = await inspector.getConversation(conversationRef);
4630
+ const conversation = await inspector.getConversation({ ref: conversationRef });
4616
4631
  return conversation === void 0 ? void 0 : inspectorConversationDetail(conversation, summary.agentIds);
4617
4632
  }
4618
4633
  };
@@ -4835,12 +4850,12 @@ function pipelineRunFailedLog(pipelineId, runId, error, startedAt) {
4835
4850
  }
4836
4851
  };
4837
4852
  }
4838
- function pipelineStageLog(pipelineId, runId, event) {
4853
+ function pipelineStageLog(event, studioPipelineId = event.pipelineId) {
4839
4854
  const category = stageCategory(event.node);
4840
4855
  if (event.type === "stage_started") {
4841
4856
  return {
4842
- pipelineId,
4843
- runId,
4857
+ pipelineId: studioPipelineId,
4858
+ runId: event.runId,
4844
4859
  level: "debug",
4845
4860
  category,
4846
4861
  event: `${event.node.kind}.started`,
@@ -4850,8 +4865,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4850
4865
  }
4851
4866
  if (event.type === "stage_completed") {
4852
4867
  return {
4853
- pipelineId,
4854
- runId,
4868
+ pipelineId: studioPipelineId,
4869
+ runId: event.runId,
4855
4870
  level: "debug",
4856
4871
  category,
4857
4872
  event: `${event.node.kind}.completed`,
@@ -4863,8 +4878,8 @@ function pipelineStageLog(pipelineId, runId, event) {
4863
4878
  };
4864
4879
  }
4865
4880
  return {
4866
- pipelineId,
4867
- runId,
4881
+ pipelineId: studioPipelineId,
4882
+ runId: event.runId,
4868
4883
  level: "error",
4869
4884
  category,
4870
4885
  event: `${event.node.kind}.failed`,
@@ -4891,6 +4906,7 @@ function stageCategory(node) {
4891
4906
  function nodeMetadata(node) {
4892
4907
  const metadata = {
4893
4908
  nodeId: node.id,
4909
+ nodePath: [...node.path],
4894
4910
  kind: node.kind,
4895
4911
  label: node.label
4896
4912
  };
@@ -5052,17 +5068,24 @@ async function executePipelineRun(c, props, pipeline, body) {
5052
5068
  if (body.metadata !== void 0) streamOptions.metadata = body.metadata;
5053
5069
  if (props.logStore !== void 0) streamOptions.logStore = props.logStore;
5054
5070
  if (props.runStore !== void 0) streamOptions.runStore = props.runStore;
5071
+ streamOptions.abortSignal = c.req.raw.signal;
5055
5072
  return streamPipelineRun(c, streamOptions);
5056
5073
  }
5057
5074
  try {
5058
5075
  await appendPipelineLog(props.logStore, pipelineRunStartedLog(pipeline, runId));
5059
- const output = await pipeline.pipeline.run(body.input, {
5076
+ const result = await pipeline.pipeline.run({
5077
+ input: body.input,
5078
+ runId,
5079
+ metadata: body.metadata,
5080
+ abortSignal: c.req.raw.signal,
5060
5081
  observer: {
5061
5082
  async onEvent(event) {
5062
- await appendPipelineLog(props.logStore, pipelineStageLog(pipeline.id, runId, event));
5083
+ await appendPipelineLog(props.logStore, pipelineStageLog(event, pipeline.id));
5063
5084
  }
5064
- }
5085
+ },
5086
+ failOnObserverError: true
5065
5087
  });
5088
+ const output = result.output;
5066
5089
  const jsonOutput = toJsonValue(output);
5067
5090
  const endedAt = Date.now();
5068
5091
  const successRecord = {
@@ -5127,19 +5150,24 @@ function streamPipelineRun(_c, props) {
5127
5150
  async function* pipelineRunEvents(props) {
5128
5151
  yield* emitPipelineLog(props.logStore, pipelineRunStartedLog(props.pipeline, props.runId));
5129
5152
  const events = new AsyncEventQueue();
5130
- const run = props.pipeline.pipeline.run(props.input, {
5153
+ const run = props.pipeline.pipeline.run({
5154
+ input: props.input,
5155
+ runId: props.runId,
5156
+ metadata: props.metadata,
5157
+ abortSignal: props.abortSignal,
5131
5158
  observer: {
5132
5159
  async onEvent(event) {
5133
5160
  const log = await appendPipelineLog(
5134
5161
  props.logStore,
5135
- pipelineStageLog(props.pipeline.id, props.runId, event)
5162
+ pipelineStageLog(event, props.pipeline.id)
5136
5163
  );
5137
5164
  if (log !== void 0) {
5138
5165
  events.push({ type: "pipeline_log", log });
5139
5166
  }
5140
5167
  }
5141
- }
5142
- }).then(async (output) => {
5168
+ },
5169
+ failOnObserverError: true
5170
+ }).then(async ({ output }) => {
5143
5171
  const jsonOutput = toJsonValue(output);
5144
5172
  const endedAt = Date.now();
5145
5173
  const successRecord = {
@@ -5292,272 +5320,284 @@ function parsePipelineLogAfter(value) {
5292
5320
  return after;
5293
5321
  }
5294
5322
 
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) });
5323
+ // src/runtime/sandbox-views.ts
5324
+ import { upgradeWebSocket } from "@hono/node-server";
5325
+ import { getConnInfo } from "@hono/node-server/conninfo";
5326
+ import { WebSocket } from "ws";
5327
+ var minLeaseTimeoutMs = 5e3;
5328
+ var maxLeaseTimeoutMs = 12e4;
5329
+ var maxPendingWebSocketBytes = 1024 * 1024;
5330
+ function registerSandboxViewRoutes(app, registry) {
5331
+ const leases = /* @__PURE__ */ new Map();
5332
+ const upstreamSockets = /* @__PURE__ */ new Set();
5333
+ app.get("/sandboxes/:sandboxRef/views/:viewId/control", async (c) => {
5334
+ const resolved = await authorizedView(c, registry);
5335
+ if (resolved instanceof Response) return resolved;
5336
+ pruneExpiredLease(leases, viewKey(resolved));
5337
+ return c.json(resolved.view.source.control.snapshot());
5321
5338
  });
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);
5339
+ app.get("/sandboxes/:sandboxRef/views/:viewId/connection", async (c) => {
5340
+ const resolved = await authorizedView(c, registry);
5341
+ if (resolved instanceof Response) return resolved;
5342
+ const response = {
5343
+ sandboxRef: resolved.entry.summary.ref,
5344
+ viewId: resolved.view.id,
5345
+ protocol: "novnc",
5346
+ websocketPath: `/sandboxes/${encodeURIComponent(resolved.entry.summary.ref)}/views/${encodeURIComponent(resolved.view.id)}/ws`,
5347
+ authentication: resolved.view.authentication.type === "password" ? { type: "password", password: resolved.view.authentication.password } : { type: "none" }
5348
+ };
5349
+ c.header("cache-control", "no-store");
5350
+ return c.json(response);
5335
5351
  });
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") };
5352
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/acquire", async (c) => {
5353
+ const resolved = await authorizedView(c, registry);
5354
+ if (resolved instanceof Response) return resolved;
5355
+ const body = await readControlBody(c);
5356
+ if (body instanceof Response) return body;
5357
+ const key = viewKey(resolved);
5358
+ pruneExpiredLease(leases, key);
5359
+ if (leases.has(key)) {
5360
+ return errorResponse(c, 409, "conflict", "Browser view is already controlled");
5361
5361
  }
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") };
5362
+ try {
5363
+ const lease = await resolved.view.source.control.acquireHumanControl({
5364
+ ownerId: body.ownerId,
5365
+ leaseTimeoutMs: body.leaseTimeoutMs,
5366
+ abortSignal: c.req.raw.signal
5367
+ });
5368
+ leases.set(key, lease);
5369
+ return c.json(leaseResponse(lease), 201);
5370
+ } catch (error) {
5371
+ return controlErrorResponse(c, error);
5367
5372
  }
5368
- if ("choice" in answer && typeof answer.choice !== "string") {
5369
- return { error: errorResponse(c, 400, "bad_request", "choice must be a string") };
5373
+ });
5374
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/renew", async (c) => {
5375
+ const resolved = await authorizedView(c, registry);
5376
+ if (resolved instanceof Response) return resolved;
5377
+ const body = await readLeaseBody(c);
5378
+ if (body instanceof Response) return body;
5379
+ const key = viewKey(resolved);
5380
+ pruneExpiredLease(leases, key);
5381
+ const lease = leases.get(key);
5382
+ if (lease === void 0 || lease.id !== body.leaseId) {
5383
+ return errorResponse(c, 409, "conflict", "Browser control lease is not active");
5370
5384
  }
5371
- if ("custom" in answer && typeof answer.custom !== "boolean") {
5372
- return { error: errorResponse(c, 400, "bad_request", "custom must be a boolean") };
5385
+ try {
5386
+ lease.renew({ leaseTimeoutMs: body.leaseTimeoutMs });
5387
+ return c.json(leaseResponse(lease));
5388
+ } catch (error) {
5389
+ leases.delete(key);
5390
+ return controlErrorResponse(c, error);
5373
5391
  }
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 };
5392
+ });
5393
+ app.post("/sandboxes/:sandboxRef/views/:viewId/control/release", async (c) => {
5394
+ const resolved = await authorizedView(c, registry);
5395
+ if (resolved instanceof Response) return resolved;
5396
+ const body = await readReleaseBody(c);
5397
+ if (body instanceof Response) return body;
5398
+ const key = viewKey(resolved);
5399
+ const lease = leases.get(key);
5400
+ if (lease === void 0 || lease.id !== body.leaseId) {
5401
+ return errorResponse(c, 409, "conflict", "Browser control lease is not active");
5402
+ }
5403
+ lease.release();
5404
+ leases.delete(key);
5405
+ return c.json({ released: true });
5406
+ });
5407
+ app.get("/sandboxes/:sandboxRef/views/:viewId/ws", async (c) => {
5408
+ const resolved = await authorizedView(c, registry);
5409
+ if (resolved instanceof Response) return resolved;
5410
+ const upstreamUrl = `ws://${hostForUrl(resolved.host)}:${resolved.port}/websockify`;
5411
+ return upgradeWebSocket(c, createProxyEvents(upstreamUrl, upstreamSockets), {
5412
+ onError: () => void 0
5413
+ });
5414
+ });
5415
+ return () => {
5416
+ for (const lease of leases.values()) lease.release();
5417
+ leases.clear();
5418
+ for (const socket of upstreamSockets) socket.close(1001, "Studio closed");
5419
+ upstreamSockets.clear();
5420
+ };
5383
5421
  }
5384
- function createQuestionRuntime() {
5385
- const questions = /* @__PURE__ */ new Map();
5422
+ function createProxyEvents(upstreamUrl, sockets) {
5423
+ let upstream;
5424
+ let pendingBytes = 0;
5425
+ const pending = [];
5386
5426
  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
- }
5427
+ onOpen(_event, downstream) {
5428
+ const protocols = downstream.protocol === null || downstream.protocol.length === 0 ? void 0 : [downstream.protocol];
5429
+ upstream = new WebSocket(upstreamUrl, protocols, {
5430
+ headers: { origin: "http://127.0.0.1" }
5431
+ });
5432
+ sockets.add(upstream);
5433
+ upstream.binaryType = "arraybuffer";
5434
+ upstream.once("open", () => {
5435
+ for (const message of pending) upstream?.send(message);
5436
+ pending.length = 0;
5437
+ pendingBytes = 0;
5438
+ });
5439
+ upstream.on("message", (data, isBinary) => {
5440
+ const value = isBinary ? Uint8Array.from(Buffer.from(data)) : data.toString();
5441
+ downstream.send(value);
5408
5442
  });
5443
+ upstream.once("close", (code, reason) => {
5444
+ sockets.delete(upstream);
5445
+ downstream.close(normalizeCloseCode(code), reason.toString("utf8").slice(0, 123));
5446
+ });
5447
+ upstream.once("error", () => downstream.close(1011, "noVNC upstream failed"));
5409
5448
  },
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;
5449
+ onMessage(event, downstream) {
5450
+ void normalizeWebSocketMessage(event.data).then((message) => {
5451
+ if (upstream?.readyState === WebSocket.OPEN) {
5452
+ upstream.send(message);
5453
+ return;
5423
5454
  }
5424
- if (options.sessionId !== void 0 && question.sessionId !== options.sessionId) {
5425
- return false;
5455
+ pendingBytes += typeof message === "string" ? Buffer.byteLength(message) : message.byteLength;
5456
+ if (pendingBytes > maxPendingWebSocketBytes) {
5457
+ downstream.close(1009, "Pending noVNC messages exceeded the limit");
5458
+ return;
5426
5459
  }
5427
- return true;
5428
- }).map(publicQuestion);
5460
+ pending.push(message);
5461
+ }).catch(() => downstream.close(1003, "Unsupported noVNC message"));
5429
5462
  },
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;
5463
+ onClose() {
5464
+ upstream?.close(1e3, "Viewer disconnected");
5465
+ if (upstream !== void 0) sockets.delete(upstream);
5443
5466
  },
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);
5467
+ onError() {
5468
+ upstream?.close(1011, "Viewer connection failed");
5457
5469
  }
5458
5470
  };
5459
5471
  }
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
- };
5472
+ async function authorizedView(c, registry) {
5473
+ const entry = registry.get(c.req.param("sandboxRef") ?? "");
5474
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5475
+ const view = entry.views.get(c.req.param("viewId") ?? "");
5476
+ if (view === void 0) return errorResponse(c, 404, "not_found", "Sandbox view not found");
5477
+ if (!await authorize(c, entry, view)) {
5478
+ return errorResponse(c, 403, "forbidden", "Sandbox view access denied");
5479
+ }
5480
+ const published = entry.inspector.publishedPorts?.find(
5481
+ (port) => port.containerPort === view.source.containerPort && port.protocol === "tcp" && isLoopbackAddress(port.host)
5482
+ );
5483
+ if (published === void 0) {
5484
+ return errorResponse(c, 503, "internal_error", "Sandbox view port is unavailable");
5485
+ }
5486
+ return { entry, view, host: published.host, port: published.hostPort };
5487
+ }
5488
+ async function authorize(c, entry, view) {
5489
+ if (view.access.mode === "authorize") {
5490
+ try {
5491
+ return await view.access.authorize({
5492
+ request: c.req.raw,
5493
+ sandboxRef: entry.summary.ref,
5494
+ viewId: view.id
5495
+ }) === true;
5496
+ } catch {
5497
+ return false;
5502
5498
  }
5503
- questions.push(normalized);
5504
5499
  }
5505
- return { questions };
5500
+ let remoteAddress;
5501
+ try {
5502
+ const address = getConnInfo(c).remote.address;
5503
+ if (address === void 0) return false;
5504
+ remoteAddress = address;
5505
+ } catch {
5506
+ return false;
5507
+ }
5508
+ if (!isLoopbackAddress(remoteAddress)) return false;
5509
+ const expectedOrigin = new URL(c.req.url).origin;
5510
+ const origin = c.req.header("origin");
5511
+ return origin === expectedOrigin || origin === void 0 && c.req.header("sec-fetch-site") === "same-origin";
5506
5512
  }
5507
- function normalizeQuestionPrompt(value, index) {
5508
- if (!isObject(value) || typeof value.question !== "string" || value.question.trim().length === 0) {
5509
- return void 0;
5513
+ async function readControlBody(c) {
5514
+ const body = await readJsonObject(c);
5515
+ if (body instanceof Response) return body;
5516
+ if (typeof body.ownerId !== "string" || body.ownerId.length === 0 || body.ownerId.length > 200) {
5517
+ return errorResponse(c, 400, "bad_request", "ownerId must be a non-empty string");
5510
5518
  }
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;
5519
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5520
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5514
5521
  }
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
- };
5522
+ return { ownerId: body.ownerId, leaseTimeoutMs: body.leaseTimeoutMs };
5520
5523
  }
5521
- function normalizeQuestionChoice(value) {
5522
- if (typeof value === "string" && value.trim().length > 0) {
5523
- return { label: value.trim(), value: value.trim() };
5524
+ async function readLeaseBody(c) {
5525
+ const body = await readJsonObject(c);
5526
+ if (body instanceof Response) return body;
5527
+ if (typeof body.leaseId !== "string" || body.leaseId.length === 0) {
5528
+ return errorResponse(c, 400, "bad_request", "leaseId must be a non-empty string");
5524
5529
  }
5525
- if (!isObject(value) || typeof value.label !== "string" || value.label.trim().length === 0) {
5526
- return void 0;
5530
+ if (!isLeaseTimeout(body.leaseTimeoutMs)) {
5531
+ return errorResponse(c, 400, "bad_request", "leaseTimeoutMs is outside the allowed range");
5527
5532
  }
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
- };
5533
+ return { leaseId: body.leaseId, leaseTimeoutMs: body.leaseTimeoutMs };
5532
5534
  }
5533
- function isPendingQuestion(question) {
5534
- return question !== void 0 && question.status === "pending" && "resolve" in question;
5535
+ async function readReleaseBody(c) {
5536
+ const body = await readJsonObject(c);
5537
+ if (body instanceof Response) return body;
5538
+ if (typeof body.leaseId !== "string" || body.leaseId.length === 0) {
5539
+ return errorResponse(c, 400, "bad_request", "leaseId must be a non-empty string");
5540
+ }
5541
+ return { leaseId: body.leaseId };
5535
5542
  }
5536
- function resolveQuestion(question, answers) {
5537
- return publicQuestion({
5538
- ...question,
5539
- status: "answered",
5540
- answeredAt: (/* @__PURE__ */ new Date()).toISOString(),
5541
- answers
5542
- });
5543
+ async function readJsonObject(c) {
5544
+ let body;
5545
+ try {
5546
+ body = await c.req.json();
5547
+ } catch {
5548
+ return errorResponse(c, 400, "bad_request", "Request body must be valid JSON");
5549
+ }
5550
+ return isRecord4(body) ? body : errorResponse(c, 400, "bad_request", "Request body must be an object");
5543
5551
  }
5544
- function cancelQuestion(question) {
5545
- return publicQuestion({
5546
- ...question,
5547
- status: "cancelled",
5548
- cancelledAt: (/* @__PURE__ */ new Date()).toISOString()
5549
- });
5552
+ function controlErrorResponse(c, error) {
5553
+ if (isRecord4(error) && error.code === "human_controlled") {
5554
+ return errorResponse(c, 409, "conflict", "Browser view is already controlled");
5555
+ }
5556
+ if (isRecord4(error) && error.code === "invalid_state") {
5557
+ return errorResponse(c, 409, "conflict", "Browser control lease is no longer active");
5558
+ }
5559
+ return errorResponse(c, 500, "internal_error", "Browser control operation failed");
5560
+ }
5561
+ function leaseResponse(lease) {
5562
+ return { id: lease.id, ownerId: lease.ownerId, expiresAt: lease.expiresAt };
5563
+ }
5564
+ function pruneExpiredLease(leases, key) {
5565
+ const lease = leases.get(key);
5566
+ if (lease !== void 0 && Date.parse(lease.expiresAt) <= Date.now()) {
5567
+ lease.release();
5568
+ leases.delete(key);
5569
+ }
5570
+ }
5571
+ function viewKey(resolved) {
5572
+ return `${resolved.entry.summary.ref}\0${resolved.view.id}`;
5573
+ }
5574
+ function isLeaseTimeout(value) {
5575
+ return Number.isSafeInteger(value) && Number(value) >= minLeaseTimeoutMs && Number(value) <= maxLeaseTimeoutMs;
5576
+ }
5577
+ function isLoopbackAddress(value) {
5578
+ return value === "localhost" || value === "127.0.0.1" || value === "::1" || value.startsWith("127.") || value.startsWith("::ffff:127.");
5579
+ }
5580
+ function hostForUrl(host) {
5581
+ return host.includes(":") ? `[${host}]` : host;
5582
+ }
5583
+ async function normalizeWebSocketMessage(value) {
5584
+ if (typeof value === "string") return value;
5585
+ if (value instanceof ArrayBuffer) return Uint8Array.from(new Uint8Array(value));
5586
+ if (ArrayBuffer.isView(value)) {
5587
+ return Uint8Array.from(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
5588
+ }
5589
+ if (value instanceof Blob) return Uint8Array.from(new Uint8Array(await value.arrayBuffer()));
5590
+ throw new TypeError("Unsupported noVNC WebSocket message type.");
5591
+ }
5592
+ function normalizeCloseCode(code) {
5593
+ return code >= 1e3 && code <= 4999 && code !== 1004 && code !== 1005 && code !== 1006 ? code : 1001;
5550
5594
  }
5551
- function publicQuestion(question) {
5552
- const { emit, resolve: resolve2, ...rest } = question;
5553
- void emit;
5554
- void resolve2;
5555
- return rest;
5595
+ function isRecord4(value) {
5596
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5556
5597
  }
5557
5598
 
5558
5599
  // src/runtime/sandboxes.ts
5559
5600
  import path from "path";
5560
- var sandboxToolMetadataKey = /* @__PURE__ */ Symbol.for("anvia.sandbox.tool.metadata");
5561
5601
  var maxFileResponseBytes = 10 * 1024 * 1024;
5562
5602
  var defaultProcessLogBytes = 64 * 1024;
5563
5603
  var maxProcessLogBytes = 1024 * 1024;
@@ -5576,51 +5616,41 @@ var StudioSandboxRegistry = class {
5576
5616
  return this.entries.get(ref);
5577
5617
  }
5578
5618
  };
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}`;
5619
+ function createStudioSandboxRegistry(agents, registrations) {
5620
+ const agentIds = new Set(agents.map((agent) => agent.id));
5621
+ const refs = /* @__PURE__ */ new Set();
5622
+ const entries = registrations.map((registration, index) => {
5623
+ validateRegistration(registration, index, agentIds);
5624
+ const inspector = registration.inspector;
5625
+ const ref = sandboxRef(inspector.provider, inspector.id);
5626
+ if (refs.has(ref)) {
5627
+ throw new TypeError(
5628
+ `sandboxes contains a duplicate provider/id registration: ${inspector.provider}/${inspector.id}`
5629
+ );
5630
+ }
5631
+ refs.add(ref);
5632
+ const views = new Map((registration.views ?? []).map((view) => [view.id, view]));
5607
5633
  return {
5608
- session: draft.session,
5634
+ inspector,
5635
+ views,
5609
5636
  summary: {
5610
5637
  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(),
5638
+ id: inspector.id,
5639
+ provider: inspector.provider,
5640
+ workdir: inspector.workdir,
5641
+ agentIds: sortedUnique(registration.agentIds ?? []),
5642
+ toolNames: sortedUnique(registration.toolNames ?? []),
5643
+ views: [...views.values()].map((view) => ({ id: view.id, label: view.label, protocol: view.source.protocol })).sort((left, right) => left.id.localeCompare(right.id)),
5616
5644
  capabilities: {
5617
- files: true,
5618
- ports: supportsPorts(draft.session),
5619
- processes: supportsProcesses(draft.session)
5645
+ files: supportsFiles(inspector),
5646
+ ports: supportsPorts(inspector),
5647
+ processes: supportsProcesses(inspector),
5648
+ views: views.size > 0
5620
5649
  }
5621
5650
  }
5622
5651
  };
5623
5652
  });
5653
+ entries.sort((left, right) => left.summary.ref.localeCompare(right.summary.ref));
5624
5654
  return new StudioSandboxRegistry(entries);
5625
5655
  }
5626
5656
  function registerSandboxRoutes(app, registry) {
@@ -5629,24 +5659,19 @@ function registerSandboxRoutes(app, registry) {
5629
5659
  app.get("/sandboxes", (c) => c.json({ sandboxes: registry.list() }));
5630
5660
  app.get("/sandboxes/:sandboxRef", (c) => {
5631
5661
  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));
5662
+ return entry === void 0 ? errorResponse(c, 404, "not_found", "Sandbox not found") : c.json(copySummary(entry.summary));
5636
5663
  });
5637
5664
  app.get("/sandboxes/:sandboxRef/files", async (c) => {
5638
5665
  const entry = registry.get(c.req.param("sandboxRef"));
5639
- if (entry === void 0) {
5640
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5641
- }
5666
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5667
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files");
5642
5668
  try {
5643
5669
  const requestedPath = c.req.query("path") ?? ".";
5644
5670
  const normalizedPath = normalizeSandboxPath(requestedPath, true);
5645
- const entries = await listSandboxFiles(entry.session, normalizedPath);
5646
5671
  return c.json({
5647
5672
  sandboxRef: entry.summary.ref,
5648
5673
  path: normalizedPath,
5649
- entries
5674
+ entries: await listSandboxFiles(entry.inspector, normalizedPath, c.req.raw.signal)
5650
5675
  });
5651
5676
  } catch (error) {
5652
5677
  return sandboxErrorResponse(c, error);
@@ -5654,9 +5679,8 @@ function registerSandboxRoutes(app, registry) {
5654
5679
  });
5655
5680
  app.get("/sandboxes/:sandboxRef/files/content", async (c) => {
5656
5681
  const entry = registry.get(c.req.param("sandboxRef"));
5657
- if (entry === void 0) {
5658
- return errorResponse(c, 404, "not_found", "Sandbox not found");
5659
- }
5682
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5683
+ if (!supportsFiles(entry.inspector)) return unsupportedSandboxOperation(c, "files.content");
5660
5684
  try {
5661
5685
  const requestedPath = c.req.query("path");
5662
5686
  if (requestedPath === void 0) {
@@ -5667,8 +5691,11 @@ function registerSandboxRoutes(app, registry) {
5667
5691
  throw new SandboxRouteError(400, "bad_request", "download must be 1 when provided");
5668
5692
  }
5669
5693
  const normalizedPath = normalizeSandboxPath(requestedPath, false);
5670
- const parentPath = path.posix.dirname(normalizedPath);
5671
- const siblings = await listSandboxFiles(entry.session, parentPath);
5694
+ const siblings = await listSandboxFiles(
5695
+ entry.inspector,
5696
+ path.posix.dirname(normalizedPath),
5697
+ c.req.raw.signal
5698
+ );
5672
5699
  const file = siblings.find((candidate) => candidate.path === normalizedPath);
5673
5700
  if (file === void 0) {
5674
5701
  throw new SandboxRouteError(404, "not_found", "Sandbox file not found");
@@ -5680,25 +5707,24 @@ function registerSandboxRoutes(app, registry) {
5680
5707
  "Sandbox content path must refer to a regular file"
5681
5708
  );
5682
5709
  }
5683
- if (file.size !== void 0 && file.size > maxFileResponseBytes) {
5710
+ if (file.size !== void 0 && file.size > maxFileResponseBytes)
5684
5711
  throw fileTooLarge(file.size);
5685
- }
5686
- const rawBytes = await entry.session.readFile(normalizedPath);
5712
+ const rawBytes = await entry.inspector.readFile({
5713
+ path: normalizedPath,
5714
+ abortSignal: c.req.raw.signal
5715
+ });
5687
5716
  if (!(rawBytes instanceof Uint8Array)) {
5688
5717
  throw new TypeError("Sandbox readFile returned an invalid byte payload");
5689
5718
  }
5690
- if (rawBytes.byteLength > maxFileResponseBytes) {
5691
- throw fileTooLarge(rawBytes.byteLength);
5692
- }
5693
- const bytes = new Uint8Array(rawBytes.byteLength);
5694
- bytes.set(rawBytes);
5719
+ if (rawBytes.byteLength > maxFileResponseBytes) throw fileTooLarge(rawBytes.byteLength);
5720
+ const bytes = rawBytes.slice();
5695
5721
  const disposition = download === "1" ? "attachment" : "inline";
5696
5722
  const filename = encodeURIComponent(path.posix.basename(normalizedPath));
5697
5723
  return new Response(bytes, {
5698
5724
  headers: {
5699
5725
  "cache-control": "no-store",
5700
5726
  "content-disposition": `${disposition}; filename*=UTF-8''${filename}`,
5701
- "content-length": String(bytes.byteLength),
5727
+ "content-length": `${bytes.byteLength}`,
5702
5728
  "content-type": "application/octet-stream",
5703
5729
  "x-content-type-options": "nosniff"
5704
5730
  }
@@ -5709,16 +5735,12 @@ function registerSandboxRoutes(app, registry) {
5709
5735
  });
5710
5736
  app.get("/sandboxes/:sandboxRef/ports", (c) => {
5711
5737
  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
- }
5738
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5739
+ if (!supportsPorts(entry.inspector)) return unsupportedSandboxOperation(c, "ports");
5718
5740
  try {
5719
5741
  return c.json({
5720
5742
  sandboxRef: entry.summary.ref,
5721
- ports: normalizePorts(entry.session.publishedPorts)
5743
+ ports: normalizePorts(entry.inspector.publishedPorts)
5722
5744
  });
5723
5745
  } catch (error) {
5724
5746
  return sandboxErrorResponse(c, error);
@@ -5726,16 +5748,16 @@ function registerSandboxRoutes(app, registry) {
5726
5748
  });
5727
5749
  app.get("/sandboxes/:sandboxRef/processes", async (c) => {
5728
5750
  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)) {
5751
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5752
+ if (!supportsProcesses(entry.inspector)) {
5733
5753
  return unsupportedSandboxOperation(c, "processes");
5734
5754
  }
5735
5755
  try {
5736
5756
  return c.json({
5737
5757
  sandboxRef: entry.summary.ref,
5738
- processes: normalizeProcesses(await entry.session.listProcesses())
5758
+ processes: normalizeProcesses(
5759
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5760
+ )
5739
5761
  });
5740
5762
  } catch (error) {
5741
5763
  return sandboxErrorResponse(c, error);
@@ -5743,21 +5765,25 @@ function registerSandboxRoutes(app, registry) {
5743
5765
  });
5744
5766
  app.get("/sandboxes/:sandboxRef/processes/:processId/logs", async (c) => {
5745
5767
  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)) {
5768
+ if (entry === void 0) return errorResponse(c, 404, "not_found", "Sandbox not found");
5769
+ if (!supportsProcesses(entry.inspector)) {
5750
5770
  return unsupportedSandboxOperation(c, "processes.logs");
5751
5771
  }
5752
5772
  try {
5753
5773
  const tailBytes = parseTailBytes(c.req.query("tailBytes"));
5754
5774
  const processId = c.req.param("processId");
5755
- const processes = normalizeProcesses(await entry.session.listProcesses());
5775
+ const processes = normalizeProcesses(
5776
+ await entry.inspector.listProcesses({ abortSignal: c.req.raw.signal })
5777
+ );
5756
5778
  if (!processes.some((process2) => process2.id === processId)) {
5757
5779
  throw new SandboxRouteError(404, "not_found", "Sandbox process not found");
5758
5780
  }
5759
5781
  const logs = normalizeProcessLogs(
5760
- await entry.session.readProcessLogs(processId, { tailBytes }),
5782
+ await entry.inspector.readProcessLogs({
5783
+ processId,
5784
+ tailBytes,
5785
+ abortSignal: c.req.raw.signal
5786
+ }),
5761
5787
  tailBytes
5762
5788
  );
5763
5789
  return c.json({
@@ -5774,21 +5800,86 @@ async function sandboxNoStore(c, next) {
5774
5800
  c.header("cache-control", "no-store");
5775
5801
  await next();
5776
5802
  }
5777
- function sandboxSessionFromTool(tool) {
5778
- const metadata = tool[sandboxToolMetadataKey];
5779
- if (!isRecord4(metadata)) {
5780
- return void 0;
5803
+ function validateRegistration(registration, index, knownAgentIds) {
5804
+ if (!isRecord5(registration) || !isInspector(registration.inspector)) {
5805
+ throw new TypeError(`sandboxes[${index}] must contain a valid inspector.`);
5806
+ }
5807
+ const inspector = registration.inspector;
5808
+ const files = supportsFiles(inspector);
5809
+ if (inspector.listFiles === void 0 !== (inspector.readFile === void 0)) {
5810
+ throw new TypeError(`sandboxes[${index}] must register both file inspector methods.`);
5811
+ }
5812
+ const processes = supportsProcesses(inspector);
5813
+ if (inspector.listProcesses === void 0 !== (inspector.readProcessLogs === void 0)) {
5814
+ throw new TypeError(`sandboxes[${index}] must register both process inspector methods.`);
5815
+ }
5816
+ if (!files && !supportsPorts(inspector) && !processes) {
5817
+ throw new TypeError(`sandboxes[${index}] inspector exposes no capabilities.`);
5818
+ }
5819
+ for (const agentId of sortedUnique(registration.agentIds ?? [])) {
5820
+ if (!knownAgentIds.has(agentId)) {
5821
+ throw new TypeError(`sandboxes[${index}] references an unknown agent: ${agentId}`);
5822
+ }
5823
+ }
5824
+ sortedUnique(registration.toolNames ?? []);
5825
+ validateViews(registration.views, inspector, index);
5826
+ }
5827
+ function validateViews(views, inspector, registrationIndex) {
5828
+ if (views === void 0) return;
5829
+ if (!Array.isArray(views))
5830
+ throw new TypeError(`sandboxes[${registrationIndex}].views must be an array.`);
5831
+ if (!supportsPorts(inspector)) {
5832
+ throw new TypeError(`sandboxes[${registrationIndex}] views require published port inspection.`);
5833
+ }
5834
+ const ids = /* @__PURE__ */ new Set();
5835
+ for (const [viewIndex, view] of views.entries()) {
5836
+ const prefix = `sandboxes[${registrationIndex}].views[${viewIndex}]`;
5837
+ const candidate = view;
5838
+ if (!isRecord5(candidate) || typeof candidate.id !== "string" || !/^[a-z0-9](?:[a-z0-9_-]{0,62})$/.test(candidate.id)) {
5839
+ throw new TypeError(`${prefix}.id must be a stable lowercase identifier.`);
5840
+ }
5841
+ if (ids.has(candidate.id)) throw new TypeError(`${prefix}.id is duplicated: ${candidate.id}`);
5842
+ ids.add(candidate.id);
5843
+ if (typeof candidate.label !== "string" || candidate.label.length === 0) {
5844
+ throw new TypeError(`${prefix}.label must be a non-empty string.`);
5845
+ }
5846
+ const source = candidate.source;
5847
+ if (!isRecord5(source) || source.protocol !== "novnc" || !isPort(source.containerPort) || !isRecord5(source.control) || typeof source.control.snapshot !== "function" || typeof source.control.acquireHumanControl !== "function") {
5848
+ throw new TypeError(`${prefix}.source must be a valid noVNC view source.`);
5849
+ }
5850
+ const port = inspector.publishedPorts?.find(
5851
+ (candidate2) => candidate2.containerPort === source.containerPort && candidate2.protocol === "tcp"
5852
+ );
5853
+ if (port === void 0 || !isLoopbackHost(port.host)) {
5854
+ throw new TypeError(`${prefix}.source must resolve to a loopback-published TCP port.`);
5855
+ }
5856
+ const access = candidate.access;
5857
+ if (!isRecord5(access) || access.mode !== "local" && access.mode !== "authorize") {
5858
+ throw new TypeError(`${prefix}.access must explicitly select local or authorize mode.`);
5859
+ }
5860
+ if (access.mode === "authorize" && typeof access.authorize !== "function") {
5861
+ throw new TypeError(`${prefix}.access.authorize must be a function.`);
5862
+ }
5863
+ const authentication = candidate.authentication;
5864
+ if (!isRecord5(authentication) || authentication.type !== "none" && authentication.type !== "password") {
5865
+ throw new TypeError(`${prefix}.authentication must explicitly select none or password.`);
5866
+ }
5867
+ if (authentication.type === "password" && (typeof authentication.password !== "string" || authentication.password.length === 0)) {
5868
+ throw new TypeError(`${prefix}.authentication.password must be a non-empty string.`);
5869
+ }
5781
5870
  }
5782
- return isSandboxSession(metadata.session) ? metadata.session : void 0;
5783
5871
  }
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";
5872
+ function isInspector(value) {
5873
+ 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
5874
  }
5787
- function supportsPorts(session) {
5788
- return Array.isArray(session.publishedPorts);
5875
+ function supportsFiles(inspector) {
5876
+ return typeof inspector.listFiles === "function" && typeof inspector.readFile === "function";
5789
5877
  }
5790
- function supportsProcesses(session) {
5791
- return typeof session.listProcesses === "function" && typeof session.readProcessLogs === "function";
5878
+ function supportsPorts(inspector) {
5879
+ return Array.isArray(inspector.publishedPorts);
5880
+ }
5881
+ function supportsProcesses(inspector) {
5882
+ return typeof inspector.listProcesses === "function" && typeof inspector.readProcessLogs === "function";
5792
5883
  }
5793
5884
  function sandboxRef(provider, id) {
5794
5885
  return Buffer.from(JSON.stringify([provider, id]), "utf8").toString("base64url");
@@ -5798,22 +5889,26 @@ function copySummary(summary) {
5798
5889
  ...summary,
5799
5890
  agentIds: [...summary.agentIds],
5800
5891
  toolNames: [...summary.toolNames],
5892
+ views: summary.views.map((view) => ({ ...view })),
5801
5893
  capabilities: { ...summary.capabilities }
5802
5894
  };
5803
5895
  }
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
- });
5896
+ function isLoopbackHost(host) {
5897
+ return host === "127.0.0.1" || host === "::1" || host === "localhost";
5898
+ }
5899
+ async function listSandboxFiles(inspector, filePath, abortSignal) {
5900
+ return normalizeFileEntries(await inspector.listFiles({ path: filePath, abortSignal })).sort(
5901
+ (left, right) => {
5902
+ if (left.type === "directory" && right.type !== "directory") return -1;
5903
+ if (left.type !== "directory" && right.type === "directory") return 1;
5904
+ return left.path.localeCompare(right.path);
5905
+ }
5906
+ );
5810
5907
  }
5811
5908
  function normalizeFileEntries(value) {
5812
- if (!Array.isArray(value)) {
5813
- throw new TypeError("Sandbox listFiles returned an invalid payload");
5814
- }
5909
+ if (!Array.isArray(value)) throw new TypeError("Sandbox listFiles returned an invalid payload");
5815
5910
  return value.map((item) => {
5816
- if (!isRecord4(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5911
+ if (!isRecord5(item) || typeof item.path !== "string" || !isFileType(item.type)) {
5817
5912
  throw new TypeError("Sandbox listFiles returned an invalid entry");
5818
5913
  }
5819
5914
  let entryPath;
@@ -5824,7 +5919,7 @@ function normalizeFileEntries(value) {
5824
5919
  }
5825
5920
  const entry = { path: entryPath, type: item.type };
5826
5921
  if (item.size !== void 0) {
5827
- if (typeof item.size !== "number" || !Number.isFinite(item.size) || item.size < 0) {
5922
+ if (!Number.isSafeInteger(item.size) || Number(item.size) < 0) {
5828
5923
  throw new TypeError("Sandbox listFiles returned an invalid file size");
5829
5924
  }
5830
5925
  entry.size = item.size;
@@ -5833,11 +5928,10 @@ function normalizeFileEntries(value) {
5833
5928
  });
5834
5929
  }
5835
5930
  function normalizePorts(value) {
5836
- if (!Array.isArray(value)) {
5931
+ if (!Array.isArray(value))
5837
5932
  throw new TypeError("Sandbox publishedPorts returned an invalid payload");
5838
- }
5839
5933
  return value.map((item) => {
5840
- if (!isRecord4(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
5934
+ if (!isRecord5(item) || !isPort(item.containerPort) || typeof item.host !== "string" || !isPort(item.hostPort) || typeof item.protocol !== "string") {
5841
5935
  throw new TypeError("Sandbox publishedPorts returned an invalid entry");
5842
5936
  }
5843
5937
  return {
@@ -5849,17 +5943,16 @@ function normalizePorts(value) {
5849
5943
  });
5850
5944
  }
5851
5945
  function normalizeProcesses(value) {
5852
- if (!Array.isArray(value)) {
5946
+ if (!Array.isArray(value))
5853
5947
  throw new TypeError("Sandbox listProcesses returned an invalid payload");
5854
- }
5855
5948
  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") {
5949
+ 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
5950
  throw new TypeError("Sandbox listProcesses returned an invalid entry");
5858
5951
  }
5859
5952
  const process2 = {
5860
5953
  id: item.id,
5861
5954
  command: item.command,
5862
- args: item.args,
5955
+ args: [...item.args],
5863
5956
  status: item.status,
5864
5957
  startedAt: item.startedAt
5865
5958
  };
@@ -5870,11 +5963,11 @@ function normalizeProcesses(value) {
5870
5963
  });
5871
5964
  }
5872
5965
  function normalizeProcessLogs(value, maxBytes) {
5873
- if (!isRecord4(value) || typeof value.stdout !== "string" || typeof value.stderr !== "string" || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
5966
+ if (!isRecord5(value) || !(value.stdout instanceof Uint8Array) || !(value.stderr instanceof Uint8Array) || typeof value.stdoutTruncated !== "boolean" || typeof value.stderrTruncated !== "boolean") {
5874
5967
  throw new TypeError("Sandbox readProcessLogs returned an invalid payload");
5875
5968
  }
5876
- const stdout = boundedLogText(value.stdout, maxBytes);
5877
- const stderr = boundedLogText(value.stderr, maxBytes);
5969
+ const stdout = boundedLogBytes(value.stdout, maxBytes);
5970
+ const stderr = boundedLogBytes(value.stderr, maxBytes);
5878
5971
  return {
5879
5972
  stdout: stdout.text,
5880
5973
  stderr: stderr.text,
@@ -5882,21 +5975,13 @@ function normalizeProcessLogs(value, maxBytes) {
5882
5975
  stderrTruncated: value.stderrTruncated || stderr.truncated
5883
5976
  };
5884
5977
  }
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
- }
5978
+ function boundedLogBytes(value, maxBytes) {
5979
+ if (maxBytes === 0) return { text: "", truncated: value.byteLength > 0 };
5980
+ let start = Math.max(0, value.byteLength - maxBytes);
5981
+ while (start < value.byteLength && ((value[start] ?? 0) & 192) === 128) start += 1;
5897
5982
  return {
5898
- text: new TextDecoder().decode(bytes.subarray(start)),
5899
- truncated: true
5983
+ text: new TextDecoder("utf-8", { fatal: true }).decode(value.subarray(start)),
5984
+ truncated: start > 0
5900
5985
  };
5901
5986
  }
5902
5987
  function normalizeSandboxPath(input, allowRoot) {
@@ -5919,9 +6004,7 @@ function normalizeSandboxPath(input, allowRoot) {
5919
6004
  return normalized;
5920
6005
  }
5921
6006
  function parseTailBytes(value) {
5922
- if (value === void 0) {
5923
- return defaultProcessLogBytes;
5924
- }
6007
+ if (value === void 0) return defaultProcessLogBytes;
5925
6008
  const parsed = Number(value);
5926
6009
  if (!Number.isInteger(parsed) || parsed < 0 || parsed > maxProcessLogBytes) {
5927
6010
  throw new SandboxRouteError(
@@ -5942,15 +6025,13 @@ function sandboxErrorResponse(c, error) {
5942
6025
  if (error instanceof SandboxRouteError) {
5943
6026
  return errorResponse(c, error.status, error.code, error.message);
5944
6027
  }
5945
- const name = errorName(error);
5946
- if (name === "SandboxPathError") {
5947
- return errorResponse(c, 400, "bad_request", errorMessage(error));
5948
- }
5949
- if (name === "SandboxFileSizeError") {
6028
+ const code = errorCode(error);
6029
+ if (code === "invalid_path") return errorResponse(c, 400, "bad_request", errorMessage(error));
6030
+ if (code === "file_too_large") {
5950
6031
  return errorResponse(c, 413, "payload_too_large", errorMessage(error));
5951
6032
  }
5952
- if (name === "SandboxSessionDestroyedError") {
5953
- return errorResponse(c, 409, "conflict", "Sandbox session is no longer available");
6033
+ if (code === "invalid_state" || code === "sandbox_not_found") {
6034
+ return errorResponse(c, 409, "conflict", "Sandbox is no longer available");
5954
6035
  }
5955
6036
  return errorResponse(
5956
6037
  c,
@@ -5976,6 +6057,18 @@ var SandboxRouteError = class extends Error {
5976
6057
  status;
5977
6058
  code;
5978
6059
  };
6060
+ function sortedUnique(values) {
6061
+ if (!Array.isArray(values)) throw new TypeError("Sandbox associations must be arrays.");
6062
+ const result = /* @__PURE__ */ new Set();
6063
+ for (const value of values) {
6064
+ if (typeof value !== "string" || value.length === 0) {
6065
+ throw new TypeError("Sandbox associations must contain non-empty strings.");
6066
+ }
6067
+ if (result.has(value)) throw new TypeError(`Sandbox association is duplicated: ${value}`);
6068
+ result.add(value);
6069
+ }
6070
+ return [...result].sort();
6071
+ }
5979
6072
  function isFileType(value) {
5980
6073
  return value === "file" || value === "directory" || value === "symlink" || value === "other";
5981
6074
  }
@@ -5985,14 +6078,14 @@ function isProcessStatus(value) {
5985
6078
  function isPort(value) {
5986
6079
  return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 65535;
5987
6080
  }
5988
- function isRecord4(value) {
5989
- return typeof value === "object" && value !== null;
6081
+ function isRecord5(value) {
6082
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5990
6083
  }
5991
- function errorName(error) {
5992
- return isRecord4(error) && typeof error.name === "string" ? error.name : "";
6084
+ function errorCode(error) {
6085
+ return isRecord5(error) && typeof error.code === "string" ? error.code : void 0;
5993
6086
  }
5994
6087
  function errorMessage(error) {
5995
- return isRecord4(error) && typeof error.message === "string" ? error.message : String(error);
6088
+ return isRecord5(error) && typeof error.message === "string" ? error.message : "Sandbox failed";
5996
6089
  }
5997
6090
 
5998
6091
  // src/runtime/sessions.ts
@@ -6143,6 +6236,16 @@ function createInMemoryStudioStore() {
6143
6236
  }
6144
6237
  var InMemoryStudioStore = class {
6145
6238
  kind = "memory";
6239
+ compaction = {
6240
+ snapshot: ({ scope }) => {
6241
+ const messages = this.sessions.get(scope.sessionId)?.messages ?? [];
6242
+ return Promise.resolve({
6243
+ revision: memoryRevision(messages),
6244
+ messages: cloneMessages(messages)
6245
+ });
6246
+ },
6247
+ replacePrefix: (options) => this.replaceCompactionPrefix(options)
6248
+ };
6146
6249
  sessions = /* @__PURE__ */ new Map();
6147
6250
  traces = /* @__PURE__ */ new Map();
6148
6251
  pipelineLogs = /* @__PURE__ */ new Map();
@@ -6184,20 +6287,20 @@ var InMemoryStudioStore = class {
6184
6287
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6185
6288
  return materializeSession(session);
6186
6289
  }
6187
- load(context) {
6188
- return Promise.resolve(this.sessions.get(context.sessionId)?.messages ?? []);
6290
+ load({ scope }) {
6291
+ return Promise.resolve(cloneMessages(this.sessions.get(scope.sessionId)?.messages ?? []));
6189
6292
  }
6190
6293
  append(input) {
6191
- const session = this.sessions.get(input.context.sessionId);
6294
+ const session = this.sessions.get(input.scope.sessionId);
6192
6295
  if (session !== void 0) {
6193
- session.messages.push(...input.messages);
6296
+ session.messages.push(...cloneMessages(input.messages));
6194
6297
  session.messageCount = session.messages.length;
6195
6298
  session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6196
6299
  }
6197
6300
  return Promise.resolve();
6198
6301
  }
6199
- clear(context) {
6200
- const session = this.sessions.get(context.sessionId);
6302
+ clear({ scope }) {
6303
+ const session = this.sessions.get(scope.sessionId);
6201
6304
  if (session !== void 0) {
6202
6305
  session.messages = [];
6203
6306
  session.runs = [];
@@ -6208,13 +6311,26 @@ var InMemoryStudioStore = class {
6208
6311
  }
6209
6312
  async recordError(input) {
6210
6313
  await this.saveSessionRunTranscript({
6211
- id: input.context.sessionId,
6212
- runId: studioRunId(input.context) ?? input.runId,
6314
+ id: input.scope.sessionId,
6315
+ runId: studioRunId(input.scope) ?? input.runId,
6213
6316
  transcript: transcriptFromMessages(input.messages),
6214
6317
  status: "error",
6215
6318
  error: serializeJsonError(input.error)
6216
6319
  });
6217
6320
  }
6321
+ replaceCompactionPrefix(input) {
6322
+ if (!Number.isSafeInteger(input.messageCount) || input.messageCount < 1) {
6323
+ throw new RangeError("messageCount must be a positive integer.");
6324
+ }
6325
+ const session = this.sessions.get(input.scope.sessionId);
6326
+ if (session === void 0 || memoryRevision(session.messages) !== input.revision || input.messageCount > session.messages.length) {
6327
+ return Promise.resolve({ status: "conflict" });
6328
+ }
6329
+ session.messages.splice(0, input.messageCount, structuredClone(input.replacement));
6330
+ session.messageCount = session.messages.length;
6331
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
6332
+ return Promise.resolve({ status: "committed" });
6333
+ }
6218
6334
  saveSessionRunTranscript(input) {
6219
6335
  const session = this.sessions.get(input.id);
6220
6336
  if (session === void 0) {
@@ -6340,18 +6456,24 @@ function sessionSummary(session) {
6340
6456
  function materializeSession(session) {
6341
6457
  return {
6342
6458
  ...sessionSummary(session),
6343
- messages: [...session.messages],
6459
+ messages: cloneMessages(session.messages),
6344
6460
  transcript: renumberTranscript(session.runs.flatMap((run) => run.transcript))
6345
6461
  };
6346
6462
  }
6463
+ function cloneMessages(messages) {
6464
+ return structuredClone([...messages]);
6465
+ }
6347
6466
  function traceAgentId(trace) {
6348
6467
  const nestedMetadata = trace.metadata?.metadata;
6349
6468
  return isJsonObject(nestedMetadata) && typeof nestedMetadata.agentId === "string" ? nestedMetadata.agentId : void 0;
6350
6469
  }
6351
- function studioRunId(context) {
6352
- const value = context.metadata?.studioRunId;
6470
+ function studioRunId(scope) {
6471
+ const value = scope.metadata?.studioRunId;
6353
6472
  return typeof value === "string" && value.length > 0 ? value : void 0;
6354
6473
  }
6474
+ function memoryRevision(messages) {
6475
+ return JSON.stringify(messages);
6476
+ }
6355
6477
  function serializeJsonError(error) {
6356
6478
  if (error instanceof Error) {
6357
6479
  return {
@@ -6687,6 +6809,7 @@ var Studio = class {
6687
6809
  options;
6688
6810
  studio;
6689
6811
  server;
6812
+ websocketServer;
6690
6813
  sigintHandler;
6691
6814
  constructor(targets = [], options = {}) {
6692
6815
  this.options = studioOptionsFromTargets(targets, options);
@@ -6711,9 +6834,15 @@ var Studio = class {
6711
6834
  this.studio = createStudioApp(this.options);
6712
6835
  const port = serveOptions.port ?? Number(process.env.RUNNER_PORT ?? 4021);
6713
6836
  const serverOptions = {
6714
- fetch: (request) => this.fetch(request),
6837
+ fetch: (request, env) => this.studio.app.fetch(request, env),
6715
6838
  port
6716
6839
  };
6840
+ this.websocketServer = new WebSocketServer({ noServer: true });
6841
+ serverOptions.websocket = {
6842
+ // @types/ws overloads are stricter than Hono's structural adapter, while Hono officially
6843
+ // supports ws.WebSocketServer. Keep the interop assertion at this single ownership boundary.
6844
+ server: this.websocketServer
6845
+ };
6717
6846
  if (serveOptions.hostname !== void 0) serverOptions.hostname = serveOptions.hostname;
6718
6847
  this.server = serve(serverOptions);
6719
6848
  const log = serveOptions.log ?? true;
@@ -6758,6 +6887,8 @@ var Studio = class {
6758
6887
  }
6759
6888
  this.server?.close();
6760
6889
  this.server = void 0;
6890
+ this.websocketServer?.close();
6891
+ this.websocketServer = void 0;
6761
6892
  this.studio.close();
6762
6893
  }
6763
6894
  logAddress(host, port) {
@@ -6836,6 +6967,21 @@ function studioOptionsFromTargets(targets, options) {
6836
6967
  if (options.models !== void 0) runtimeOptions.models = options.models;
6837
6968
  if (options.stores !== void 0) runtimeOptions.stores = options.stores;
6838
6969
  if (options.ui !== void 0) runtimeOptions.ui = options.ui;
6970
+ if (options.sandboxes !== void 0) {
6971
+ runtimeOptions.sandboxes = options.sandboxes.map((registration) => {
6972
+ let snapshot = { inspector: registration.inspector };
6973
+ if (registration.agentIds !== void 0) {
6974
+ snapshot = { ...snapshot, agentIds: [...registration.agentIds] };
6975
+ }
6976
+ if (registration.toolNames !== void 0) {
6977
+ snapshot = { ...snapshot, toolNames: [...registration.toolNames] };
6978
+ }
6979
+ if (registration.views !== void 0) {
6980
+ snapshot = { ...snapshot, views: [...registration.views] };
6981
+ }
6982
+ return snapshot;
6983
+ });
6984
+ }
6839
6985
  return runtimeOptions;
6840
6986
  }
6841
6987
  function inferStudioAgents(agents, quickPrompts) {
@@ -6877,11 +7023,11 @@ function uniqueAgentId(baseId, ids) {
6877
7023
  function agentMetadata(agent) {
6878
7024
  const metadata = {
6879
7025
  staticContextCount: staticContextDocuments(agent).length,
6880
- dynamicContextCount: contextIndexes(agent).length,
7026
+ dynamicContextCount: vectorContexts(agent).length,
6881
7027
  dynamicToolCount: getAgentToolState3(agent).toolIndexes.length,
6882
7028
  hasLifecycle: agent.lifecycle !== void 0,
6883
7029
  hasOutputSchema: agent.outputSchema !== void 0,
6884
- observerCount: agent.observers.length,
7030
+ observerCount: Object.keys(agent.observability?.observers ?? {}).length,
6885
7031
  approvalToolCount: agent.tools.filter(toolRequiresApproval).length
6886
7032
  };
6887
7033
  if (agent.defaultMaxTurns !== void 0) metadata.defaultMaxTurns = agent.defaultMaxTurns;
@@ -6898,9 +7044,8 @@ function createStudioApp(options) {
6898
7044
  const agentMap = new Map(agents.map((agent) => [agent.id, agent]));
6899
7045
  const pipelineMap = new Map(pipelines.map((pipeline) => [pipeline.id, pipeline]));
6900
7046
  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);
7047
+ const continuationRegistry = createStudioContinuationRegistry();
7048
+ const sandboxRegistry = createStudioSandboxRegistry(agents, options.sandboxes ?? []);
6904
7049
  const memorySources = createStudioMemorySourceRegistry(agents, stores.sessions);
6905
7050
  const app = new HonoApp();
6906
7051
  const uiOptions = isStudioUiEnabled(options.ui) ? resolveStudioUiOptions(options.ui) : void 0;
@@ -6951,8 +7096,7 @@ function createStudioApp(options) {
6951
7096
  registerMcpRoutes(app, { agentMap });
6952
7097
  registerToolRoutes(app, { agentMap });
6953
7098
  registerSandboxRoutes(app, sandboxRegistry);
6954
- registerApprovalRoutes(app, approvalRuntime);
6955
- registerQuestionRoutes(app, questionRuntime);
7099
+ const closeSandboxViews = registerSandboxViewRoutes(app, sandboxRegistry);
6956
7100
  registerObservabilityRoutes(app, observabilityHub);
6957
7101
  registerEvalRoutes(app, {
6958
7102
  evals: options.evals,
@@ -6972,8 +7116,7 @@ function createStudioApp(options) {
6972
7116
  agentMap,
6973
7117
  stores,
6974
7118
  modelRegistry,
6975
- approvalRuntime,
6976
- questionRuntime
7119
+ continuationRegistry
6977
7120
  });
6978
7121
  if (memorySources.size > 0 || stores.sessions !== void 0) {
6979
7122
  registerMemoryRoutes(app, {
@@ -7005,6 +7148,8 @@ function createStudioApp(options) {
7005
7148
  return buildConfig(options, agents, pipelines, stores, sandboxRegistry.size);
7006
7149
  },
7007
7150
  close() {
7151
+ continuationRegistry.clear();
7152
+ closeSandboxViews();
7008
7153
  }
7009
7154
  };
7010
7155
  if (stores.sessions !== void 0) Object.assign(studio, { sessionStore: stores.sessions });
@@ -7012,30 +7157,37 @@ function createStudioApp(options) {
7012
7157
  return studio;
7013
7158
  }
7014
7159
  function withStudioTraceObserver(studioAgent, traceStore) {
7015
- if (traceStore === void 0 || hasStudioTraceObserver(studioAgent.agent)) {
7016
- return studioAgent;
7160
+ if (traceStore === void 0) return studioAgent;
7161
+ const existing = studioAgent.agent.observability;
7162
+ const studioObserver = existing?.observers.studio;
7163
+ if (studioObserver !== void 0 && !(studioObserver instanceof StudioTraceObserver)) {
7164
+ throw new TypeError('Studio reserves the observer name "studio" for local trace persistence.');
7165
+ }
7166
+ for (const [name, observer] of Object.entries(existing?.observers ?? {})) {
7167
+ if (observer instanceof StudioTraceObserver && name !== "studio") {
7168
+ throw new TypeError('StudioTraceObserver must be registered with the name "studio".');
7169
+ }
7017
7170
  }
7018
7171
  return {
7019
7172
  ...studioAgent,
7020
7173
  agent: cloneAgent(studioAgent.agent, {
7021
- observers: [
7022
- ...studioAgent.agent.observers,
7023
- { observer: new StudioTraceObserver({ store: traceStore }) }
7024
- ]
7174
+ observability: {
7175
+ observers: {
7176
+ ...existing?.observers ?? {},
7177
+ studio: studioObserver ?? new StudioTraceObserver({ store: traceStore })
7178
+ },
7179
+ primaryTrace: "studio",
7180
+ errorPolicy: existing?.errorPolicy ?? "ignore"
7181
+ }
7025
7182
  })
7026
7183
  };
7027
7184
  }
7028
- function hasStudioTraceObserver(agent) {
7029
- return agent.observers.some(
7030
- (registration) => registration.observer instanceof StudioTraceObserver
7031
- );
7032
- }
7033
7185
 
7034
7186
  // src/storage/sqlite-store.ts
7035
7187
  import { mkdirSync } from "fs";
7036
7188
  import { createRequire } from "module";
7037
7189
  import { dirname, resolve } from "path";
7038
- import { isJsonValue as isJsonValue2 } from "@anvia/core/completion";
7190
+ import { isJsonValue as isJsonValue3 } from "@anvia/core/completion";
7039
7191
  var DatabaseSync;
7040
7192
  function createSqliteSessionStore(options = {}) {
7041
7193
  return new SqliteSessionStore(options.path ?? ":memory:");
@@ -7046,6 +7198,13 @@ var SqliteSessionStore = class {
7046
7198
  }
7047
7199
  path;
7048
7200
  kind = "sqlite";
7201
+ compaction = {
7202
+ snapshot: ({ scope }) => {
7203
+ const messages = this.listSessionMessages(scope.sessionId);
7204
+ return Promise.resolve({ revision: memoryRevision2(messages), messages });
7205
+ },
7206
+ replacePrefix: (options) => this.replaceCompactionPrefix(options)
7207
+ };
7049
7208
  db;
7050
7209
  listSessions(options) {
7051
7210
  const db = this.database();
@@ -7117,8 +7276,8 @@ var SqliteSessionStore = class {
7117
7276
  }
7118
7277
  return this.getSession(id);
7119
7278
  }
7120
- load(context) {
7121
- const session = this.getSession(context.sessionId);
7279
+ load({ scope }) {
7280
+ const session = this.getSession(scope.sessionId);
7122
7281
  return Promise.resolve(session?.messages ?? []);
7123
7282
  }
7124
7283
  append(input) {
@@ -7129,20 +7288,20 @@ var SqliteSessionStore = class {
7129
7288
  `SELECT id, agent_id, title, metadata_json, created_at, updated_at
7130
7289
  FROM anvia_studio_sessions
7131
7290
  WHERE id = $id`
7132
- ).get({ $id: input.context.sessionId });
7291
+ ).get({ $id: input.scope.sessionId });
7133
7292
  if (row === void 0) {
7134
7293
  db.exec("ROLLBACK");
7135
7294
  return Promise.resolve();
7136
7295
  }
7137
7296
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7138
- const nextIndex = this.nextMessageIndex(input.context.sessionId);
7139
- this.insertMessages(input.context.sessionId, input.messages, nextIndex, updatedAt);
7297
+ const nextIndex = this.nextMessageIndex(input.scope.sessionId);
7298
+ this.insertMessages(input.scope.sessionId, input.messages, nextIndex, updatedAt);
7140
7299
  db.prepare(
7141
7300
  `UPDATE anvia_studio_sessions
7142
7301
  SET updated_at = $updatedAt
7143
7302
  WHERE id = $id`
7144
7303
  ).run({
7145
- $id: input.context.sessionId,
7304
+ $id: input.scope.sessionId,
7146
7305
  $updatedAt: updatedAt
7147
7306
  });
7148
7307
  db.exec("COMMIT");
@@ -7154,7 +7313,7 @@ var SqliteSessionStore = class {
7154
7313
  throw error;
7155
7314
  }
7156
7315
  }
7157
- clear(context) {
7316
+ clear({ scope }) {
7158
7317
  const db = this.database();
7159
7318
  const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7160
7319
  try {
@@ -7164,14 +7323,14 @@ var SqliteSessionStore = class {
7164
7323
  SET updated_at = $updatedAt
7165
7324
  WHERE id = $id`
7166
7325
  ).run({
7167
- $id: context.sessionId,
7326
+ $id: scope.sessionId,
7168
7327
  $updatedAt: updatedAt
7169
7328
  });
7170
7329
  db.prepare("DELETE FROM anvia_studio_session_messages WHERE session_id = $id").run({
7171
- $id: context.sessionId
7330
+ $id: scope.sessionId
7172
7331
  });
7173
7332
  db.prepare("DELETE FROM anvia_studio_session_runs WHERE session_id = $id").run({
7174
- $id: context.sessionId
7333
+ $id: scope.sessionId
7175
7334
  });
7176
7335
  db.exec("COMMIT");
7177
7336
  return Promise.resolve();
@@ -7183,17 +7342,51 @@ var SqliteSessionStore = class {
7183
7342
  }
7184
7343
  }
7185
7344
  async recordError(input) {
7186
- const runId = studioRunId2(input.context) ?? input.runId;
7187
- const existing = this.getSessionRun(input.context.sessionId, runId);
7345
+ const runId = studioRunId2(input.scope) ?? input.runId;
7346
+ const existing = this.getSessionRun(input.scope.sessionId, runId);
7188
7347
  const transcript = existing === void 0 || parseJsonArray(existing.transcript_json).length === 0 ? transcriptFromMessages(input.messages) : parseJsonArray(existing.transcript_json);
7189
7348
  await this.saveSessionRunTranscript({
7190
- id: input.context.sessionId,
7349
+ id: input.scope.sessionId,
7191
7350
  runId,
7192
7351
  transcript,
7193
7352
  status: "error",
7194
7353
  error: serializeJsonError2(input.error)
7195
7354
  });
7196
7355
  }
7356
+ replaceCompactionPrefix(input) {
7357
+ if (!Number.isSafeInteger(input.messageCount) || input.messageCount < 1) {
7358
+ throw new RangeError("messageCount must be a positive integer.");
7359
+ }
7360
+ const db = this.database();
7361
+ try {
7362
+ db.exec("BEGIN IMMEDIATE");
7363
+ const messages = this.listSessionMessages(input.scope.sessionId);
7364
+ if (memoryRevision2(messages) !== input.revision || input.messageCount > messages.length || this.getSessionRow(input.scope.sessionId) === void 0) {
7365
+ db.exec("ROLLBACK");
7366
+ return Promise.resolve({ status: "conflict" });
7367
+ }
7368
+ db.prepare("DELETE FROM anvia_studio_session_messages WHERE session_id = $id").run({
7369
+ $id: input.scope.sessionId
7370
+ });
7371
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
7372
+ this.insertMessages(
7373
+ input.scope.sessionId,
7374
+ [input.replacement, ...messages.slice(input.messageCount)],
7375
+ 0,
7376
+ updatedAt
7377
+ );
7378
+ db.prepare(
7379
+ `UPDATE anvia_studio_sessions
7380
+ SET updated_at = $updatedAt
7381
+ WHERE id = $id`
7382
+ ).run({ $id: input.scope.sessionId, $updatedAt: updatedAt });
7383
+ db.exec("COMMIT");
7384
+ return Promise.resolve({ status: "committed" });
7385
+ } catch (error) {
7386
+ if (db.isTransaction) db.exec("ROLLBACK");
7387
+ throw error;
7388
+ }
7389
+ }
7197
7390
  saveSessionRunTranscript(input) {
7198
7391
  const db = this.database();
7199
7392
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -7889,7 +8082,7 @@ var SqliteSessionStore = class {
7889
8082
  );
7890
8083
  messages.forEach((message, messageOffset) => {
7891
8084
  const messageIndex = startIndex + messageOffset;
7892
- if (message.metadata !== void 0 && !isJsonValue2(message.metadata)) {
8085
+ if (message.metadata !== void 0 && !isJsonValue3(message.metadata)) {
7893
8086
  throw new TypeError("Studio message metadata must be a strict JSON value.");
7894
8087
  }
7895
8088
  insertMessage.run({
@@ -7990,6 +8183,9 @@ function messageParts(message) {
7990
8183
  if (message.role === "system") {
7991
8184
  return [{ type: "text", value: { type: "text", text: message.content } }];
7992
8185
  }
8186
+ if (typeof message.content === "string") {
8187
+ return [{ type: "text", value: { type: "text", text: message.content } }];
8188
+ }
7993
8189
  return message.content.map((content) => ({
7994
8190
  type: content.type,
7995
8191
  value: content
@@ -7998,40 +8194,40 @@ function messageParts(message) {
7998
8194
  function messageFromRows(row, partRows) {
7999
8195
  const parts = partRows.map((partRow) => JSON.parse(partRow.part_json));
8000
8196
  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.");
8197
+ if (metadata !== void 0 && (!isJsonValue3(metadata) || typeof metadata !== "object" || metadata === null || Array.isArray(metadata))) {
8198
+ throw new TypeError("Stored Studio message metadata is not a strict JSON object.");
8003
8199
  }
8004
8200
  if (row.role === "system") {
8005
- const message = {
8201
+ let message = {
8006
8202
  role: "system",
8007
8203
  content: systemContentFromParts(parts)
8008
8204
  };
8009
- if (metadata !== void 0) message.metadata = metadata;
8205
+ if (metadata !== void 0) message = { ...message, metadata };
8010
8206
  return message;
8011
8207
  }
8012
8208
  if (row.role === "user") {
8013
- const message = {
8209
+ let message = {
8014
8210
  role: "user",
8015
8211
  content: parts
8016
8212
  };
8017
- if (metadata !== void 0) message.metadata = metadata;
8213
+ if (metadata !== void 0) message = { ...message, metadata };
8018
8214
  return message;
8019
8215
  }
8020
8216
  if (row.role === "assistant") {
8021
- const message = {
8217
+ let message = {
8022
8218
  role: "assistant",
8023
8219
  content: parts
8024
8220
  };
8025
- if (row.message_id !== null) message.id = row.message_id;
8026
- if (metadata !== void 0) message.metadata = metadata;
8221
+ if (row.message_id !== null) message = { ...message, id: row.message_id };
8222
+ if (metadata !== void 0) message = { ...message, metadata };
8027
8223
  return message;
8028
8224
  }
8029
8225
  if (row.role === "tool") {
8030
- const message = {
8226
+ let message = {
8031
8227
  role: "tool",
8032
8228
  content: parts
8033
8229
  };
8034
- if (metadata !== void 0) message.metadata = metadata;
8230
+ if (metadata !== void 0) message = { ...message, metadata };
8035
8231
  return message;
8036
8232
  }
8037
8233
  throw new Error(`Unsupported stored message role: ${row.role}`);
@@ -8116,10 +8312,13 @@ function parseJsonValue(value) {
8116
8312
  }
8117
8313
  return JSON.parse(value);
8118
8314
  }
8119
- function studioRunId2(context) {
8120
- const value = context.metadata?.studioRunId;
8315
+ function studioRunId2(scope) {
8316
+ const value = scope.metadata?.studioRunId;
8121
8317
  return typeof value === "string" && value.length > 0 ? value : void 0;
8122
8318
  }
8319
+ function memoryRevision2(messages) {
8320
+ return JSON.stringify(messages);
8321
+ }
8123
8322
  function serializeJsonError2(error) {
8124
8323
  if (error instanceof Error) {
8125
8324
  return {