@webless/agent 0.8.0 → 0.9.1

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
@@ -42,700 +42,816 @@ __export(index_exports, {
42
42
  });
43
43
  module.exports = __toCommonJS(index_exports);
44
44
 
45
- // src/runtime/client.ts
46
- var import_client2 = require("eve/client");
47
-
48
- // src/runtime/capability.ts
49
- var import_client = require("eve/client");
50
- var MAX_REFRESH_SKEW_MS = 3e4;
51
- var LOCAL_LOOPBACK_ORIGINS = [
52
- "http://127.0.0.1:3010",
53
- "http://127.0.0.1:3001"
54
- ];
55
- function isLoopbackRuntimeOrigin(origin) {
56
- try {
57
- const host = new URL(origin).hostname;
58
- return host === "127.0.0.1" || host === "localhost";
59
- } catch {
60
- return false;
61
- }
62
- }
63
- function localBootstrapOrigins(origin) {
64
- const normalized = origin.replace(/\/$/, "");
65
- if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
66
- return [
67
- normalized,
68
- ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
69
- ];
45
+ // src/runtime/tool-ui.ts
46
+ var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
47
+ var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
48
+ var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
49
+ function formatAgentStructuredToolInput(surface, values) {
50
+ const payload = {
51
+ schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
52
+ toolSlug: surface.toolSlug,
53
+ ...surface.operationId ? { operationId: surface.operationId } : {},
54
+ values
55
+ };
56
+ return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
70
57
  }
71
58
  function isRecord(value) {
72
- return typeof value === "object" && value !== null && !Array.isArray(value);
59
+ return value !== null && typeof value === "object" && !Array.isArray(value);
73
60
  }
74
- function parseBootstrapResponse(value, indexId, now) {
75
- if (!isRecord(value) || value.apiVersion !== "webless.ai/agent-runtime-bootstrap/v1" || typeof value.accessToken !== "string" || !value.accessToken || typeof value.expiresAt !== "string" || !isRecord(value.identity) || value.identity.indexId !== indexId || typeof value.identity.revision !== "string" || !value.identity.revision || typeof value.identity.tenantId !== "string" || !value.identity.tenantId || typeof value.origin !== "string" || !value.origin || value.tokenType !== "Bearer" || typeof value.visitorSubject !== "string" || !value.visitorSubject) {
76
- throw new Error("Agent Runtime returned an invalid access response.");
77
- }
78
- const expiresAt = Date.parse(value.expiresAt);
79
- if (!Number.isFinite(expiresAt) || expiresAt <= now) {
80
- throw new Error("Agent Runtime returned an expired access response.");
61
+ function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
62
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
63
+ return true;
81
64
  }
82
- const refreshSkew = Math.min(
83
- MAX_REFRESH_SKEW_MS,
84
- Math.floor((expiresAt - now) / 10)
65
+ if (typeof value === "number") return Number.isFinite(value);
66
+ if (typeof value !== "object") return false;
67
+ if (seen.has(value)) return false;
68
+ seen.add(value);
69
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
70
+ ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
85
71
  );
86
- return {
87
- accessToken: value.accessToken,
88
- refreshAt: expiresAt - refreshSkew
89
- };
90
- }
91
- async function readBootstrapError(response) {
92
- const fallback = `Agent Runtime is unavailable (${response.status}).`;
93
- try {
94
- const value = await response.json();
95
- return isRecord(value) && typeof value.error === "string" && value.error ? value.error : fallback;
96
- } catch {
97
- return fallback;
98
- }
99
- }
100
- function createAgentRuntimeCapability(options) {
101
- const fetchImplementation = options.fetchImplementation ?? fetch;
102
- const now = options.now ?? Date.now;
103
- let capability;
104
- let generation = 0;
105
- let pendingBootstrap;
106
- const bootstrap = async () => {
107
- let previewGrant;
108
- const previewBuildId = options.previewBuildId?.trim();
109
- if (options.version === "unpublished") {
110
- previewGrant = previewBuildId ? void 0 : (await options.getUnpublishedPreviewGrant?.())?.trim();
111
- if (!previewGrant && !previewBuildId) {
112
- throw new Error(
113
- "Unpublished preview authorization is required to use the Agent Runtime preview."
114
- );
115
- }
116
- }
117
- const bootstrapBody = JSON.stringify({
118
- clientSessionId: options.visitorSessionId,
119
- indexId: options.indexId,
120
- ...previewBuildId ? { previewBuildId } : {},
121
- ...previewGrant ? { previewGrant } : {},
122
- version: options.version
123
- });
124
- const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
125
- body: bootstrapBody,
126
- headers: { "content-type": "application/json" },
127
- method: "POST"
128
- });
129
- let response;
130
- let lastError;
131
- for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
132
- try {
133
- response = await postBootstrap(origin);
134
- break;
135
- } catch (error) {
136
- lastError = error;
137
- }
138
- }
139
- if (!response) {
140
- throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
141
- }
142
- if (!response.ok) {
143
- throw new Error(await readBootstrapError(response));
144
- }
145
- let value;
146
- try {
147
- value = await response.json();
148
- } catch {
149
- throw new Error("Agent Runtime returned an invalid access response.");
150
- }
151
- return parseBootstrapResponse(value, options.indexId, now());
152
- };
153
- const getAccessToken = async () => {
154
- if (capability && capability.refreshAt > now()) {
155
- return capability.accessToken;
156
- }
157
- const requestedGeneration = generation;
158
- if (!pendingBootstrap) {
159
- const trackedBootstrap = bootstrap().finally(() => {
160
- if (pendingBootstrap === trackedBootstrap) {
161
- pendingBootstrap = void 0;
162
- }
163
- });
164
- pendingBootstrap = trackedBootstrap;
165
- }
166
- const nextCapability = await pendingBootstrap;
167
- if (requestedGeneration !== generation) {
168
- return getAccessToken();
169
- }
170
- capability = nextCapability;
171
- return capability.accessToken;
172
- };
173
- return {
174
- getAccessToken,
175
- invalidate: () => {
176
- capability = void 0;
177
- generation += 1;
178
- pendingBootstrap = void 0;
179
- }
180
- };
181
- }
182
- async function withCapabilityRefresh(capability, request) {
183
- try {
184
- return await request();
185
- } catch (error) {
186
- if (!(error instanceof import_client.ClientError) || error.status !== 401) {
187
- throw error;
188
- }
189
- capability.invalidate();
190
- return await request();
191
- }
72
+ seen.delete(value);
73
+ return valid;
192
74
  }
