@webless/agent 0.6.3 → 0.6.5

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.cjs CHANGED
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ AGENT_TOOL_UI_SCHEMA_VERSION: () => AGENT_TOOL_UI_SCHEMA_VERSION,
23
24
  DEFAULT_RUNTIME_ORIGIN: () => DEFAULT_RUNTIME_ORIGIN,
24
25
  agentHealthUrl: () => agentHealthUrl,
25
26
  buildAgentStorageKeyPrefix: () => buildAgentStorageKeyPrefix,
@@ -30,6 +31,8 @@ __export(index_exports, {
30
31
  formatAgentError: () => formatAgentError,
31
32
  getOrCreateVisitorSessionId: () => getOrCreateVisitorSessionId,
32
33
  loadPersistedAgentSession: () => loadPersistedAgentSession,
34
+ parseAgentToolResultEnvelope: () => parseAgentToolResultEnvelope,
35
+ parseAgentToolUiSurface: () => parseAgentToolUiSurface,
33
36
  pingAgentHealth: () => pingAgentHealth,
34
37
  resolveAgentRuntimeConfig: () => resolveAgentRuntimeConfig,
35
38
  savePersistedAgentSession: () => savePersistedAgentSession
@@ -42,6 +45,26 @@ var import_client2 = require("eve/client");
42
45
  // src/runtime/capability.ts
43
46
  var import_client = require("eve/client");
44
47
  var MAX_REFRESH_SKEW_MS = 3e4;
48
+ var LOCAL_LOOPBACK_ORIGINS = [
49
+ "http://127.0.0.1:3010",
50
+ "http://127.0.0.1:3001"
51
+ ];
52
+ function isLoopbackRuntimeOrigin(origin) {
53
+ try {
54
+ const host = new URL(origin).hostname;
55
+ return host === "127.0.0.1" || host === "localhost";
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+ function localBootstrapOrigins(origin) {
61
+ const normalized = origin.replace(/\/$/, "");
62
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
63
+ return [
64
+ normalized,
65
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
66
+ ];
67
+ }
45
68
  function isRecord(value) {
46
69
  return typeof value === "object" && value !== null && !Array.isArray(value);
47
70
  }
@@ -88,20 +111,31 @@ function createAgentRuntimeCapability(options) {
88
111
  );
89
112
  }
90
113
  }
91
- const response = await fetchImplementation(
92
- `${options.runtimeOrigin}/webless/v1/bootstrap`,
93
- {
94
- body: JSON.stringify({
95
- clientSessionId: options.visitorSessionId,
96
- indexId: options.indexId,
97
- ...previewBuildId ? { previewBuildId } : {},
98
- ...previewGrant ? { previewGrant } : {},
99
- version: options.version
100
- }),
101
- headers: { "content-type": "application/json" },
102
- method: "POST"
114
+ const bootstrapBody = JSON.stringify({
115
+ clientSessionId: options.visitorSessionId,
116
+ indexId: options.indexId,
117
+ ...previewBuildId ? { previewBuildId } : {},
118
+ ...previewGrant ? { previewGrant } : {},
119
+ version: options.version
120
+ });
121
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
122
+ body: bootstrapBody,
123
+ headers: { "content-type": "application/json" },
124
+ method: "POST"
125
+ });
126
+ let response;
127
+ let lastError;
128
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
129
+ try {
130
+ response = await postBootstrap(origin);
131
+ break;
132
+ } catch (error) {
133
+ lastError = error;
103
134
  }
104
- );
135
+ }
136
+ if (!response) {
137
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
138
+ }
105
139
  if (!response.ok) {
106
140
  throw new Error(await readBootstrapError(response));
107
141
  }
@@ -272,6 +306,226 @@ function clearPersistedAgentSession(visitorSessionId, options) {
272
306
  sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
273
307
  }
274
308
 
