@webless/agent 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,6 +7,26 @@ import {
7
7
  // src/runtime/capability.ts
8
8
  import { ClientError } from "eve/client";
9
9
  var MAX_REFRESH_SKEW_MS = 3e4;
10
+ var LOCAL_LOOPBACK_ORIGINS = [
11
+ "http://127.0.0.1:3010",
12
+ "http://127.0.0.1:3001"
13
+ ];
14
+ function isLoopbackRuntimeOrigin(origin) {
15
+ try {
16
+ const host = new URL(origin).hostname;
17
+ return host === "127.0.0.1" || host === "localhost";
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+ function localBootstrapOrigins(origin) {
23
+ const normalized = origin.replace(/\/$/, "");
24
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
25
+ return [
26
+ normalized,
27
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
28
+ ];
29
+ }
10
30
  function isRecord(value) {
11
31
  return typeof value === "object" && value !== null && !Array.isArray(value);
12
32
  }
@@ -53,20 +73,31 @@ function createAgentRuntimeCapability(options) {
53
73
  );
54
74
  }
55
75
  }
56
- const response = await fetchImplementation(
57
- `${options.runtimeOrigin}/webless/v1/bootstrap`,
58
- {
59
- body: JSON.stringify({
60
- clientSessionId: options.visitorSessionId,
61
- indexId: options.indexId,
62
- ...previewBuildId ? { previewBuildId } : {},
63
- ...previewGrant ? { previewGrant } : {},
64
- version: options.version
65
- }),
66
- headers: { "content-type": "application/json" },
67
- method: "POST"
76
+ const bootstrapBody = JSON.stringify({
77
+ clientSessionId: options.visitorSessionId,
78
+ indexId: options.indexId,
79
+ ...previewBuildId ? { previewBuildId } : {},
80
+ ...previewGrant ? { previewGrant } : {},
81
+ version: options.version
82
+ });
83
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
84
+ body: bootstrapBody,
85
+ headers: { "content-type": "application/json" },
86
+ method: "POST"
87
+ });
88
+ let response;
89
+ let lastError;
90
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
91
+ try {
92
+ response = await postBootstrap(origin);
93
+ break;
94
+ } catch (error) {
95
+ lastError = error;
68
96
  }
69
- );
97
+ }
98
+ if (!response) {
99
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
100
+ }
70
101
  if (!response.ok) {
71
102
  throw new Error(await readBootstrapError(response));
72
103
  }
@@ -237,6 +268,226 @@ function clearPersistedAgentSession(visitorSessionId, options) {
237
268
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
238
269
  }
239
270
 