193
-
194
- // src/runtime/config.ts
195
- var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
196
- var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
197
- function buildEveHost(origin, indexId, version = "published") {
198
- const base = trimTrailingSlash(origin);
199
- const searchParams = new URLSearchParams({ indexId: indexId.trim() });
200
- if (version === "unpublished") {
201
- searchParams.set("version", version);
75
+ function boundedString(value, max) {
76
+ if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
77
+ return void 0;
202
78
  }
203
- return `${base}?${searchParams.toString()}`;
79
+ return value.trim();
204
80
  }
205
- function resolveAgentRuntimeConfig(input) {
206
- const origin = trimTrailingSlash(input.runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
207
- const resolvedIndexId = input.indexId.trim();
208
- const version = input.version ?? "published";
209
- return {
210
- origin,
211
- indexId: resolvedIndexId,
212
- version,
213
- host: resolvedIndexId ? buildEveHost(origin, resolvedIndexId, version) : origin
214
- };
81
+ function numberValue(value) {
82
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
215
83
  }
216
- function agentHealthUrl(runtimeOrigin) {
217
- const origin = trimTrailingSlash(runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
218
- return `${origin}/eve/v1/health`;
84
+ function integerValue(value) {
85
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
219
86
  }
220
-
221
- // src/runtime/session-store.ts
222
- var DEFAULT_STORAGE_KEY_PREFIX = "webless:agent";
223
- function normalizeRuntimeOrigin(runtimeOrigin) {
224
- const trimmed = runtimeOrigin?.trim();
225
- if (!trimmed) return "_";
226
- return trimmed.replace(/\/+$/, "");
87
+ function isFieldKind(value) {
88
+ return typeof value === "string" && [
89
+ "text",
90
+ "textarea",
91
+ "email",
92
+ "number",
93
+ "select",
94
+ "multi-select",
95
+ "checkbox",
96
+ "confirmation",
97
+ "radio",
98
+ "date",
99
+ "time",
100
+ "date-time",
101
+ "calendar",
102
+ "range",
103
+ "json"
104
+ ].includes(value);
227
105
  }
228
- function buildAgentStorageKeyPrefix(input) {
229
- const customerId = input.customerId?.trim() || "_";
230
- const indexId = input.indexId.trim();
231
- if (!indexId) {
232
- throw new Error("indexId is required to build agent storage keys.");
106
+ function parseField(value) {
107
+ if (!isRecord(value)) return null;
108
+ if (!hasOnlyKeys(value, [
109
+ "description",
110
+ "kind",
111
+ "label",
112
+ "max",
113
+ "maxItems",
114
+ "maxLength",
115
+ "min",
116
+ "minLength",
117
+ "options",
118
+ "path",
119
+ "placeholder",
120
+ "required",
121
+ "step",
122
+ "defaultValue"
123
+ ])) {
124
+ return null;
233
125
  }
234
- const versionSuffix = input.version === "unpublished" ? ":unpublished" : "";
235
- return `${DEFAULT_STORAGE_KEY_PREFIX}:${customerId}:${indexId}${versionSuffix}:${normalizeRuntimeOrigin(input.runtimeOrigin)}`;
236
- }
237
- function createSessionId() {
238
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
239
- return crypto.randomUUID();
126
+ if (!isFieldKind(value.kind)) return null;
127
+ const path = boundedString(value.path, 160);
128
+ const label = boundedString(value.label, 160);
129
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
130
+ const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
131
+ const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
132
+ const min = value.min === void 0 ? void 0 : numberValue(value.min);
133
+ const max = value.max === void 0 ? void 0 : numberValue(value.max);
134
+ const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
135
+ const step = value.step === void 0 ? void 0 : numberValue(value.step);
136
+ const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
137
+ const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
138
+ const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
139
+ if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
140
+ return null;
240
141
  }
241
- return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
242
- }
243
- function resolvePrefix(options) {
244
- return options?.storageKeyPrefix?.trim() || DEFAULT_STORAGE_KEY_PREFIX;
245
- }
246
- function getOrCreateVisitorSessionId(options) {
247
- const prefix = resolvePrefix(options);
248
- const visitorKey = `${prefix}:visitorSessionId`;
249
- if (typeof sessionStorage === "undefined") {
250
- return createSessionId();
142
+ if (value.description !== void 0 && !description) return null;
143
+ if (value.placeholder !== void 0 && placeholder === void 0) return null;
144
+ if (value.required !== void 0 && required === void 0) return null;
145
+ if (value.min !== void 0 && min === void 0) return null;
146
+ if (value.max !== void 0 && max === void 0) return null;
147
+ if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
148
+ return null;
149
+ if (value.step !== void 0 && step === void 0) return null;
150
+ if (value.minLength !== void 0 && minLength === void 0) return null;
151
+ if (value.maxLength !== void 0 && maxLength === void 0) return null;
152
+ if (value.defaultValue !== void 0 && defaultValue === void 0)
153
+ return null;
154
+ if (value.options !== void 0) {
155
+ if (!Array.isArray(value.options) || value.options.length > 100)
156
+ return null;
157
+ for (const option of value.options) {
158
+ if (!isRecord(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
159
+ return null;
160
+ }
161
+ }
251
162
  }
252
- const existing = sessionStorage.getItem(visitorKey)?.trim();
253
- if (existing) return existing;
254
- const next = createSessionId();
255
- sessionStorage.setItem(visitorKey, next);
256
- return next;
257
- }
258
- function clearVisitorSessionId(options) {
259
- if (typeof sessionStorage === "undefined") return;
260
- const prefix = resolvePrefix(options);
261
- sessionStorage.removeItem(`${prefix}:visitorSessionId`);
262
- }
263
- function runtimeSessionIdKey(visitorSessionId, prefix) {
264
- return `${prefix}:eve:${visitorSessionId}:sessionId`;
265
- }
266
- function runtimeStreamIndexKey(visitorSessionId, prefix) {
267
- return `${prefix}:eve:${visitorSessionId}:streamIndex`;
268
- }
269
- function runtimeLastMessageKey(visitorSessionId, prefix) {
270
- return `${prefix}:eve:${visitorSessionId}:lastMessage`;
163
+ return {
164
+ kind: value.kind,
165
+ path,
166
+ label,
167
+ ...description ? { description } : {},
168
+ ...placeholder !== void 0 ? { placeholder } : {},
169
+ ...required !== void 0 ? { required } : {},
170
+ ...defaultValue !== void 0 ? { defaultValue } : {},
171
+ ...value.options !== void 0 ? { options: value.options } : {},
172
+ ...min !== void 0 ? { min } : {},
173
+ ...max !== void 0 ? { max } : {},
174
+ ...maxItems !== void 0 ? { maxItems } : {},
175
+ ...step !== void 0 ? { step } : {},
176
+ ...minLength !== void 0 ? { minLength } : {},
177
+ ...maxLength !== void 0 ? { maxLength } : {}
178
+ };
271
179
  }
272
- function loadPersistedAgentSession(visitorSessionId, options) {
273
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
274
- const prefix = resolvePrefix(options);
275
- const sessionId = sessionStorage.getItem(runtimeSessionIdKey(visitorSessionId, prefix))?.trim();
276
- if (!sessionId) return null;
277
- const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
278
- const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
279
- const lastMessage = sessionStorage.getItem(runtimeLastMessageKey(visitorSessionId, prefix))?.trim();
180
+ function parseStep(value) {
181
+ if (!isRecord(value)) return null;
182
+ if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
183
+ return null;
184
+ }
185
+ const id = boundedString(value.id, 80);
186
+ const label = boundedString(value.label, 160);
187
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
188
+ 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(
189
+ (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
190
+ )) {
191
+ return null;
192
+ }
280
193
  return {
281
- sessionId,
282
- streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0,
283
- ...lastMessage ? { lastMessage } : {}
194
+ id,
195
+ label,
196
+ fieldPaths: value.fieldPaths,
197
+ ...description ? { description } : {}
284
198
  };
285
199
  }
286
- function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
287
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !sessionId.trim()) {
288
- return;
200
+ function parseAction(value) {
201
+ if (!isRecord(value)) return null;
202
+ if (!hasOnlyKeys(value, ["id", "label"])) return null;
203
+ const label = boundedString(value.label, 80);
204
+ if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
205
+ return null;
289
206
  }
290
- const prefix = resolvePrefix(options);
291
- sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
292
- sessionStorage.setItem(
293
- runtimeStreamIndexKey(visitorSessionId, prefix),
294
- String(Math.max(0, streamIndex))
295
- );
207
+ return {
208
+ id: value.id,
209
+ label
210
+ };
296
211
  }
297
- function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
298
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
299
- return;
212
+ function parseAgentToolUiSurface(value) {
213
+ if (!isRecord(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
214
+ return null;
215
+ if (!hasOnlyKeys(value, [
216
+ "actions",
217
+ "description",
218
+ "fields",
219
+ "id",
220
+ "operationId",
221
+ "requestId",
222
+ "schemaVersion",
223
+ "steps",
224
+ "submitLabel",
225
+ "title",
226
+ "toolSlug",
227
+ "values"
228
+ ])) {
229
+ return null;
300
230
  }
301
- const prefix = resolvePrefix(options);
302
- sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
231
+ const id = boundedString(value.id, 200);
232
+ const title = boundedString(value.title, 200);
233
+ const toolSlug = boundedString(value.toolSlug, 200);
234
+ const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
235
+ const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
236
+ const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
237
+ const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
238
+ if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
239
+ return null;
240
+ }
241
+ const fields = value.fields.map(parseField);
242
+ if (fields.some((field) => field === null)) return null;
243
+ const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
244
+ if (steps?.some((step) => step === null)) return null;
245
+ const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
246
+ if (actions?.some((action) => action === null)) return null;
247
+ if (value.description !== void 0 && !description) return null;
248
+ if (value.operationId !== void 0 && !operationId) return null;
249
+ if (value.requestId !== void 0 && !requestId) return null;
250
+ if (value.submitLabel !== void 0 && !submitLabel) return null;
251
+ const values = value.values !== void 0 && isRecord(value.values) ? value.values : void 0;
252
+ if (value.values !== void 0) {
253
+ if (!values || !Object.values(values).every((item) => isJsonValue(item)))
254
+ return null;
255
+ }
256
+ return {
257
+ schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
258
+ id,
259
+ title,
260
+ toolSlug,
261
+ fields,
262
+ ...actions ? { actions } : {},
263
+ ...description ? { description } : {},
264
+ ...operationId ? { operationId } : {},
265
+ ...requestId ? { requestId } : {},
266
+ ...submitLabel ? { submitLabel } : {},
267
+ ...steps ? { steps } : {},
268
+ ...values ? { values } : {}
269
+ };
303
270
  }
304
- function clearPersistedAgentSession(visitorSessionId, options) {
305
- if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
306
- const prefix = resolvePrefix(options);
307
- sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
308
- sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
309
- sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
271
+ function hasOnlyKeys(value, allowed) {
272
+ const allowedKeys = new Set(allowed);
273
+ return Object.keys(value).every((key) => allowedKeys.has(key));
310
274
  }
311
275
 
312
- // src/runtime/subagent-child-stream.ts
313
- var INITIAL_RETRY_DELAY_MS = 100;
314
- var MAX_RETRY_DELAY_MS = 2e3;
315
- var MAX_CONSECUTIVE_RETRIES = 6;
276
+ // src/runtime/tool-result-envelope.ts
277
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
278
+ "schemaVersion",
279
+ "output",
280
+ "presentationKinds",
281
+ "ui"
282
+ ]);
316
283
  function isRecord2(value) {
317
- return typeof value === "object" && value !== null && !Array.isArray(value);
284
+ return value !== null && typeof value === "object" && !Array.isArray(value);
318
285
  }
319
- function parseError(value) {
320
- if (!isRecord2(value)) return void 0;
321
- const { code, message } = value;
322
- if (typeof code !== "string" || typeof message !== "string") {
323
- return void 0;
286
+ function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
287
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
288
+ return true;
324
289
  }
325
- return { code, message };
290
+ if (typeof value === "number") return Number.isFinite(value);
291
+ if (typeof value !== "object") return false;
292
+ if (seen.has(value)) return false;
293
+ seen.add(value);
294
+ const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
295
+ ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
296
+ );
297
+ seen.delete(value);
298
+ return valid;
326
299
  }
327
- function parseChildStreamEvent(value) {
328
- if (!isRecord2(value) || typeof value.type !== "string") {
329
- return { type: "other" };
330
- }
331
- if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
332
- return { type: "session.boundary" };
333
- }
334
- if (value.type === "subagent.event" && isRecord2(value.data) && Object.hasOwn(value.data, "event")) {
335
- return parseChildStreamEvent(value.data.event);
336
- }
337
- if (value.type === "subagent.called" && isRecord2(value.data)) {
338
- const { childStreamPath } = value.data;
339
- if (typeof childStreamPath === "string") {
340
- return { childStreamPath, type: "subagent.called" };
341
- }
342
- }
343
- if (value.type !== "action.result" || !isRecord2(value.data)) {
344
- return { type: "other" };
345
- }
346
- const { data } = value;
347
- if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
348
- return { type: "other" };
300
+ function decodeEnvelope(value) {
301
+ if (typeof value !== "string") return value;
302
+ try {
303
+ return JSON.parse(value);
304
+ } catch {
305
+ return null;
349
306
  }
350
- if (!isRecord2(data.result) || data.result.kind !== "tool-result") {
351
- return { type: "other" };
307
+ }
308
+ function parseAgentToolResultEnvelope(value) {
309
+ const decoded = decodeEnvelope(value);
310
+ if (!isRecord2(decoded)) return null;
311
+ const keys = Object.keys(decoded);
312
+ 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) {
313
+ return null;
352
314
  }
353
- const result = data.result;
354
- if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
355
- return { type: "other" };
315
+ const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
316
+ if (typeof kind !== "string") return [];
317
+ const normalized = kind.trim();
318
+ return normalized && normalized.length <= 128 ? [normalized] : [];
319
+ });
320
+ if (presentationKinds.length !== decoded.presentationKinds.length) {
321
+ return null;
356
322
  }
357
- const error = parseError(data.error);
323
+ const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
324
+ if (decoded.ui !== void 0 && !ui) return null;
358
325
  return {
359
- type: "action.result",
360
- hasOutput: Object.hasOwn(result, "output"),
361
- result: {
362
- callId: result.callId,
363
- toolName: result.toolName,
364
- status: data.status,
365
- ...Object.hasOwn(result, "output") ? { output: result.output } : {},
366
- ...error ? { error } : {}
367
- }
326
+ schemaVersion: "webless.tool-result.v1",
327
+ output: decoded.output,
328
+ presentationKinds,
329
+ ...ui ? { ui } : {}
368
330
  };
369
331
  }
370
- async function* readNdjsonStream(body) {
371
- const reader = body.getReader();
372
- const decoder = new TextDecoder();
373
- let buffer = "";
332
+
333
+ // src/runtime/connected-tool-work.ts
334
+ var HUBSPOT_ACTION_LABELS = {
335
+ HUBSPOT_LIST_CONTACTS: "Checking your details",
336
+ HUBSPOT_CREATE_CONTACT: "Saving your details",
337
+ HUBSPOT_UPDATE_CONTACT: "Updating your details",
338
+ HUBSPOT_CREATE_COMPANY: "Saving your company details"
339
+ };
340
+ function connectedToolWork(action) {
341
+ const slug = action.toolName === "COMPOSIO_MULTI_EXECUTE_TOOL" ? action.input.toolSlug : action.toolName;
342
+ if (typeof slug !== "string") return null;
343
+ const detail = HUBSPOT_ACTION_LABELS[slug];
344
+ return detail ? {
345
+ id: action.callId,
346
+ kind: "tool",
347
+ label: "Contact details",
348
+ detail,
349
+ state: "active"
350
+ } : null;
351
+ }
352
+ function toolResultFailed(result) {
353
+ if (result.status !== "completed") return true;
354
+ const output = parseAgentToolResultEnvelope(result.output)?.output ?? result.output;
355
+ if (typeof output !== "object" || output === null || Array.isArray(output))
356
+ return false;
357
+ return "error" in output && Boolean(output.error) || "providerError" in output && Boolean(output.providerError);
358
+ }
359
+ function completeConnectedToolWork(item, result) {
360
+ const failed = toolResultFailed(result);
361
+ return {
362
+ ...item,
363
+ state: failed ? "error" : "completed",
364
+ detail: failed ? "Action could not be confirmed" : "Action completed"
365
+ };
366
+ }
367
+
368
+ // src/runtime/client.ts
369
+ var import_client2 = require("eve/client");
370
+
371
+ // src/runtime/capability.ts
372
+ var import_client = require("eve/client");
373
+ var MAX_REFRESH_SKEW_MS = 3e4;
374
+ var LOCAL_LOOPBACK_ORIGINS = [
375
+ "http://127.0.0.1:3010",
376
+ "http://127.0.0.1:3001"
377
+ ];
378
+ function isLoopbackRuntimeOrigin(origin) {
374
379
  try {
375
- while (true) {
376
- const { done, value } = await reader.read();
377
- buffer += decoder.decode(value, { stream: !done });
378
- const lines = buffer.split("\n");
379
- buffer = lines.pop() ?? "";
380
- for (const line of lines) {
381
- const trimmed2 = line.trim();
382
- if (!trimmed2) continue;
383
- try {
384
- const parsed = JSON.parse(trimmed2);
385
- yield parsed;
386
- } catch {
387
- }
388
- }
389
- if (done) break;
390
- }
391
- const trimmed = buffer.trim();
392
- if (trimmed) {
393
- try {
394
- const parsed = JSON.parse(trimmed);
395
- yield parsed;
396
- } catch {
397
- }
398
- }
399
- } finally {
400
- reader.releaseLock();
380
+ const host = new URL(origin).hostname;
381
+ return host === "127.0.0.1" || host === "localhost";
382
+ } catch {
383
+ return false;
401
384
  }
402
385
  }
403
- function streamPathAt(path, streamIndex) {
404
- if (streamIndex === 0) return path;
405
- return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
386
+ function localBootstrapOrigins(origin) {
387
+ const normalized = origin.replace(/\/$/, "");
388
+ if (!isLoopbackRuntimeOrigin(normalized)) return [normalized];
389
+ return [
390
+ normalized,
391
+ ...LOCAL_LOOPBACK_ORIGINS.filter((candidate) => candidate !== normalized)
392
+ ];
406
393
  }
407
- function abortableDelay(delayMs, signal) {
408
- if (signal.aborted) return Promise.resolve();
409
- return new Promise((resolve) => {
410
- const finish = () => {
411
- clearTimeout(timeout);
412
- signal.removeEventListener("abort", finish);
413
- resolve();
414
- };
415
- const timeout = setTimeout(finish, delayMs);
416
- signal.addEventListener("abort", finish, { once: true });
417
- });
394
+ function isRecord3(value) {
395
+ return typeof value === "object" && value !== null && !Array.isArray(value);
418
396
  }
419
- var SubagentChildStreamCoordinator = class {
420
- constructor(client, handlers, parentSignal) {
421
- this.client = client;
422
- this.handlers = handlers;
423
- this.parentSignal = parentSignal;
424
- }
425
- client;
426
- handlers;
427
- parentSignal;
428
- controllers = /* @__PURE__ */ new Map();
429
- tasks = /* @__PURE__ */ new Map();
430
- begin(event) {
431
- this.beginPath(event.data.childStreamPath);
397
+ function parseBootstrapResponse(value, indexId, now) {
398
+ if (!isRecord3(value) || value.apiVersion !== "webless.ai/agent-runtime-bootstrap/v1" || typeof value.accessToken !== "string" || !value.accessToken || typeof value.expiresAt !== "string" || !isRecord3(value.identity) || value.identity.indexId !== indexId || typeof value.identity.revision !== "string" || !value.identity.revision || typeof value.identity.tenantId !== "string" || !value.identity.tenantId || typeof value.origin !== "string" || !value.origin || value.tokenType !== "Bearer" || typeof value.visitorSubject !== "string" || !value.visitorSubject) {
399
+ throw new Error("Agent Runtime returned an invalid access response.");
432
400
  }
433
- async waitForAll() {
434
- let observedTaskCount = -1;
435
- while (observedTaskCount !== this.tasks.size) {
436
- observedTaskCount = this.tasks.size;
437
- await Promise.all(this.tasks.values());
438
- }
401
+ const expiresAt = Date.parse(value.expiresAt);
402
+ if (!Number.isFinite(expiresAt) || expiresAt <= now) {
403
+ throw new Error("Agent Runtime returned an expired access response.");
439
404
  }
440
- abortAll() {
441
- for (const controller of this.controllers.values()) controller.abort();
442
- this.controllers.clear();
405
+ const refreshSkew = Math.min(
406
+ MAX_REFRESH_SKEW_MS,
407
+ Math.floor((expiresAt - now) / 10)
408
+ );
409
+ return {
410
+ accessToken: value.accessToken,
411
+ refreshAt: expiresAt - refreshSkew
412
+ };
413
+ }
414
+ async function readBootstrapError(response) {
415
+ const fallback = `Agent Runtime is unavailable (${response.status}).`;
416
+ try {
417
+ const value = await response.json();
418
+ return isRecord3(value) && typeof value.error === "string" && value.error ? value.error : fallback;
419
+ } catch {
420
+ return fallback;
443
421
  }
444
- beginPath(childStreamPath) {
445
- if (this.tasks.has(childStreamPath)) return;
446
- const controller = new AbortController();
447
- const abort = () => controller.abort();
448
- if (this.parentSignal.aborted) {
449
- controller.abort();
450
- } else {
451
- this.parentSignal.addEventListener("abort", abort, { once: true });
452
- }
453
- this.controllers.set(childStreamPath, controller);
454
- const task = this.consume(childStreamPath, controller.signal).finally(
455
- () => {
456
- this.parentSignal.removeEventListener("abort", abort);
457
- if (this.controllers.get(childStreamPath) === controller) {
458
- this.controllers.delete(childStreamPath);
459
- }
422
+ }
423
+ function createAgentRuntimeCapability(options) {
424
+ const fetchImplementation = options.fetchImplementation ?? fetch;
425
+ const now = options.now ?? Date.now;
426
+ let capability;
427
+ let generation = 0;
428
+ let pendingBootstrap;
429
+ const bootstrap = async () => {
430
+ let previewGrant;
431
+ const previewBuildId = options.previewBuildId?.trim();
432
+ if (options.version === "unpublished") {
433
+ previewGrant = previewBuildId ? void 0 : (await options.getUnpublishedPreviewGrant?.())?.trim();
434
+ if (!previewGrant && !previewBuildId) {
435
+ throw new Error(
436
+ "Unpublished preview authorization is required to use the Agent Runtime preview."
437
+ );
460
438
  }
461
- );
462
- this.tasks.set(childStreamPath, task);
463
- }
464
- async consume(path, signal) {
465
- let streamIndex = 0;
466
- let consecutiveRetries = 0;
467
- let retryDelayMs = INITIAL_RETRY_DELAY_MS;
468
- while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
469
- let receivedEvent = false;
439
+ }
440
+ const bootstrapBody = JSON.stringify({
441
+ clientSessionId: options.visitorSessionId,
442
+ indexId: options.indexId,
443
+ ...previewBuildId ? { previewBuildId } : {},
444
+ ...previewGrant ? { previewGrant } : {},
445
+ version: options.version
446
+ });
447
+ const postBootstrap = (origin) => fetchImplementation(`${origin}/webless/v1/bootstrap`, {
448
+ body: bootstrapBody,
449
+ headers: { "content-type": "application/json" },
450
+ method: "POST"
451
+ });
452
+ let response;
453
+ let lastError;
454
+ for (const origin of localBootstrapOrigins(options.runtimeOrigin)) {
470
455
  try {
471
- const response = await this.client.fetch(
472
- streamPathAt(path, streamIndex),
473
- {
474
- cache: "no-store",
475
- signal
476
- }
477
- );
478
- if (!response.ok || response.body === null) {
479
- await response.body?.cancel().catch(() => {
480
- });
481
- throw new Error(`Child stream returned ${response.status}.`);
482
- }
483
- for await (const rawEvent of readNdjsonStream(response.body)) {
484
- if (signal.aborted) return;
485
- receivedEvent = true;
486
- streamIndex += 1;
487
- const event = parseChildStreamEvent(rawEvent);
488
- if (event.type === "session.boundary") return;
489
- if (event.type === "subagent.called") {
490
- this.beginPath(event.childStreamPath);
491
- continue;
492
- }
493
- if (event.type !== "action.result") continue;
494
- this.handlers.onToolResult?.(event.result);
495
- if (event.hasOutput) {
496
- this.handlers.onActionResult?.(event.result.output);
497
- }
498
- }
499
- } catch {
500
- if (signal.aborted) return;
456
+ response = await postBootstrap(origin);
457
+ break;
458
+ } catch (error) {
459
+ lastError = error;
501
460
  }
502
- consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
503
- retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
504
- await abortableDelay(retryDelayMs, signal);
505
461
  }
462
+ if (!response) {
463
+ throw lastError instanceof Error ? lastError : new Error("Agent Runtime is unavailable.");
464
+ }
465
+ if (!response.ok) {
466
+ throw new Error(await readBootstrapError(response));
467
+ }
468
+ let value;
469
+ try {
470
+ value = await response.json();
471
+ } catch {
472
+ throw new Error("Agent Runtime returned an invalid access response.");
473
+ }
474
+ return parseBootstrapResponse(value, options.indexId, now());
475
+ };
476
+ const getAccessToken = async () => {
477
+ if (capability && capability.refreshAt > now()) {
478
+ return capability.accessToken;
479
+ }
480
+ const requestedGeneration = generation;
481
+ if (!pendingBootstrap) {
482
+ const trackedBootstrap = bootstrap().finally(() => {
483
+ if (pendingBootstrap === trackedBootstrap) {
484
+ pendingBootstrap = void 0;
485
+ }
486
+ });
487
+ pendingBootstrap = trackedBootstrap;
488
+ }
489
+ const nextCapability = await pendingBootstrap;
490
+ if (requestedGeneration !== generation) {
491
+ return getAccessToken();
492
+ }
493
+ capability = nextCapability;
494
+ return capability.accessToken;
495
+ };
496
+ return {
497
+ getAccessToken,
498
+ invalidate: () => {
499
+ capability = void 0;
500
+ generation += 1;
501
+ pendingBootstrap = void 0;
502
+ }
503
+ };
504
+ }
505
+ async function withCapabilityRefresh(capability, request) {
506
+ try {
507
+ return await request();
508
+ } catch (error) {
509
+ if (!(error instanceof import_client.ClientError) || error.status !== 401) {
510
+ throw error;
511
+ }
512
+ capability.invalidate();
513
+ return await request();
506
514
  }
507
- };
515
+ }
508
516
 
509
- // src/runtime/tool-ui.ts
510
- var AGENT_TOOL_UI_SCHEMA_VERSION = "webless.tool-ui.v1";
511
- var AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION = "webless.structured-tool-input.v1";
512
- var AGENT_STRUCTURED_TOOL_INPUT_HEADER = "Webless-Structured-Tool-Input-JSON:";
513
- function formatAgentStructuredToolInput(surface, values) {
514
- const payload = {
515
- schemaVersion: AGENT_STRUCTURED_TOOL_INPUT_SCHEMA_VERSION,
516
- toolSlug: surface.toolSlug,
517
- ...surface.operationId ? { operationId: surface.operationId } : {},
518
- values
517
+ // src/runtime/config.ts
518
+ var DEFAULT_RUNTIME_ORIGIN = "https://runtime.staging.webless.ai";
519
+ var trimTrailingSlash = (value) => value.replace(/\/+$/, "");
520
+ function buildEveHost(origin, indexId, version = "published") {
521
+ const base = trimTrailingSlash(origin);
522
+ const searchParams = new URLSearchParams({ indexId: indexId.trim() });
523
+ if (version === "unpublished") {
524
+ searchParams.set("version", version);
525
+ }
526
+ return `${base}?${searchParams.toString()}`;
527
+ }
528
+ function resolveAgentRuntimeConfig(input) {
529
+ const origin = trimTrailingSlash(input.runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
530
+ const resolvedIndexId = input.indexId.trim();
531
+ const version = input.version ?? "published";
532
+ return {
533
+ origin,
534
+ indexId: resolvedIndexId,
535
+ version,
536
+ host: resolvedIndexId ? buildEveHost(origin, resolvedIndexId, version) : origin
519
537
  };
520
- return `${AGENT_STRUCTURED_TOOL_INPUT_HEADER} ${JSON.stringify(payload)}`;
521
538
  }
522
- function isRecord3(value) {
523
- return value !== null && typeof value === "object" && !Array.isArray(value);
539
+ function agentHealthUrl(runtimeOrigin) {
540
+ const origin = trimTrailingSlash(runtimeOrigin?.trim() || DEFAULT_RUNTIME_ORIGIN);
541
+ return `${origin}/eve/v1/health`;
542
+ }
543
+
544
+ // src/runtime/session-store.ts
545
+ var DEFAULT_STORAGE_KEY_PREFIX = "webless:agent";
546
+ function normalizeRuntimeOrigin(runtimeOrigin) {
547
+ const trimmed = runtimeOrigin?.trim();
548
+ if (!trimmed) return "_";
549
+ return trimmed.replace(/\/+$/, "");
550
+ }
551
+ function buildAgentStorageKeyPrefix(input) {
552
+ const customerId = input.customerId?.trim() || "_";
553
+ const indexId = input.indexId.trim();
554
+ if (!indexId) {
555
+ throw new Error("indexId is required to build agent storage keys.");
556
+ }
557
+ const versionSuffix = input.version === "unpublished" ? ":unpublished" : "";
558
+ return `${DEFAULT_STORAGE_KEY_PREFIX}:${customerId}:${indexId}${versionSuffix}:${normalizeRuntimeOrigin(input.runtimeOrigin)}`;
524
559
  }
525
- function isJsonValue(value, seen = /* @__PURE__ */ new Set()) {
526
- if (value === null || typeof value === "string" || typeof value === "boolean") {
527
- return true;
560
+ function createSessionId() {
561
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
562
+ return crypto.randomUUID();
528
563
  }
529
- if (typeof value === "number") return Number.isFinite(value);
530
- if (typeof value !== "object") return false;
531
- if (seen.has(value)) return false;
532
- seen.add(value);
533
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue(item, seen)) : Object.entries(value).every(
534
- ([key, item]) => typeof key === "string" && isJsonValue(item, seen)
535
- );
536
- seen.delete(value);
537
- return valid;
564
+ return `sess_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
538
565
  }
539
- function boundedString(value, max) {
540
- if (typeof value !== "string" || value.trim().length === 0 || value.length > max) {
541
- return void 0;
566
+ function resolvePrefix(options) {
567
+ return options?.storageKeyPrefix?.trim() || DEFAULT_STORAGE_KEY_PREFIX;
568
+ }
569
+ function getOrCreateVisitorSessionId(options) {
570
+ const prefix = resolvePrefix(options);
571
+ const visitorKey = `${prefix}:visitorSessionId`;
572
+ if (typeof sessionStorage === "undefined") {
573
+ return createSessionId();
542
574
  }
543
- return value.trim();
575
+ const existing = sessionStorage.getItem(visitorKey)?.trim();
576
+ if (existing) return existing;
577
+ const next = createSessionId();
578
+ sessionStorage.setItem(visitorKey, next);
579
+ return next;
544
580
  }
545
- function numberValue(value) {
546
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
581
+ function clearVisitorSessionId(options) {
582
+ if (typeof sessionStorage === "undefined") return;
583
+ const prefix = resolvePrefix(options);
584
+ sessionStorage.removeItem(`${prefix}:visitorSessionId`);
547
585
  }
548
- function integerValue(value) {
549
- return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 8e3 ? value : void 0;
586
+ function runtimeSessionIdKey(visitorSessionId, prefix) {
587
+ return `${prefix}:eve:${visitorSessionId}:sessionId`;
550
588
  }
551
- function isFieldKind(value) {
552
- return typeof value === "string" && [
553
- "text",
554
- "textarea",
555
- "email",
556
- "number",
557
- "select",
558
- "multi-select",
559
- "checkbox",
560
- "confirmation",
561
- "radio",
562
- "date",
563
- "time",
564
- "date-time",
565
- "calendar",
566
- "range",
567
- "json"
568
- ].includes(value);
589
+ function runtimeStreamIndexKey(visitorSessionId, prefix) {
590
+ return `${prefix}:eve:${visitorSessionId}:streamIndex`;
569
591
  }
570
- function parseField(value) {
571
- if (!isRecord3(value)) return null;
572
- if (!hasOnlyKeys(value, [
573
- "description",
574
- "kind",
575
- "label",
576
- "max",
577
- "maxItems",
578
- "maxLength",
579
- "min",
580
- "minLength",
581
- "options",
582
- "path",
583
- "placeholder",
584
- "required",
585
- "step",
586
- "defaultValue"
587
- ])) {
588
- return null;
589
- }
590
- if (!isFieldKind(value.kind)) return null;
591
- const path = boundedString(value.path, 160);
592
- const label = boundedString(value.label, 160);
593
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
594
- const placeholder = value.placeholder === void 0 || typeof value.placeholder !== "string" ? void 0 : value.placeholder;
595
- const required = value.required === void 0 || typeof value.required !== "boolean" ? void 0 : value.required;
596
- const min = value.min === void 0 ? void 0 : numberValue(value.min);
597
- const max = value.max === void 0 ? void 0 : numberValue(value.max);
598
- const maxItems = value.maxItems === void 0 ? void 0 : integerValue(value.maxItems);
599
- const step = value.step === void 0 ? void 0 : numberValue(value.step);
600
- const minLength = value.minLength === void 0 ? void 0 : integerValue(value.minLength);
601
- const maxLength = value.maxLength === void 0 ? void 0 : integerValue(value.maxLength);
602
- const defaultValue = value.defaultValue === void 0 || !isJsonValue(value.defaultValue) ? void 0 : value.defaultValue;
603
- if (!path || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || !label) {
604
- return null;
605
- }
606
- if (value.description !== void 0 && !description) return null;
607
- if (value.placeholder !== void 0 && placeholder === void 0) return null;
608
- if (value.required !== void 0 && required === void 0) return null;
609
- if (value.min !== void 0 && min === void 0) return null;
610
- if (value.max !== void 0 && max === void 0) return null;
611
- if (value.maxItems !== void 0 && (maxItems === void 0 || maxItems < 1 || maxItems > 100))
612
- return null;
613
- if (value.step !== void 0 && step === void 0) return null;
614
- if (value.minLength !== void 0 && minLength === void 0) return null;
615
- if (value.maxLength !== void 0 && maxLength === void 0) return null;
616
- if (value.defaultValue !== void 0 && defaultValue === void 0)
617
- return null;
618
- if (value.options !== void 0) {
619
- if (!Array.isArray(value.options) || value.options.length > 100)
620
- return null;
621
- for (const option of value.options) {
622
- if (!isRecord3(option) || !boundedString(option.label, 160) || !isJsonValue(option.value)) {
623
- return null;
624
- }
625
- }
626
- }
592
+ function runtimeLastMessageKey(visitorSessionId, prefix) {
593
+ return `${prefix}:eve:${visitorSessionId}:lastMessage`;
594
+ }
595
+ function loadPersistedAgentSession(visitorSessionId, options) {
596
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return null;
597
+ const prefix = resolvePrefix(options);
598
+ const sessionId = sessionStorage.getItem(runtimeSessionIdKey(visitorSessionId, prefix))?.trim();
599
+ if (!sessionId) return null;
600
+ const rawIndex = sessionStorage.getItem(runtimeStreamIndexKey(visitorSessionId, prefix));
601
+ const streamIndex = rawIndex ? Number.parseInt(rawIndex, 10) : 0;
602
+ const lastMessage = sessionStorage.getItem(runtimeLastMessageKey(visitorSessionId, prefix))?.trim();
627
603
  return {
628
- kind: value.kind,
629
- path,
630
- label,
631
- ...description ? { description } : {},
632
- ...placeholder !== void 0 ? { placeholder } : {},
633
- ...required !== void 0 ? { required } : {},
634
- ...defaultValue !== void 0 ? { defaultValue } : {},
635
- ...value.options !== void 0 ? { options: value.options } : {},
636
- ...min !== void 0 ? { min } : {},
637
- ...max !== void 0 ? { max } : {},
638
- ...maxItems !== void 0 ? { maxItems } : {},
639
- ...step !== void 0 ? { step } : {},
640
- ...minLength !== void 0 ? { minLength } : {},
641
- ...maxLength !== void 0 ? { maxLength } : {}
604
+ sessionId,
605
+ streamIndex: Number.isFinite(streamIndex) && streamIndex >= 0 ? streamIndex : 0,
606
+ ...lastMessage ? { lastMessage } : {}
642
607
  };
643
608
  }
644
- function parseStep(value) {
645
- if (!isRecord3(value)) return null;
646
- if (!hasOnlyKeys(value, ["description", "fieldPaths", "id", "label"])) {
647
- return null;
609
+ function savePersistedAgentSession(visitorSessionId, sessionId, streamIndex, options) {
610
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !sessionId.trim()) {
611
+ return;
648
612
  }
649
- const id = boundedString(value.id, 80);
650
- const label = boundedString(value.label, 160);
651
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 500);
652
- 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(
653
- (path) => typeof path !== "string" || !/^[A-Za-z0-9_.[\]-]+$/u.test(path) || path.length > 160
654
- )) {
655
- return null;
613
+ const prefix = resolvePrefix(options);
614
+ sessionStorage.setItem(runtimeSessionIdKey(visitorSessionId, prefix), sessionId);
615
+ sessionStorage.setItem(
616
+ runtimeStreamIndexKey(visitorSessionId, prefix),
617
+ String(Math.max(0, streamIndex))
618
+ );
619
+ }
620
+ function savePersistedAgentTurnMessage(visitorSessionId, message, options) {
621
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim() || !message) {
622
+ return;
656
623
  }
657
- return {
658
- id,
659
- label,
660
- fieldPaths: value.fieldPaths,
661
- ...description ? { description } : {}
662
- };
624
+ const prefix = resolvePrefix(options);
625
+ sessionStorage.setItem(runtimeLastMessageKey(visitorSessionId, prefix), message);
663
626
  }
664
- function parseAction(value) {
665
- if (!isRecord3(value)) return null;
666
- if (!hasOnlyKeys(value, ["id", "label"])) return null;
667
- const label = boundedString(value.label, 80);
668
- if (value.id !== "submit" && value.id !== "back" && value.id !== "next" && value.id !== "reset" || !label) {
669
- return null;
627
+ function clearPersistedAgentSession(visitorSessionId, options) {
628
+ if (typeof sessionStorage === "undefined" || !visitorSessionId.trim()) return;
629
+ const prefix = resolvePrefix(options);
630
+ sessionStorage.removeItem(runtimeSessionIdKey(visitorSessionId, prefix));
631
+ sessionStorage.removeItem(runtimeStreamIndexKey(visitorSessionId, prefix));
632
+ sessionStorage.removeItem(runtimeLastMessageKey(visitorSessionId, prefix));
633
+ }
634
+
635
+ // src/runtime/subagent-child-stream.ts
636
+ var INITIAL_RETRY_DELAY_MS = 100;
637
+ var MAX_RETRY_DELAY_MS = 2e3;
638
+ var MAX_CONSECUTIVE_RETRIES = 6;
639
+ function isRecord4(value) {
640
+ return typeof value === "object" && value !== null && !Array.isArray(value);
641
+ }
642
+ function parseError(value) {
643
+ if (!isRecord4(value)) return void 0;
644
+ const { code, message } = value;
645
+ if (typeof code !== "string" || typeof message !== "string") {
646
+ return void 0;
670
647
  }
671
- return {
672
- id: value.id,
673
- label
674
- };
648
+ return { code, message };
675
649
  }
676
- function parseAgentToolUiSurface(value) {
677
- if (!isRecord3(value) || value.schemaVersion !== AGENT_TOOL_UI_SCHEMA_VERSION)
678
- return null;
679
- if (!hasOnlyKeys(value, [
680
- "actions",
681
- "description",
682
- "fields",
683
- "id",
684
- "operationId",
685
- "requestId",
686
- "schemaVersion",
687
- "steps",
688
- "submitLabel",
689
- "title",
690
- "toolSlug",
691
- "values"
692
- ])) {
693
- return null;
650
+ function parseChildStreamEvent(value) {
651
+ if (!isRecord4(value) || typeof value.type !== "string") {
652
+ return { type: "other" };
653
+ }
654
+ if (value.type === "session.waiting" || value.type === "session.completed" || value.type === "session.failed") {
655
+ return { type: "session.boundary" };
656
+ }
657
+ if (value.type === "subagent.event" && isRecord4(value.data) && Object.hasOwn(value.data, "event")) {
658
+ return parseChildStreamEvent(value.data.event);
659
+ }
660
+ if (value.type === "subagent.called" && isRecord4(value.data)) {
661
+ const { childStreamPath } = value.data;
662
+ if (typeof childStreamPath === "string") {
663
+ return { childStreamPath, type: "subagent.called" };
664
+ }
665
+ }
666
+ if (value.type === "actions.requested" && isRecord4(value.data) && Array.isArray(value.data.actions)) {
667
+ const items = value.data.actions.flatMap((action) => {
668
+ if (!isRecord4(action) || action.kind !== "tool-call" || typeof action.callId !== "string" || typeof action.toolName !== "string" || !isRecord4(action.input)) return [];
669
+ const item = connectedToolWork({
670
+ callId: action.callId,
671
+ toolName: action.toolName,
672
+ input: action.input
673
+ });
674
+ return item ? [item] : [];
675
+ });
676
+ return { type: "actions.requested", items };
677
+ }
678
+ if (value.type !== "action.result" || !isRecord4(value.data)) {
679
+ return { type: "other" };
680
+ }
681
+ const { data } = value;
682
+ if (data.status !== "completed" && data.status !== "failed" && data.status !== "rejected") {
683
+ return { type: "other" };
694
684
  }
695
- const id = boundedString(value.id, 200);
696
- const title = boundedString(value.title, 200);
697
- const toolSlug = boundedString(value.toolSlug, 200);
698
- const description = value.description === void 0 ? void 0 : boundedString(value.description, 800);
699
- const operationId = value.operationId === void 0 ? void 0 : boundedString(value.operationId, 200);
700
- const requestId = value.requestId === void 0 ? void 0 : boundedString(value.requestId, 200);
701
- const submitLabel = value.submitLabel === void 0 ? void 0 : boundedString(value.submitLabel, 80);
702
- if (!id || !title || !toolSlug || !Array.isArray(value.fields) || value.fields.length < 1 || value.fields.length > 32) {
703
- return null;
685
+ if (!isRecord4(data.result) || data.result.kind !== "tool-result") {
686
+ return { type: "other" };
704
687
  }
705
- const fields = value.fields.map(parseField);
706
- if (fields.some((field) => field === null)) return null;
707
- const steps = value.steps === void 0 ? void 0 : Array.isArray(value.steps) && value.steps.length <= 8 ? value.steps.map(parseStep) : null;
708
- if (steps?.some((step) => step === null)) return null;
709
- const actions = value.actions === void 0 ? void 0 : Array.isArray(value.actions) && value.actions.length <= 8 ? value.actions.map(parseAction) : null;
710
- if (actions?.some((action) => action === null)) return null;
711
- if (value.description !== void 0 && !description) return null;
712
- if (value.operationId !== void 0 && !operationId) return null;
713
- if (value.requestId !== void 0 && !requestId) return null;
714
- if (value.submitLabel !== void 0 && !submitLabel) return null;
715
- const values = value.values !== void 0 && isRecord3(value.values) ? value.values : void 0;
716
- if (value.values !== void 0) {
717
- if (!values || !Object.values(values).every((item) => isJsonValue(item)))
718
- return null;
688
+ const result = data.result;
689
+ if (typeof result.callId !== "string" || typeof result.toolName !== "string") {
690
+ return { type: "other" };
719
691
  }
692
+ const error = parseError(data.error);
720
693
  return {
721
- schemaVersion: AGENT_TOOL_UI_SCHEMA_VERSION,
722
- id,
723
- title,
724
- toolSlug,
725
- fields,
726
- ...actions ? { actions } : {},
727
- ...description ? { description } : {},
728
- ...operationId ? { operationId } : {},
729
- ...requestId ? { requestId } : {},
730
- ...submitLabel ? { submitLabel } : {},
731
- ...steps ? { steps } : {},
732
- ...values ? { values } : {}
694
+ type: "action.result",
695
+ hasOutput: Object.hasOwn(result, "output"),
696
+ result: {
697
+ callId: result.callId,
698
+ toolName: result.toolName,
699
+ status: result.isError === true ? "failed" : data.status,
700
+ ...Object.hasOwn(result, "output") ? { output: result.output } : {},
701
+ ...error ? { error } : {}
702
+ }
733
703
  };
734
704
  }
735
- function hasOnlyKeys(value, allowed) {
736
- const allowedKeys = new Set(allowed);
737
- return Object.keys(value).every((key) => allowedKeys.has(key));
705
+ async function* readNdjsonStream(body) {
706
+ const reader = body.getReader();
707
+ const decoder = new TextDecoder();
708
+ let buffer = "";
709
+ try {
710
+ while (true) {
711
+ const { done, value } = await reader.read();
712
+ buffer += decoder.decode(value, { stream: !done });
713
+ const lines = buffer.split("\n");
714
+ buffer = lines.pop() ?? "";
715
+ for (const line of lines) {
716
+ const trimmed2 = line.trim();
717
+ if (!trimmed2) continue;
718
+ try {
719
+ const parsed = JSON.parse(trimmed2);
720
+ yield parsed;
721
+ } catch {
722
+ }
723
+ }
724
+ if (done) break;
725
+ }
726
+ const trimmed = buffer.trim();
727
+ if (trimmed) {
728
+ try {
729
+ const parsed = JSON.parse(trimmed);
730
+ yield parsed;
731
+ } catch {
732
+ }
733
+ }
734
+ } finally {
735
+ reader.releaseLock();
736
+ }
737
+ }
738
+ function streamPathAt(path, streamIndex) {
739
+ if (streamIndex === 0) return path;
740
+ return `${path}${path.includes("?") ? "&" : "?"}startIndex=${streamIndex}`;
741
+ }
742
+ function abortableDelay(delayMs, signal) {
743
+ if (signal.aborted) return Promise.resolve();
744
+ return new Promise((resolve) => {
745
+ const finish = () => {
746
+ clearTimeout(timeout);
747
+ signal.removeEventListener("abort", finish);
748
+ resolve();
749
+ };
750
+ const timeout = setTimeout(finish, delayMs);
751
+ signal.addEventListener("abort", finish, { once: true });
752
+ });
738
753
  }
754
+ var SubagentChildStreamCoordinator = class {
755
+ constructor(client, handlers, parentSignal) {
756
+ this.client = client;
757
+ this.handlers = handlers;
758
+ this.parentSignal = parentSignal;
759
+ }
760
+ client;
761
+ handlers;
762
+ parentSignal;
763
+ controllers = /* @__PURE__ */ new Map();
764
+ tasks = /* @__PURE__ */ new Map();
765
+ begin(event) {
766
+ this.beginPath(event.data.childStreamPath);
767
+ }
768
+ async waitForAll() {
769
+ let observedTaskCount = -1;
770
+ while (observedTaskCount !== this.tasks.size) {
771
+ observedTaskCount = this.tasks.size;
772
+ await Promise.all(this.tasks.values());
773
+ }
774
+ }
775
+ abortAll() {
776
+ for (const controller of this.controllers.values()) controller.abort();
777
+ this.controllers.clear();
778
+ }
779
+ beginPath(childStreamPath) {
780
+ if (this.tasks.has(childStreamPath)) return;
781
+ const controller = new AbortController();
782
+ const abort = () => controller.abort();
783
+ if (this.parentSignal.aborted) {
784
+ controller.abort();
785
+ } else {
786
+ this.parentSignal.addEventListener("abort", abort, { once: true });
787
+ }
788
+ this.controllers.set(childStreamPath, controller);
789
+ const task = this.consume(childStreamPath, controller.signal).finally(
790
+ () => {
791
+ this.parentSignal.removeEventListener("abort", abort);
792
+ if (this.controllers.get(childStreamPath) === controller) {
793
+ this.controllers.delete(childStreamPath);
794
+ }
795
+ }
796
+ );
797
+ this.tasks.set(childStreamPath, task);
798
+ }
799
+ async consume(path, signal) {
800
+ const workItems = /* @__PURE__ */ new Map();
801
+ let streamIndex = 0;
802
+ let consecutiveRetries = 0;
803
+ let retryDelayMs = INITIAL_RETRY_DELAY_MS;
804
+ while (!signal.aborted && consecutiveRetries < MAX_CONSECUTIVE_RETRIES) {
805
+ let receivedEvent = false;
806
+ try {
807
+ const response = await this.client.fetch(
808
+ streamPathAt(path, streamIndex),
809
+ {
810
+ cache: "no-store",
811
+ signal
812
+ }
813
+ );
814
+ if (!response.ok || response.body === null) {
815
+ await response.body?.cancel().catch(() => {
816
+ });
817
+ throw new Error(`Child stream returned ${response.status}.`);
818
+ }
819
+ for await (const rawEvent of readNdjsonStream(response.body)) {
820
+ if (signal.aborted) return;
821
+ receivedEvent = true;
822
+ streamIndex += 1;
823
+ const event = parseChildStreamEvent(rawEvent);
824
+ if (event.type === "session.boundary") return;
825
+ if (event.type === "subagent.called") {
826
+ this.beginPath(event.childStreamPath);
827
+ continue;
828
+ }
829
+ if (event.type === "actions.requested") {
830
+ for (const item2 of event.items) {
831
+ workItems.set(item2.id, item2);
832
+ this.handlers.onWork?.(item2);
833
+ }
834
+ continue;
835
+ }
836
+ if (event.type !== "action.result") continue;
837
+ const item = workItems.get(event.result.callId);
838
+ if (item) {
839
+ this.handlers.onWork?.(completeConnectedToolWork(item, event.result));
840
+ }
841
+ this.handlers.onToolResult?.(event.result);
842
+ if (event.hasOutput) {
843
+ this.handlers.onActionResult?.(event.result.output);
844
+ }
845
+ }
846
+ } catch {
847
+ if (signal.aborted) return;
848
+ }
849
+ consecutiveRetries = receivedEvent ? 0 : consecutiveRetries + 1;
850
+ retryDelayMs = receivedEvent ? INITIAL_RETRY_DELAY_MS : Math.min(retryDelayMs * 2, MAX_RETRY_DELAY_MS);
851
+ await abortableDelay(retryDelayMs, signal);
852
+ }
853
+ }
854
+ };
739
855
 
740
856
  // src/runtime/client.ts
741
857
  function isTurnBoundary(event) {
@@ -769,7 +885,7 @@ function emitActionResult(event, handlers) {
769
885
  handlers.onToolResult?.({
770
886
  callId: result.callId,
771
887
  toolName: result.toolName,
772
- status: event.data.status,
888
+ status: result.isError ? "failed" : event.data.status,
773
889
  output: result.output,
774
890
  ...event.data.error ? { error: event.data.error } : {}
775
891
  });
@@ -895,9 +1011,13 @@ function requestedWorkItem(action) {
895
1011
  state: "active"
896
1012
  };
897
1013
  }
898
- return null;
1014
+ return action.kind === "tool-call" ? connectedToolWork(action) : null;
899
1015
  }
900
1016
  function applyWorkEvent(event, handlers, workItems) {
1017
+ if (event.type === "subagent.event") {
1018
+ applyWorkEvent(event.data.event, handlers, workItems);
1019
+ return;
1020
+ }
901
1021
  if (event.type === "step.started" && workItems.size === 0) {
902
1022
  emitWorkItem(
903
1023
  {
@@ -956,6 +1076,15 @@ function applyWorkEvent(event, handlers, workItems) {
956
1076
  const { result, status } = event.data;
957
1077
  const current = workItems.get(result.callId);
958
1078
  if (!current) return;
1079
+ if (current.kind === "tool" && result.kind === "tool-result") {
1080
+ emitWorkItem(completeConnectedToolWork(current, {
1081
+ callId: result.callId,
1082
+ toolName: result.toolName,
1083
+ status: result.isError ? "failed" : status,
1084
+ output: result.output
1085
+ }), handlers, workItems);
1086
+ return;
1087
+ }
959
1088
  const failed = status !== "completed" || result.isError === true;
960
1089
  emitWorkItem(
961
1090
  {
@@ -1443,63 +1572,6 @@ function formatAgentError(error) {
1443
1572
  return TRANSIENT_AGENT_ERROR_MESSAGE;
1444
1573
  }
1445
1574
 
1446
- // src/runtime/tool-result-envelope.ts
1447
- var ENVELOPE_KEYS = /* @__PURE__ */ new Set([
1448
- "schemaVersion",
1449
- "output",
1450
- "presentationKinds",
1451
- "ui"
1452
- ]);
1453
- function isRecord4(value) {
1454
- return value !== null && typeof value === "object" && !Array.isArray(value);
1455
- }
1456
- function isJsonValue2(value, seen = /* @__PURE__ */ new Set()) {
1457
- if (value === null || typeof value === "string" || typeof value === "boolean") {
1458
- return true;
1459
- }
1460
- if (typeof value === "number") return Number.isFinite(value);
1461
- if (typeof value !== "object") return false;
1462
- if (seen.has(value)) return false;
1463
- seen.add(value);
1464
- const valid = Array.isArray(value) ? value.every((item) => isJsonValue2(item, seen)) : Object.entries(value).every(
1465
- ([key, item]) => typeof key === "string" && isJsonValue2(item, seen)
1466
- );
1467
- seen.delete(value);
1468
- return valid;
1469
- }
1470
- function decodeEnvelope(value) {
1471
- if (typeof value !== "string") return value;
1472
- try {
1473
- return JSON.parse(value);
1474
- } catch {
1475
- return null;
1476
- }
1477
- }
1478
- function parseAgentToolResultEnvelope(value) {
1479
- const decoded = decodeEnvelope(value);
1480
- if (!isRecord4(decoded)) return null;
1481
- const keys = Object.keys(decoded);
1482
- 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) {
1483
- return null;
1484
- }
1485
- const presentationKinds = decoded.presentationKinds.flatMap((kind) => {
1486
- if (typeof kind !== "string") return [];
1487
- const normalized = kind.trim();
1488
- return normalized && normalized.length <= 128 ? [normalized] : [];
1489
- });
1490
- if (presentationKinds.length !== decoded.presentationKinds.length) {
1491
- return null;
1492
- }
1493
- const ui = decoded.ui === void 0 ? void 0 : parseAgentToolUiSurface(decoded.ui);
1494
- if (decoded.ui !== void 0 && !ui) return null;
1495
- return {
1496
- schemaVersion: "webless.tool-result.v1",
1497
- output: decoded.output,
1498
- presentationKinds,
1499
- ...ui ? { ui } : {}
1500
- };
1501
- }
1502
-
1503
1575
  // src/runtime/health.ts
1504
1576
  async function pingAgentHealth(runtimeOrigin, fetchImpl = fetch) {
1505
1577
  const healthUrl = agentHealthUrl(runtimeOrigin);