309
+ // src/runtime/tool-ui.ts
310
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
311
+ function isRecord2(value) {
312
+ return value !== null && typeof value === "object" && !Array.isArray(value);
313
+ }
314
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
315
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
316
+ return true;
317
+ }
318
+ if (typeof value === "number") return Number.isFinite(value);
319
+ if (typeof value !== "object") return false;
320
+ if (seen.has(value)) return false;
321
+ seen.add(value);
322
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
323
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
324
+ );
325
+ seen.delete(value);
326
+ return valid;
327
+ }
328
+ function boundedString(value, max) {
329
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
330
+ return void 0;
331
+ }
332
+ return value.trim();
333
+ }
334
+ function numberValue(value) {
335
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
336
+ }
337
+ function integerValue(value) {
338
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
339
+ }
340
+ function isFieldKind(value) {
341
+ return typeof value === "string" && [
342
+ "text",
343
+ "textarea",
344
+ "email",
345
+ "number",
346
+ "select",
347
+ "multi-select",
348
+ "checkbox",
349
+ "confirmation",
350
+ "radio",
351
+ "date",
352
+ "time",
353
+ "date-time",
354
+ "calendar",
355
+ "range",
356
+ "json"
357
+ ].includes(value);
358
+ }
359
+ function parseField(value) {
360
+ if (!isRecord2(value)) return null;
361
+ if (!hasOnlyKeys(value, [
362
+ "description",
363
+ "kind",
364
+ "label",
365
+ "max",
366
+ "maxItems",
367
+ "maxLength",
368
+ "min",
369
+ "minLength",
370
+ "options",
371
+ "path",
372
+ "placeholder",
373
+ "required",
374
+ "step",
375
+ "defaultValue"
376
+ ])) {
377
+ return null;
378
+ }
379
+ if (!isFieldKind(value.kind)) return null;
380
+ const path = boundedString(value.path, 160);
381
+ const label = boundedString(value.label, 160);
382
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
383
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
384
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
385
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
386
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
387
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
388
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
389
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
390
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
391
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
392
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
393
+ return null;
394
+ }
395
+ if (value.description !== void 0 && !description) return null;
396
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
397
+ if (value.required !== void 0 && required === void 0) return null;
398
+ if (value.min !== void 0 && min === void 0) return null;
399
+ if (value.max !== void 0 && max === void 0) return null;
400
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
401
+ return null;
402
+ if (value.step !== void 0 && step === void 0) return null;
403
+ if (value.minLength !== void 0 && minLength === void 0) return null;
404
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
405
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
406
+ return null;
407
+ if (value.options !== void 0) {
408
+ if (!Array.isArray(value.options) || value.options.length > 100)
409
+ return null;
410
+ for (const option of value.options) {
411
+ if (!isRecord2(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
412
+ return null;
413
+ }
414
+ }
415
+ }
416
+ return {
417
+ kind: value.kind,
418
+ path,
419
+ label,
420
+ ...description ? { description } : {},
421
+ ...placeholder !== void 0 ? { placeholder } : {},
422
+ ...required !== void 0 ? { required } : {},
423
+ ...defaultValue !== void 0 ? { defaultValue } : {},
424
+ ...value.options !== void 0 ? { options: value.options } : {},
425
+ ...min !== void 0 ? { min } : {},
426
+ ...max !== void 0 ? { max } : {},
427
+ ...maxItems !== void 0 ? { maxItems } : {},
428
+ ...step !== void 0 ? { step } : {},
429
+ ...minLength !== void 0 ? { minLength } : {},
430
+ ...maxLength !== void 0 ? { maxLength } : {}
431
+ };
432
+ }
433
+ function parseStep(value) {
434
+ if (!isRecord2(value)) return null;
435
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
436
+ return null;
437
+ }
438
+ const id = boundedString(value.id, 80);
439
+ const label = boundedString(value.label, 160);
440
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
441
+ 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(
442
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
443
+ )) {
444
+ return null;
445
+ }
446
+ return {
447
+ id,
448
+ label,
449
+ fieldPaths: value.fieldPaths,
450
+ ...description ? { description } : {}
451
+ };
452
+ }
453
+ function parseAction(value) {
454
+ if (!isRecord2(value)) return null;
455
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
456
+ const label = boundedString(value.label, 80);
457
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
458
+ return null;
459
+ }
460
+ return {
461
+ id: value.id,
462
+ label
463
+ };
464
+ }
465
+ function parseAgentToolUiSurface(value) {
466
+ if (!isRecord2(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
467
+ return null;
468
+ if (!hasOnlyKeys(value, [
469
+ "actions",
470
+ "description",
471
+ "fields",
472
+ "id",
473
+ "operationId",
474
+ "requestId",
475
+ "schemaVersion",
476
+ "steps",
477
+ "submitLabel",
478
+ "title",
479
+ "toolSlug",
480
+ "values"
481
+ ])) {
482
+ return null;
483
+ }
484
+ const id = boundedString(value.id, 200);
485
+ const title = boundedString(value.title, 200);
486
+ const toolSlug = boundedString(value.toolSlug, 200);
487
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
488
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
489
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
490
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
491
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
492
+ return null;
493
+ }
494
+ const fields = value.fields.map(parseField);
495
+ if (fields.some((field) => field === null)) return null;
496
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
497
+ if (steps?.some((step) => step === null)) return null;
498
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
499
+ if (actions?.some((action) => action === null)) return null;
500
+ if (value.description !== void 0 && !description) return null;
501
+ if (value.operationId !== void 0 && !operationId) return null;
502
+ if (value.requestId !== void 0 && !requestId) return null;
503
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
504
+ const values = value.values !== void 0 && isRecord2(value.values) ? value.values : void 0;
505
+ if (value.values !== void 0) {
506
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
507
+ return null;
508
+ }
509
+ return {
510
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
511
+ id,
512
+ title,
513
+ toolSlug,
514
+ fields,
515
+ ...actions ? { actions } : {},
516
+ ...description ? { description } : {},
517
+ ...operationId ? { operationId } : {},
518
+ ...requestId ? { requestId } : {},
519
+ ...submitLabel ? { submitLabel } : {},
520
+ ...steps ? { steps } : {},
521
+ ...values ? { values } : {}
522
+ };
523
+ }
524
+ function hasOnlyKeys(value, allowed) {
525
+ const allowedKeys = new Set(allowed);
526
+ return Object.keys(value).every((key) => allowedKeys.has(key));
527
+ }
528
+
275
529
  // src/runtime/client.ts
276
530
  function isTurnBoundary(event) {
277
531
  return event.type === "session.waiting" || event.type === "session.completed" || event.type === "session.failed";
@@ -284,12 +538,8 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
284
538
  if (event.type === "message.completed") {
285
539
  handlers.onComplete?.();
286
540
  }
287
- if (event.type === "action.result") {
288
- const result = event.data.result;
289
- if (result && typeof result === "object" && "output" in result) {
290
- handlers.onActionResult?.(result.output);
291
- }
292
- }
541
+ if (event.type === "action.result") emitActionResult(event, handlers);
542
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
293
543
  if (event.type !== "message.appended") return rendered;
294
544
  const { messageDelta, messageSoFar } = event.data;
295
545
  let delta = messageDelta;
@@ -300,9 +550,45 @@ function applyMessageEvent(event, rendered, handlers, workItems) {
300
550
  } else if (messageDelta) {
301
551
  next += messageDelta;
302
552
  }
303
- if (delta) handlers.onDelta(delta);
553
+ if (delta) handlers.onDelta?.(delta);
304
554
  return next;
305
555
  }
556
+ function emitActionResult(event, handlers) {
557
+ const result = event.data.result;
558
+ if (result.kind === "tool-result") {
559
+ handlers.onToolResult?.({
560
+ callId: result.callId,
561
+ toolName: result.toolName,
562
+ status: event.data.status,
563
+ output: result.output,
564
+ ...event.data.error ? { error: event.data.error } : {}
565
+ });
566
+ }
567
+ if ("output" in result) handlers.onActionResult?.(result.output);
568
+ }
569
+ function emitInputRequests(event, handlers) {
570
+ handlers.onInputRequest?.(
571
+ event.data.requests.map((request) => {
572
+ const ui = parseAgentToolUiSurface(
573
+ request.ui
574
+ );
575
+ return {
576
+ requestId: request.requestId,
577
+ kind: request.kind,
578
+ prompt: request.prompt,
579
+ ...request.display ? { display: request.display } : {},
580
+ ...request.allowFreeform !== void 0 ? { allowFreeform: request.allowFreeform } : {},
581
+ ...request.options ? { options: request.options } : {},
582
+ ...ui ? { ui } : {},
583
+ action: {
584
+ callId: request.action.callId,
585
+ kind: "tool-call",
586
+ toolName: request.action.toolName
587
+ }
588
+ };
589
+ })
590
+ );
591
+ }
306
592
  function isResumeTurnMessage(received, candidate) {
307
593
  if (received === candidate) return true;
308
594
  return Boolean(candidate) && received.endsWith(`
@@ -578,9 +864,11 @@ var AgentSession = class {
578
864
  let streamIndex = session?.state.streamIndex ?? 0;
579
865
  let rendered = "";
580
866
  const workItems = /* @__PURE__ */ new Map();
867
+ let requestedInput = false;
581
868
  try {
582
869
  for await (const event of response) {
583
870
  if (signal.aborted) break;
871
+ if (event.type === "input.requested") requestedInput = true;
584
872
  rendered = applyMessageEvent(event, rendered, handlers, workItems);
585
873
  streamIndex += 1;
586
874
  if (session) {
@@ -598,7 +886,7 @@ var AgentSession = class {
598
886
  this.persistSessionCursor(session);
599
887
  }
600
888
  }
601
- if (!rendered.trim() && !signal.aborted) {
889
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
602
890
  throw new Error("Empty response from runtime");
603
891
  }
604
892
  return rendered.trim();
@@ -618,6 +906,9 @@ var AgentSession = class {
618
906
  () => attached.snapshot({ signal })
619
907
  );
620
908
  const turnEvents = latestTurnEvents(snapshot.events);
909
+ const hasInputRequest = turnEvents.some(
910
+ (event) => event.type === "input.requested"
911
+ );
621
912
  const received = turnEvents[0];
622
913
  const lastSent = persisted.lastMessage;
623
914
  const inFlight = !turnEvents.some((event) => isTurnBoundary(event));
@@ -628,6 +919,8 @@ var AgentSession = class {
628
919
  const workItems = /* @__PURE__ */ new Map();
629
920
  for (const event of turnEvents) {
630
921
  applyWorkEvent(event, handlers, workItems);
922
+ if (event.type === "input.requested") emitInputRequests(event, handlers);
923
+ if (event.type === "action.result") emitActionResult(event, handlers);
631
924
  }
632
925
  if (rendered.startsWith(initialText)) {
633
926
  const missedText = rendered.slice(initialText.length);
@@ -657,7 +950,9 @@ var AgentSession = class {
657
950
  );
658
951
  }
659
952
  handlers.onComplete?.();
660
- if (!rendered.trim()) throw new Error("Empty response from runtime");
953
+ if (!rendered.trim() && !hasInputRequest) {
954
+ throw new Error("Empty response from runtime");
955
+ }
661
956
  return rendered.trim();
662
957
  }
663
958
  let streamIndex = snapshot.session.streamIndex;
@@ -676,7 +971,55 @@ var AgentSession = class {
676
971
  session = client.sessions.attach(session.state.sessionId, { streamIndex });
677
972
  this.session = session;
678
973
  this.persistSessionCursor(session);
679
- if (!rendered.trim() && !signal.aborted) {
974
+ if (!rendered.trim() && !signal.aborted && !hasInputRequest) {
975
+ throw new Error("Empty response from runtime");
976
+ }
977
+ return rendered.trim();
978
+ }
979
+ async respondTurn(responses, signal, handlers) {
980
+ const client = this.ensureClient();
981
+ const session = this.session ?? this.attachPersistedSession(client);
982
+ if (!session) {
983
+ throw new Error("No active session is waiting for input.");
984
+ }
985
+ this.session = session;
986
+ const inputResponses = responses.map(
987
+ ({ requestId, optionId, text }) => ({
988
+ requestId,
989
+ ...optionId ? { optionId } : {},
990
+ ...text ? { text } : {}
991
+ })
992
+ );
993
+ const response = await withCapabilityRefresh(
994
+ this.capability,
995
+ () => session.respond(inputResponses, { signal })
996
+ );
997
+ this.activeResponse = response;
998
+ let streamIndex = session.state.streamIndex;
999
+ let rendered = "";
1000
+ let requestedInput = false;
1001
+ const workItems = /* @__PURE__ */ new Map();
1002
+ try {
1003
+ for await (const event of response) {
1004
+ if (signal.aborted) break;
1005
+ if (event.type === "input.requested") requestedInput = true;
1006
+ rendered = applyMessageEvent(event, rendered, handlers, workItems);
1007
+ streamIndex += 1;
1008
+ savePersistedAgentSession(
1009
+ this.visitorSessionId,
1010
+ session.state.sessionId,
1011
+ streamIndex,
1012
+ this.storeOptions
1013
+ );
1014
+ }
1015
+ } finally {
1016
+ this.activeResponse = void 0;
1017
+ this.session = client.sessions.attach(session.state.sessionId, {
1018
+ streamIndex
1019
+ });
1020
+ this.persistSessionCursor(this.session);
1021
+ }
1022
+ if (!rendered.trim() && !signal.aborted && !requestedInput) {
680
1023
  throw new Error("Empty response from runtime");
681
1024
  }
682
1025
  return rendered.trim();
@@ -736,6 +1079,11 @@ function createAgentClient(options) {
736
1079
  resumeOptions.handlers,
737
1080
  resumeOptions.initialText
738
1081
  ),
1082
+ respondTurn: (respondOptions) => session.respondTurn(
1083
+ respondOptions.responses,
1084
+ respondOptions.signal ?? new AbortController().signal,
1085
+ respondOptions.handlers
1086
+ ),
739
1087
  reset: () => session.reset(),
740
1088
  cancelActive: () => session.cancelActive(),
741
1089
  getActiveSessionId: () => session.getActiveSessionId()
@@ -785,6 +1133,63 @@ function formatAgentError(error) {
785
1133
  return TRANSIENT_AGENT_ERROR_MESSAGE;
786
1134
  }
787
1135
 
1136
+ // src/runtime/tool-result-envelope.ts
1137
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1138
+ "schemaVersion",
1139
+ "output",
1140
+ "presentationKinds",
1141
+ "ui"
1142
+ ]);
1143
+ function isRecord3(value) {
1144
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1145
+ }
1146
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
1147
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
1148
+ return true;
1149
+ }
1150
+ if (typeof value === "number") return Number.isFinite(value);
1151
+ if (typeof value !== "object") return false;
1152
+ if (seen.has(value)) return false;
1153
+ seen.add(value);
1154
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
1155
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
1156
+ );
1157
+ seen.delete(value);
1158
+ return valid;
1159
+ }
1160
+ function decodeEnvelope(value) {
1161
+ if (typeof value !== "string") return value;
1162
+ try {
1163
+ return JSON.parse(value);
1164
+ } catch {
1165
+ return null;
1166
+ }
1167
+ }
1168
+ function parseAgentToolResultEnvelope(value) {
1169
+ const decoded = decodeEnvelope(value);
1170
+ if (!isRecord3(decoded)) return null;
1171
+ const keys = Object.keys(decoded);
1172
+ 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) {
1173
+ return null;
1174
+ }
1175
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
1176
+ if (typeof kind !== "string") return [];
1177
+ const normalized = kind.trim();
1178
+ return normalized && normalized.length <= 128 ? [normalized] : [];
1179
+ });
1180
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
1181
+ return null;
1182
+ }
1183
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
1184
+ if (decoded.ui !== void 0 && !ui) return null;
1185
+ return {
1186
+ schemaVersion: "webless.tool-result.v1",
1187
+ output: decoded.output,
1188
+ presentationKinds,
1189
+ ...ui ? { ui } : {}
1190
+ };
1191
+ }
1192
+
788
1193
  // src/runtime/health.ts
789
1194
  async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
790
1195
  const healthUrl = agentHealthUrl(runtimeOrigin);
@@ -807,6 +1212,7 @@ async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
807
1212
  }
808
1213
  // Annotate the CommonJS export names for ESM import in node:
809
1214
  0 && (module.exports = {
1215
+ AGENT_TOOL_UI_SCHEMA_VERSION,
810
1216
  DEFAULT_RUNTIME_ORIGIN,
811
1217
  agentHealthUrl,
812
1218
  buildAgentStorageKeyPrefix,
@@ -817,6 +1223,8 @@ async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
817
1223
  formatAgentError,
818
1224
  getOrCreateVisitorSessionId,
819
1225
  loadPersistedAgentSession,
1226
+ parseAgentToolResultEnvelope,
1227
+ parseAgentToolUiSurface,
820
1228
  pingAgentHealth,
821
1229
  resolveAgentRuntimeConfig,
822
1230
  savePersistedAgentSession