271
+ // src/runtime/tool-ui.ts
272
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
273
+ function isRecord2(value) {
274
+ return value !== null && typeof value === "object" && !Array.isArray(value);
275
+ }
276
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
277
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
278
+ return true;
279
+ }
280
+ if (typeof value === "number") return Number.isFinite(value);
281
+ if (typeof value !== "object") return false;
282
+ if (seen.has(value)) return false;
283
+ seen.add(value);
284
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
285
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
286
+ );
287
+ seen.delete(value);
288
+ return valid;
289
+ }
290
+ function boundedString(value, max) {
291
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
292
+ return void 0;
293
+ }
294
+ return value.trim();
295
+ }
296
+ function numberValue(value) {
297
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
298
+ }
299
+ function integerValue(value) {
300
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
301
+ }
302
+ function isFieldKind(value) {
303
+ return typeof value === "string" && [
304
+ "text",
305
+ "textarea",
306
+ "email",
307
+ "number",
308
+ "select",
309
+ "multi-select",
310
+ "checkbox",
311
+ "confirmation",
312
+ "radio",
313
+ "date",
314
+ "time",
315
+ "date-time",
316
+ "calendar",
317
+ "range",
318
+ "json"
319
+ ].includes(value);
320
+ }
321
+ function parseField(value) {
322
+ if (!isRecord2(value)) return null;
323
+ if (!hasOnlyKeys(value, [
324
+ "description",
325
+ "kind",
326
+ "label",
327
+ "max",
328
+ "maxItems",
329
+ "maxLength",
330
+ "min",
331
+ "minLength",
332
+ "options",
333
+ "path",
334
+ "placeholder",
335
+ "required",
336
+ "step",
337
+ "defaultValue"
338
+ ])) {
339
+ return null;
340
+ }
341
+ if (!isFieldKind(value.kind)) return null;
342
+ const path = boundedString(value.path, 160);
343
+ const label = boundedString(value.label, 160);
344
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
345
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
346
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
347
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
348
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
349
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
350
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
351
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
352
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
353
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
354
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
355
+ return null;
356
+ }
357
+ if (value.description !== void 0 && !description) return null;
358
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
359
+ if (value.required !== void 0 && required === void 0) return null;
360
+ if (value.min !== void 0 && min === void 0) return null;
361
+ if (value.max !== void 0 && max === void 0) return null;
362
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
363
+ return null;
364
+ if (value.step !== void 0 && step === void 0) return null;
365
+ if (value.minLength !== void 0 && minLength === void 0) return null;
366
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
367
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
368
+ return null;
369
+ if (value.options !== void 0) {
370
+ if (!Array.isArray(value.options) || value.options.length > 100)
371
+ return null;
372
+ for (const option of value.options) {
373
+ if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
374
+ return null;
375
+ }
376
+ }
377
+ }
378
+ return {
379
+ kind: value.kind,
380
+ path,
381
+ label,
382
+ ...description ? { description } : {},
383
+ ...placeholder !== void 0 ? { placeholder } : {},
384
+ ...required !== void 0 ? { required } : {},
385
+ ...defaultValue !== void 0 ? { defaultValue } : {},
386
+ ...value.options !== void 0 ? { options: value.options } : {},
387
+ ...min !== void 0 ? { min } : {},
388
+ ...max !== void 0 ? { max } : {},
389
+ ...maxItems !== void 0 ? { maxItems } : {},
390
+ ...step !== void 0 ? { step } : {},
391
+ ...minLength !== void 0 ? { minLength } : {},
392
+ ...maxLength !== void 0 ? { maxLength } : {}
393
+ };
394
+ }
395
+ function parseStep(value) {
396
+ if (!isRecord2(value)) return null;
397
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
398
+ return null;
399
+ }
400
+ const id = boundedString(value.id, 80);
401
+ const label = boundedString(value.label, 160);
402
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
403
+ if (!id || !/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id) || !label || value.description !== void 0 && !description || !Array.isArray(value.fieldPaths) || value.fieldPaths.length < 1 || value.fieldPaths.length > 32 || value.fieldPaths.some(
404
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
405
+ )) {
406
+ return null;
407
+ }
408
+ return {
409
+ id,
410
+ label,
411
+ fieldPaths: value.fieldPaths,
412
+ ...description ? { description } : {}
413
+ };
414
+ }
415
+ function parseAction(value) {
416
+ if (!isRecord2(value)) return null;
417
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
418
+ const label = boundedString(value.label, 80);
419
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
420
+ return null;
421
+ }
422
+ return {
423
+ id: value.id,
424
+ label
425
+ };
426
+ }
427
+ function parseAgentToolUiSurface(value) {
428
+ if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
429
+ return null;
430
+ if (!hasOnlyKeys(value, [
431
+ "actions",
432
+ "description",
433
+ "fields",
434
+ "id",
435
+ "operationId",
436
+ "requestId",
437
+ "schemaVersion",
438
+ "steps",
439
+ "submitLabel",
440
+ "title",
441
+ "toolSlug",
442
+ "values"
443
+ ])) {
444
+ return null;
445
+ }
446
+ const id = boundedString(value.id, 200);
447
+ const title = boundedString(value.title, 200);
448
+ const toolSlug = boundedString(value.toolSlug, 200);
449
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
450
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
451
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
452
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
453
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
454
+ return null;
455
+ }
456
+ const fields = value.fields.map(parseField);
457
+ if (fields.some((field) => field === null)) return null;
458
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
459
+ if (steps?.some((step) => step === null)) return null;
460
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
461
+ if (actions?.some((action) => action === null)) return null;
462
+ if (value.description !== void 0 && !description) return null;
463
+ if (value.operationId !== void 0 && !operationId) return null;
464
+ if (value.requestId !== void 0 && !requestId) return null;
465
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
466
+ const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
467
+ if (value.values !== void 0) {
468
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
469
+ return null;
470
+ }
471
+ return {
472
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
473
+ id,
474
+ title,
475
+ toolSlug,
476
+ fields,
477
+ ...actions ? { actions } : {},
478
+ ...description ? { description } : {},
479
+ ...operationId ? { operationId } : {},
480
+ ...requestId ? { requestId } : {},
481
+ ...submitLabel ? { submitLabel } : {},
482
+ ...steps ? { steps } : {},
483
+ ...values ? { values } : {}
484
+ };
485
+ }
486
+ function hasOnlyKeys(value, allowed) {
487
+ const allowedKeys = new Set(allowed);
488
+ return Object.keys(value).every((key) => allowedKeys.has(key));
489
+ }
490
+
240
491
  // src/runtime/client.ts
