@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.
@@ -0,0 +1,553 @@
1
+ import {
2
+ parseAgentToolResultEnvelope,
3
+ toolResultFailed
4
+ } from "./chunk-TCO62TRN.js";
5
+
6
+ // src/runtime/search-discovery.ts
7
+ function record(value) {
8
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
9
+ }
10
+ function text(value, max = 500) {
11
+ return typeof value === "string" && value.trim().length > 0 && value.trim().length <= max;
12
+ }
13
+ function safeSearchUrl(value) {
14
+ if (typeof value !== "string") return void 0;
15
+ try {
16
+ const url = new URL(value);
17
+ return (url.protocol === "https:" || url.protocol === "http:") && !url.username && !url.password ? url.href : void 0;
18
+ } catch {
19
+ return void 0;
20
+ }
21
+ }
22
+ function parseAgentSearchReferences(value) {
23
+ const data = record(value);
24
+ if (!data || !Array.isArray(data.sources) || data.sources.length > 20)
25
+ return null;
26
+ const sources = [];
27
+ for (const value2 of data.sources) {
28
+ const source = record(value2);
29
+ if (!source || Object.keys(source).some(
30
+ (key) => !["id", "title", "url"].includes(key)
31
+ ) || !text(source.id, Infinity) || !text(source.title) || source.url !== void 0 && typeof source.url !== "string")
32
+ return null;
33
+ const url = safeSearchUrl(source.url);
34
+ sources.push({
35
+ id: source.id.trim(),
36
+ title: source.title.trim(),
37
+ ...url ? { url } : {}
38
+ });
39
+ }
40
+ let cta;
41
+ if (data.cta !== void 0) {
42
+ const action = record(data.cta);
43
+ if (!action || Object.keys(action).some((key) => !["label", "url"].includes(key)) || !text(action.label) || action.url !== void 0 && typeof action.url !== "string")
44
+ return null;
45
+ const url = safeSearchUrl(action.url);
46
+ cta = { label: action.label.trim(), ...url ? { url } : {} };
47
+ }
48
+ return { sources, ...cta ? { cta } : {} };
49
+ }
50
+ function parseAgentSearchDiscoveryOutput(value) {
51
+ let decoded = value;
52
+ if (typeof value === "string") {
53
+ try {
54
+ decoded = JSON.parse(value);
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ const data = record(decoded);
60
+ if (!data || Object.keys(data).some(
61
+ (key) => !["answer", "sources", "cta", "suggestions"].includes(key)
62
+ ) || !text(data.answer, 5e4) || !Array.isArray(data.suggestions) || data.suggestions.length > 8 || !data.suggestions.every((item) => text(item)))
63
+ return null;
64
+ const references = parseAgentSearchReferences(data);
65
+ return references ? {
66
+ ...references,
67
+ answer: data.answer.trim(),
68
+ suggestions: data.suggestions.map((item) => item.trim())
69
+ } : null;
70
+ }
71
+
72
+ // src/react/lib/composer-form.ts
73
+ var MAX_FORM_FIELDS = 5;
74
+ function asRecord(value) {
75
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
76
+ }
77
+ function asString(value) {
78
+ return typeof value === "string" ? value.trim() : "";
79
+ }
80
+ function isFieldKind(value) {
81
+ return value === "text" || value === "email" || value === "tel" || value === "textarea";
82
+ }
83
+ function parseVisitorFormFields(value) {
84
+ if (!Array.isArray(value)) return [];
85
+ const fields = [];
86
+ const seen = /* @__PURE__ */ new Set();
87
+ for (const item of value) {
88
+ const record2 = asRecord(item);
89
+ const id = asString(record2?.id).toLowerCase().replace(/[^a-z0-9_-]/g, "");
90
+ const kind = asString(record2?.kind);
91
+ if (!record2 || !id || seen.has(id) || !isFieldKind(kind)) continue;
92
+ seen.add(id);
93
+ const label = asString(record2.label) || id;
94
+ fields.push({
95
+ id,
96
+ kind,
97
+ label,
98
+ placeholder: asString(record2.placeholder) || label,
99
+ required: record2.required !== false,
100
+ ...asString(record2.autocomplete) ? { autocomplete: asString(record2.autocomplete) } : {}
101
+ });
102
+ if (fields.length >= MAX_FORM_FIELDS) break;
103
+ }
104
+ return fields;
105
+ }
106
+
107
+ // src/react/lib/tool-card.ts
108
+ function asRecord2(value) {
109
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
110
+ }
111
+ function asString2(value) {
112
+ return typeof value === "string" ? value.trim() : "";
113
+ }
114
+ function isEventUri(value) {
115
+ return /^https:\/\/api\.calendly\.com\/scheduled_events\/[^/]+$/i.test(value);
116
+ }
117
+ function isEventTypeUri(value) {
118
+ return /^https:\/\/api\.calendly\.com\/event_types\/[^/]+$/i.test(value);
119
+ }
120
+ function parseToolCard(value) {
121
+ const record2 = asRecord2(value);
122
+ if (!record2) return null;
123
+ if (record2.booking_offer && asString2(record2.type) !== "booking_offer") {
124
+ const nested = parseToolCard(record2.booking_offer);
125
+ if (nested) return nested;
126
+ }
127
+ if (record2.visitor_booking && asString2(record2.type) !== "booking_confirmed") {
128
+ const nested = parseToolCard(record2.visitor_booking);
129
+ if (nested) return nested;
130
+ }
131
+ const type = asString2(record2.type);
132
+ if (type === "booking_offer") {
133
+ const eventTypes = Array.isArray(record2.eventTypes) ? record2.eventTypes.flatMap((item) => {
134
+ const entry = asRecord2(item);
135
+ const uri = asString2(entry?.uri);
136
+ if (!entry || !uri) return [];
137
+ const duration = entry.duration;
138
+ const locationKind = asString2(entry.locationKind);
139
+ const location = asString2(entry.location);
140
+ return [
141
+ {
142
+ name: asString2(entry.name) || "Meeting",
143
+ uri,
144
+ ...typeof duration === "number" ? { duration } : {},
145
+ ...locationKind ? { locationKind } : {},
146
+ ...location ? { location } : {}
147
+ }
148
+ ];
149
+ }) : [];
150
+ const slots = Array.isArray(record2.slots) ? record2.slots.flatMap((item) => {
151
+ const entry = asRecord2(item);
152
+ const startTime = asString2(entry?.startTime);
153
+ if (!entry || !startTime) return [];
154
+ const eventTypeUri = asString2(entry.eventTypeUri);
155
+ return [
156
+ {
157
+ startTime,
158
+ ...isEventTypeUri(eventTypeUri) ? { eventTypeUri } : {}
159
+ }
160
+ ];
161
+ }) : [];
162
+ if (eventTypes.length === 0 && slots.length === 0) return null;
163
+ return { type: "booking_offer", eventTypes, slots };
164
+ }
165
+ if (type === "booking_confirmed") {
166
+ const eventUri = asString2(record2.eventUri);
167
+ if (!isEventUri(eventUri)) return null;
168
+ const inviteeUri = asString2(record2.inviteeUri);
169
+ const inviteeEmail = asString2(record2.inviteeEmail);
170
+ const startTime = asString2(record2.startTime);
171
+ return {
172
+ type: "booking_confirmed",
173
+ eventUri,
174
+ ...inviteeUri ? { inviteeUri } : {},
175
+ ...inviteeEmail ? { inviteeEmail } : {},
176
+ ...startTime ? { startTime } : {}
177
+ };
178
+ }
179
+ if (type === "booking_canceled") {
180
+ const eventUri = asString2(record2.eventUri);
181
+ if (!isEventUri(eventUri)) return null;
182
+ return { type: "booking_canceled", eventUri };
183
+ }
184
+ if (type === "visitor_form") {
185
+ const fields = parseVisitorFormFields(record2.fields);
186
+ if (fields.length < 2) return null;
187
+ return { type: "visitor_form", fields };
188
+ }
189
+ return null;
190
+ }
191
+ function bookingCardFromActionOutput(output) {
192
+ const record2 = asRecord2(output);
193
+ const data = asRecord2(record2?.output) ?? asRecord2(record2?.data) ?? record2;
194
+ return parseToolCard(data);
195
+ }
196
+
197
+ // src/react/lib/tool-result.ts
198
+ var MAX_TEXT_LENGTH = 240;
199
+ var MAX_DETAILS = 6;
200
+ var MAX_LINKS = 4;
201
+ var MAX_COLLECTION_ITEMS = 8;
202
+ var INTERNAL_FACT_LABEL = /^(hs\b|createdate|created at|updatedate|updated at|firstname|first name|lastname|last name|vid|objectid|object id|all contact)/iu;
203
+ function safeText(value) {
204
+ if (typeof value !== "string") return "";
205
+ return value.trim().slice(0, MAX_TEXT_LENGTH);
206
+ }
207
+ function safeHref(value) {
208
+ const text2 = safeText(value);
209
+ if (!text2) return "";
210
+ try {
211
+ const url = new URL(text2);
212
+ return url.protocol === "https:" ? url.toString() : "";
213
+ } catch {
214
+ return "";
215
+ }
216
+ }
217
+ function fallbackTitle(result) {
218
+ if (result.status === "failed") return "Couldn\u2019t complete this action";
219
+ if (result.status === "rejected") return "Action not approved";
220
+ return "Action completed";
221
+ }
222
+ function bookingPresenter(kind) {
223
+ return {
224
+ kind,
225
+ present: ({ envelope }) => {
226
+ const card = bookingCardFromActionOutput(envelope.output);
227
+ return card && card.type === kind ? { kind: "booking", card } : null;
228
+ }
229
+ };
230
+ }
231
+ function providerErrorPresenter() {
232
+ return {
233
+ kind: "provider_error",
234
+ present: ({ envelope }) => {
235
+ const output = asRecord3(envelope.output);
236
+ const providerError = asRecord3(output?.providerError);
237
+ const message = safeText(providerError?.message);
238
+ const action = safeText(providerError?.action);
239
+ if (!message || !action) return null;
240
+ const resourceLabels = Array.isArray(providerError?.resourceLabels) ? providerError.resourceLabels.map(safeText).filter(Boolean).slice(0, MAX_DETAILS) : [];
241
+ return {
242
+ kind: "summary",
243
+ status: "failed",
244
+ title: message,
245
+ description: action,
246
+ ...resourceLabels.length ? {
247
+ details: [
248
+ { label: "Affected", value: resourceLabels.join(", ") }
249
+ ]
250
+ } : {}
251
+ };
252
+ }
253
+ };
254
+ }
255
+ function asRecord3(value) {
256
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
257
+ }
258
+ function parseFacts(value) {
259
+ if (!Array.isArray(value)) return [];
260
+ return value.flatMap((item) => {
261
+ const fact = asRecord3(item);
262
+ const label = safeText(fact?.label);
263
+ const factValue = safeText(fact?.value);
264
+ if (!label || !factValue) return [];
265
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
266
+ return [{ label, value: factValue }];
267
+ });
268
+ }
269
+ function parseLinks(output, defaultLabel) {
270
+ const href = safeHref(output.href);
271
+ const linkLabel = safeText(output.linkLabel) || defaultLabel;
272
+ return href ? [{ label: linkLabel, href }] : [];
273
+ }
274
+ function entityResultPresenter() {
275
+ return {
276
+ kind: "entity_result",
277
+ present: ({ envelope, result }) => {
278
+ const output = asRecord3(envelope.output);
279
+ if (!output) return null;
280
+ const title = safeText(output.title) || fallbackTitle(result);
281
+ const description = safeText(output.description);
282
+ const facts = parseFacts(output.facts).filter((fact) => fact.value !== description && fact.value !== title).slice(0, MAX_DETAILS);
283
+ return {
284
+ kind: "entity",
285
+ title,
286
+ ...description && description !== title ? { description } : {},
287
+ ...facts.length ? { details: facts } : {},
288
+ ...parseLinks(output, "Open record").length ? { links: parseLinks(output, "Open record") } : {}
289
+ };
290
+ }
291
+ };
292
+ }
293
+ function collectionResultPresenter() {
294
+ return {
295
+ kind: "collection",
296
+ present: ({ envelope, result }) => {
297
+ const output = asRecord3(envelope.output);
298
+ if (!output) return null;
299
+ const title = safeText(output.title) || fallbackTitleFromKind("collection");
300
+ const items = Array.isArray(output.items) ? output.items.slice(0, MAX_COLLECTION_ITEMS).flatMap((item) => {
301
+ const entry = asRecord3(item);
302
+ if (!entry) return [];
303
+ const itemTitle = safeText(entry.title);
304
+ if (!itemTitle) return [];
305
+ const description = safeText(entry.description);
306
+ const details = parseFacts(entry.facts).slice(0, MAX_DETAILS);
307
+ const href = safeHref(entry.href);
308
+ return [
309
+ {
310
+ title: itemTitle,
311
+ ...description ? { description } : {},
312
+ ...details.length ? { details } : {},
313
+ ...href ? { href } : {}
314
+ }
315
+ ];
316
+ }) : [];
317
+ return {
318
+ kind: "collection",
319
+ title,
320
+ items
321
+ };
322
+ }
323
+ };
324
+ }
325
+ function signatureResultPresenter() {
326
+ return {
327
+ kind: "signature",
328
+ present: ({ envelope, result }) => {
329
+ const output = asRecord3(envelope.output);
330
+ if (!output) return null;
331
+ const title = safeText(output.title) || fallbackTitleFromKind("signature");
332
+ const description = safeText(output.description);
333
+ const statusLabel = safeText(output.status);
334
+ return {
335
+ kind: "signature",
336
+ title,
337
+ ...description ? { description } : {},
338
+ ...statusLabel ? { statusLabel } : {},
339
+ ...parseLinks(output, "Sign now").length ? { links: parseLinks(output, "Sign now") } : {}
340
+ };
341
+ }
342
+ };
343
+ }
344
+ function resultCardPresenter(kind) {
345
+ return {
346
+ kind,
347
+ present: ({ envelope, result }) => {
348
+ const output = asRecord3(envelope.output);
349
+ if (!output) return null;
350
+ const title = safeText(output.title) || fallbackTitleFromKind(kind);
351
+ const description = safeText(output.description);
352
+ const facts = parseFacts(output.facts).slice(0, MAX_DETAILS);
353
+ return {
354
+ kind: "summary",
355
+ title,
356
+ ...description && description !== title ? { description } : {},
357
+ ...facts.length ? { details: facts } : {},
358
+ ...parseLinks(output, "Open").length ? { links: parseLinks(output, "Open") } : {}
359
+ };
360
+ }
361
+ };
362
+ }
363
+ function fallbackTitleFromKind(kind) {
364
+ if (kind === "document") return "Document ready";
365
+ if (kind === "signature") return "Ready to sign";
366
+ if (kind === "payment") return "Payment link";
367
+ if (kind === "collection") return "Results";
368
+ if (kind === "confirmation_result") return "Confirmed";
369
+ return "Saved";
370
+ }
371
+ var builtInVisitorToolResultRegistry = [
372
+ providerErrorPresenter(),
373
+ bookingPresenter("booking_offer"),
374
+ bookingPresenter("booking_confirmed"),
375
+ bookingPresenter("booking_canceled"),
376
+ entityResultPresenter(),
377
+ collectionResultPresenter(),
378
+ signatureResultPresenter(),
379
+ resultCardPresenter("document"),
380
+ resultCardPresenter("payment"),
381
+ resultCardPresenter("confirmation_result")
382
+ ];
383
+ function resolvePresenterOutput(result, envelope, registry) {
384
+ const presenters = [...registry, ...builtInVisitorToolResultRegistry];
385
+ for (const presentationKind of envelope.presentationKinds) {
386
+ const presenter = presenters.find(
387
+ (candidate) => candidate.kind === presentationKind
388
+ );
389
+ if (!presenter) continue;
390
+ try {
391
+ const presented = presenter.present({
392
+ envelope,
393
+ presentationKind,
394
+ result
395
+ });
396
+ if (presented) return presented;
397
+ } catch {
398
+ return null;
399
+ }
400
+ }
401
+ return null;
402
+ }
403
+ function finalizeSummaryPresentation(result, proposed) {
404
+ const title = safeText(proposed.title) || fallbackTitle(result);
405
+ const description = safeText(proposed.description);
406
+ const details = proposed.details?.slice(0, MAX_DETAILS).flatMap((detail) => {
407
+ const label = safeText(detail.label);
408
+ const value = safeText(detail.value);
409
+ if (!label || !value) return [];
410
+ if (INTERNAL_FACT_LABEL.test(label)) return [];
411
+ if (value === title || value === description) return [];
412
+ return [{ label, value }];
413
+ });
414
+ const links = proposed.links?.slice(0, MAX_LINKS).flatMap((link) => {
415
+ const label = safeText(link.label);
416
+ const href = safeHref(link.href);
417
+ return label && href ? [{ label, href }] : [];
418
+ });
419
+ return {
420
+ id: result.callId,
421
+ toolName: result.toolName,
422
+ status: proposed.status ?? result.status,
423
+ kind: "summary",
424
+ title,
425
+ ...description && description !== title ? { description } : {},
426
+ ...details?.length ? { details } : {},
427
+ ...links?.length ? { links } : {}
428
+ };
429
+ }
430
+ function presentVisitorToolResult(result, registry = []) {
431
+ if (result.status === "completed" && toolResultFailed(result)) {
432
+ result = { ...result, status: "failed" };
433
+ }
434
+ if (result.toolName === "search_discovery" && result.status === "completed") {
435
+ const envelope2 = parseAgentToolResultEnvelope(result.output);
436
+ const search = parseAgentSearchDiscoveryOutput(
437
+ envelope2?.output ?? result.output
438
+ );
439
+ if (search) {
440
+ return {
441
+ id: result.callId,
442
+ toolName: "search_discovery",
443
+ status: "completed",
444
+ kind: "search",
445
+ sources: search.sources,
446
+ ...search.cta ? { cta: search.cta } : {}
447
+ };
448
+ }
449
+ }
450
+ const envelope = parseAgentToolResultEnvelope(result.output) ?? providerErrorEnvelope(result.output) ?? legacyBookingEnvelope(result.output);
451
+ const proposed = envelope ? resolvePresenterOutput(result, envelope, registry) : null;
452
+ if (proposed?.kind === "booking") {
453
+ return {
454
+ id: result.callId,
455
+ toolName: result.toolName,
456
+ status: result.status,
457
+ kind: "booking",
458
+ card: proposed.card
459
+ };
460
+ }
461
+ if (envelope?.ui && envelope.presentationKinds.includes("tool_input")) {
462
+ return {
463
+ id: result.callId,
464
+ toolName: result.toolName,
465
+ status: result.status,
466
+ kind: "input",
467
+ surface: envelope.ui
468
+ };
469
+ }
470
+ if (!proposed || result.status !== "completed" && proposed.kind !== "summary") {
471
+ if (result.status === "failed" || result.status === "rejected") {
472
+ return {
473
+ id: result.callId,
474
+ toolName: result.toolName,
475
+ status: result.status,
476
+ kind: "summary",
477
+ title: fallbackTitle(result)
478
+ };
479
+ }
480
+ return {
481
+ id: result.callId,
482
+ toolName: result.toolName,
483
+ status: result.status,
484
+ kind: "hidden"
485
+ };
486
+ }
487
+ if (proposed.kind === "entity") {
488
+ return {
489
+ id: result.callId,
490
+ toolName: result.toolName,
491
+ status: result.status,
492
+ ...proposed
493
+ };
494
+ }
495
+ if (proposed.kind === "collection") {
496
+ return {
497
+ id: result.callId,
498
+ toolName: result.toolName,
499
+ status: result.status,
500
+ ...proposed
501
+ };
502
+ }
503
+ if (proposed.kind === "signature") {
504
+ return {
505
+ id: result.callId,
506
+ toolName: result.toolName,
507
+ status: result.status,
508
+ ...proposed
509
+ };
510
+ }
511
+ return finalizeSummaryPresentation(result, proposed);
512
+ }
513
+ function providerErrorEnvelope(output) {
514
+ const record2 = asRecord3(output);
515
+ const providerError = asRecord3(record2?.providerError);
516
+ const message = safeText(providerError?.message);
517
+ const action = safeText(providerError?.action);
518
+ if (!message || !action) return null;
519
+ const resourceLabels = Array.isArray(providerError?.resourceLabels) ? providerError.resourceLabels.map(safeText).filter(Boolean).slice(0, MAX_DETAILS) : [];
520
+ return {
521
+ schemaVersion: "webless.tool-result.v1",
522
+ output: {
523
+ providerError: {
524
+ action,
525
+ message,
526
+ ...resourceLabels.length ? { resourceLabels } : {}
527
+ }
528
+ },
529
+ presentationKinds: ["provider_error"]
530
+ };
531
+ }
532
+ function legacyBookingEnvelope(output) {
533
+ const card = bookingCardFromActionOutput(output);
534
+ return card ? {
535
+ schemaVersion: "webless.tool-result.v1",
536
+ output: card,
537
+ presentationKinds: [card.type]
538
+ } : null;
539
+ }
540
+
541
+ // src/presentation.ts
542
+ function presentTranscriptToolResult(result) {
543
+ const output = parseAgentToolResultEnvelope(result.output)?.output ?? result.output;
544
+ const legacyError = output !== null && typeof output === "object" && "error" in output && typeof output.error === "string" && output.error.trim().length > 0;
545
+ return presentVisitorToolResult(
546
+ legacyError ? { ...result, status: "failed" } : result
547
+ );
548
+ }
549
+ export {
550
+ presentTranscriptToolResult,
551
+ presentVisitorToolResult
552
+ };
553
+ //# sourceMappingURL=presentation.js.map