241
492
  function isTurnBoundary(event) {
242
493
  return event.type === "session.waiting" || event.type === "session.completed" || event.type === "session.failed";
@@ -249,12 +500,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
249
500
  if (event.type === "message.completed") {
250
501
  handlers.onComplete?.();
251
502
  }
252
- if (event.type === "action.result") {
253
- const result = event.data.result;
254
- if (result && typeof result === "object" && "output" in result) {
255
- handlers.onActionResult?.(result.output);
256
- }
257
- }
503
+ if (event.type === "action.result") emitActionResult(event, handlers);
504
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
258
505
  if (event.type !== "message.appended") return rendered;
259
506
  const { messageDelta, messageSoFar } = event.data;
260
507
  let delta = messageDelta;
@@ -265,9 +512,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
265
512
  } else if (messageDelta) {
266
513
  next += messageDelta;
267
514
  }
268
- if (delta) handlers.onDelta(delta);
515
+ if (delta) handlers.onDelta?.(delta);
269
516
  return next;
270
517
  }
518
+ function emitActionResult(event, handlers) {
519
+ const result = event.data.result;
520
+ if (result.kind === "tool-result") {
521
+ handlers.onToolResult?.({
522
+ callId: result.callId,
523
+ toolName: result.toolName,
524
+ status: event.data.status,
525
+ output: result.output,
526
+ ...event.data.error ? { error: event.data.error } : {}
527
+ });
528
+ }
529
+ if ("output" in result) handlers.onActionResult?.(result.output);
530
+ }
531
+ function emitInputRequests(event, handlers) {
532
+ handlers.onInputRequest?.(
533
+ event.data.requests.map((request) => {
534
+ const ui = parseAgentToolUiSurface(
535
+ request.ui
536
+ );
537
+ return {
538
+ requestId: request.requestId,
539
+ kind: request.kind,
540
+ prompt: request.prompt,
541
+ ...request.display ? { display: request.display } : {},
542
+ ...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
543
+ ...request.options ? { options: request.options } : {},
544
+ ...ui ? { ui } : {},
545
+ action: {
546
+ callId: request.action.callId,
547
+ kind: "tool-call",
548
+ toolName: request.action.toolName
549
+ }
550
+ };
551
+ })
552
+ );
553
+ }
271
554
  function isResumeTurnMessage(received, candidate) {
272
555
  if (received === candidate) return true;
273
556
  return Boolean(candidate) && received.endsWith(`
@@ -543,9 +826,11 @@ var AgentSession = class {
543
826
  let streamIndex = session?.state.streamIndex ?? 0;
544
827
  let rendered = "";
545
828
  const workItems = /* @__PURE__ */ new Map();
829
+ let requestedInput = false;
546
830
  try {
547
831
  for await (const event of response) {
548
832
  if (signal.aborted) break;
833
+ if (event.type === "input.requested") requestedInput = true;
549
834
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
550
835
  streamIndex += 1;
551
836
  if (session) {
@@ -563,7 +848,7 @@ var AgentSession = class {
563
848
  this.persistSessionCursor(session);
564
849
  }
565
850
  }
566
- if (!rendered.trim() && !signal.aborted) {
851
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
567
852
  throw new Error("Empty response from runtime");
568
853
  }
569
854
  return rendered.trim();
@@ -583,6 +868,9 @@ var AgentSession = class {
583
868
  () => attached.snapshot({ signal })
584
869
  );
585
870
  const turnEvents = latestTurnEvents(snapshot.events);
871
+ const hasInputRequest = turnEvents.some(
872
+ (event) => event.type === "input.requested"
873
+ );
586
874
  const received = turnEvents[0];
587
875
  const lastSent = persisted.lastMessage;
588
876
  const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
@@ -593,6 +881,8 @@ var AgentSession = class {
593
881
  const workItems = /* @__PURE__ */ new Map();
594
882
  for (const event of turnEvents) {
595
883
  applyWorkEvent(event, handlers, workItems);
884
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
885
+ if (event.type === "action.result") emitActionResult(event, handlers);
596
886
  }
597
887
  if (rendered.startsWith(initialText)) {
598
888
  const missedText = rendered.slice(initialText.length);
@@ -622,7 +912,9 @@ var AgentSession = class {
622
912
  );
623
913
  }
624
914
  handlers.onComplete?.();
625
- if (!rendered.trim()) throw new Error("Empty response from runtime");
915
+ if (!rendered.trim() && !hasInputRequest) {
916
+ throw new Error("Empty response from runtime");
917
+ }
626
918
  return rendered.trim();
627
919
  }
628
920
  let streamIndex = snapshot.session.streamIndex;
@@ -641,7 +933,55 @@ var AgentSession = class {
641
933
  session = client.sessions.attach(session.state.sessionId, { streamIndex });
642
934
  this.session = session;
643
935
  this.persistSessionCursor(session);
644
- if (!rendered.trim() && !signal.aborted) {
936
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
937
+ throw new Error("Empty response from runtime");
938
+ }
939
+ return rendered.trim();
940
+ }
941
+ async respondTurn(responses, signal, handlers) {
942
+ const client = this.ensureClient();
943
+ const session = this.session ?? this.attachPersistedSession(client);
944
+ if (!session) {
945
+ throw new Error("No active session is waiting for input.");
946
+ }
947
+ this.session = session;
948
+ const inputResponses = responses.map(
949
+ ({ requestId, optionId, text }) => ({
950
+ requestId,
951
+ ...optionId ? { optionId } : {},
952
+ ...text ? { text } : {}
953
+ })
954
+ );
955
+ const response = await withCapabilityRefresh(
956
+ this.capability,
957
+ () => session.respond(inputResponses, { signal })
958
+ );
959
+ this.activeResponse = response;
960
+ let streamIndex = session.state.streamIndex;
961
+ let rendered = "";
962
+ let requestedInput = false;
963
+ const workItems = /* @__PURE__ */ new Map();
964
+ try {
965
+ for await (const event of response) {
966
+ if (signal.aborted) break;
967
+ if (event.type === "input.requested") requestedInput = true;
968
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
969
+ streamIndex += 1;
970
+ savePersistedAgentSession(
971
+ this.visitorSessionId,
972
+ session.state.sessionId,
973
+ streamIndex,
974
+ this.storeOptions
975
+ );
976
+ }
977
+ } finally {
978
+ this.activeResponse = void 0;
979
+ this.session = client.sessions.attach(session.state.sessionId, {
980
+ streamIndex
981
+ });
982
+ this.persistSessionCursor(this.session);
983
+ }
984
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
645
985
  throw new Error("Empty response from runtime");
646
986
  }
647
987
  return rendered.trim();
@@ -701,6 +1041,11 @@ function createAgentClient(options) {
701
1041
  resumeOptions.handlers,
702
1042
  resumeOptions.initialText
703
1043
  ),
1044
+ respondTurn: (respondOptions) => session.respondTurn(
1045
+ respondOptions.responses,
1046
+ respondOptions.signal ?? new AbortController().signal,
1047
+ respondOptions.handlers
1048
+ ),
704
1049
  reset: () => session.reset(),
705
1050
  cancelActive: () => session.cancelActive(),
706
1051
  getActiveSessionId: () => session.getActiveSessionId()
@@ -750,6 +1095,63 @@ function formatAgentError(error) {
750
1095
  return TRANSIENT_AGENT_ERROR_MESSAGE;
751
1096
  }
752
1097
 
1098
+ // src/runtime/tool-result-envelope.ts
1099
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1100
+ "schemaVersion",
1101
+ "output",
1102
+ "presentationKinds",
1103
+ "ui"
1104
+ ]);
1105
+ function isRecord3(value) {
1106
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1107
+ }
1108
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
1109
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
1110
+ return true;
1111
+ }
1112
+ if (typeof value === "number") return Number.isFinite(value);
1113
+ if (typeof value !== "object") return false;
1114
+ if (seen.has(value)) return false;
1115
+ seen.add(value);
1116
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
1117
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
1118
+ );
1119
+ seen.delete(value);
1120
+ return valid;
1121
+ }
1122
+ function decodeEnvelope(value) {
1123
+ if (typeof value !== "string") return value;
1124
+ try {
1125
+ return JSON.parse(value);
1126
+ } catch {
1127
+ return null;
1128
+ }
1129
+ }
1130
+ function parseAgentToolResultEnvelope(value) {
1131
+ const decoded = decodeEnvelope(value);
1132
+ if (!isRecord3(decoded)) return null;
1133
+ const keys = Object.keys(decoded);
1134
+ if (keys.some((key) => !ENVELOPE_KEYS.has(key)) || decoded.schemaVersion !== "webless.tool-result.v1" || !isJsonValue2(decoded.output) || !Array.isArray(decoded.presentationKinds) || decoded.presentationKinds.length < 1 || decoded.presentationKinds.length > 16) {
1135
+ return null;
1136
+ }
1137
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
1138
+ if (typeof kind !== "string") return [];
1139
+ const normalized = kind.trim();
1140
+ return normalized && normalized.length <= 128 ? [normalized] : [];
1141
+ });
1142
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
1143
+ return null;
1144
+ }
1145
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
1146
+ if (decoded.ui !== void 0 && !ui) return null;
1147
+ return {
1148
+ schemaVersion: "webless.tool-result.v1",
1149
+ output: decoded.output,
1150
+ presentationKinds,
1151
+ ...ui ? { ui } : {}
1152
+ };
1153
+ }
1154
+
753
1155
  // src/runtime/health.ts
754
1156
  async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
755
1157
  const healthUrl = agentHealthUrl(runtimeOrigin);
@@ -771,6 +1173,7 @@ async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
771
1173
  }
772
1174
  }
773
1175
  export {
1176
+ AGENT_TOOL_UI_SCHEMA_VERSION,
774
1177
  DEFAULT_RUNTIME_ORIGIN,
775
1178
  agentHealthUrl,
776
1179
  buildAgentStorageKeyPrefix,
@@ -781,6 +1184,8 @@ export {
781
1184
  formatAgentError,
782
1185
  getOrCreateVisitorSessionId,
783
1186
  loadPersistedAgentSession,
1187
+ parseAgentToolResultEnvelope,
1188
+ parseAgentToolUiSurface,
784
1189
  pingAgentHealth,
785
1190
  resolveAgentRuntimeConfig,
786
1191
  savePersistedAgentSession