@nekuda/webmcp-sdk 0.4.0-dev.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1607 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined")
5
+ return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ // src/define.ts
10
+ var NAME_PATTERN = /^[A-Za-z0-9_.-]{1,128}$/;
11
+ var STABLE_KEY_PATTERN = /^[a-z0-9_]+(\.[a-z0-9_]+)+$/;
12
+ var MAX_KEY_LENGTH = 1024;
13
+ var TOOL_SOURCES = ["scanner_generated", "merchant_authored"];
14
+ var TOOL_INTENTS = ["answer", "act", "transact"];
15
+ function fail(field, problem) {
16
+ throw new TypeError(`defineTool: ${field} ${problem}`);
17
+ }
18
+ function isPlainObject(value) {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+ function defineTool(definition) {
22
+ const { stableKey, description, inputSchema, execute } = definition;
23
+ if (typeof stableKey !== "string" || stableKey.trim() === "") {
24
+ fail("stableKey", "must be a non-empty string (the durable identity the platform keys on)");
25
+ }
26
+ if (stableKey.length > MAX_KEY_LENGTH) {
27
+ fail("stableKey", `must be at most ${MAX_KEY_LENGTH} characters`);
28
+ }
29
+ if (!STABLE_KEY_PATTERN.test(stableKey)) {
30
+ fail("stableKey", `must be dot-namespaced "domain.action" — [a-z0-9_]+ segments (2 or more) joined by "." (got ${JSON.stringify(stableKey)})`);
31
+ }
32
+ const nameOmitted = definition.name === undefined;
33
+ const name = definition.name ?? stableKey;
34
+ if (typeof name !== "string" || !NAME_PATTERN.test(name)) {
35
+ const field = nameOmitted ? "stableKey" : "name";
36
+ const suffix = nameOmitted ? " when used as the wire name, or set an explicit `name`" : "";
37
+ fail(field, `must be 1-128 chars of [A-Za-z0-9_\\-.]${suffix} (got ${JSON.stringify(name)})`);
38
+ }
39
+ if (typeof description !== "string" || description.trim() === "") {
40
+ fail("description", "must be a non-empty string");
41
+ }
42
+ if (inputSchema !== undefined && !isPlainObject(inputSchema)) {
43
+ fail("inputSchema", "must be a plain JSON Schema object when present");
44
+ }
45
+ if (definition.version !== undefined && (typeof definition.version !== "string" || definition.version.trim() === "")) {
46
+ fail("version", "must be a non-empty string when present");
47
+ }
48
+ if (typeof definition.version === "string" && definition.version.length > MAX_KEY_LENGTH) {
49
+ fail("version", `must be at most ${MAX_KEY_LENGTH} characters`);
50
+ }
51
+ for (const [field, allowed] of [
52
+ ["source", TOOL_SOURCES],
53
+ ["intent", TOOL_INTENTS]
54
+ ]) {
55
+ const value = definition[field];
56
+ if (value !== undefined && !allowed.includes(value)) {
57
+ fail(field, `must be one of ${allowed.join(" | ")} when present (got ${JSON.stringify(value)})`);
58
+ }
59
+ }
60
+ if (typeof execute !== "function") {
61
+ fail("execute", "must be a function");
62
+ }
63
+ return Object.freeze({ ...definition, name });
64
+ }
65
+ // src/spec.ts
66
+ function resolveModelContext(g = globalThis) {
67
+ const scope = g;
68
+ return scope.document?.modelContext ?? scope.navigator?.modelContext;
69
+ }
70
+
71
+ // src/transport.ts
72
+ var INGEST_BASE = "https://ingest.agentlane.dev";
73
+ var DEFAULT_COLLECT_ENDPOINT = `${INGEST_BASE}/v1/collect`;
74
+ var DEFAULT_TELEMETRY_ENDPOINT = `${INGEST_BASE}/v1/telemetry`;
75
+ function tryFetch(scope, url, headers, json) {
76
+ const f = scope.fetch;
77
+ if (typeof f !== "function")
78
+ return;
79
+ try {
80
+ const result = f(url, {
81
+ method: "POST",
82
+ keepalive: true,
83
+ headers,
84
+ body: json
85
+ });
86
+ if (result && typeof result.catch === "function") {
87
+ result.catch(() => {});
88
+ }
89
+ } catch {}
90
+ }
91
+ function sendToCollect(event, config, scope = globalThis) {
92
+ try {
93
+ const json = JSON.stringify(event);
94
+ if (json === undefined)
95
+ return;
96
+ const url = config.endpoint || DEFAULT_COLLECT_ENDPOINT;
97
+ const headers = { "content-type": "application/json", "x-api-key": config.apiKey };
98
+ tryFetch(scope, url, headers, json);
99
+ } catch {}
100
+ }
101
+ function sendTelemetry(event, scope = globalThis, endpoint, apiKey) {
102
+ try {
103
+ const json = JSON.stringify(event);
104
+ if (json === undefined)
105
+ return;
106
+ const url = endpoint || DEFAULT_TELEMETRY_ENDPOINT;
107
+ const headers = { "content-type": "application/json" };
108
+ if (typeof apiKey === "string" && apiKey.trim().length > 0)
109
+ headers["x-api-key"] = apiKey;
110
+ tryFetch(scope, url, headers, json);
111
+ } catch {}
112
+ }
113
+ var OTEL_LOGGER_NAME = "@nekuda/webmcp-sdk";
114
+ var OTEL_SEVERITY_INFO = 9;
115
+ async function defaultLoader() {
116
+ try {
117
+ const mod = await import("@opentelemetry/api-logs");
118
+ return mod.logs ?? null;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+ var loaderCache = new WeakMap;
124
+ function loadCached(loadLogs) {
125
+ let cached = loaderCache.get(loadLogs);
126
+ if (cached === undefined) {
127
+ cached = Promise.resolve().then(() => loadLogs()).then((api) => api ?? null).catch(() => null);
128
+ loaderCache.set(loadLogs, cached);
129
+ }
130
+ return cached;
131
+ }
132
+ function emitOtelLog(event, loadLogs = defaultLoader) {
133
+ return loadCached(loadLogs).then((api) => {
134
+ if (!api)
135
+ return;
136
+ try {
137
+ api.getLogger(OTEL_LOGGER_NAME).emit({
138
+ severityNumber: OTEL_SEVERITY_INFO,
139
+ body: event,
140
+ attributes: {
141
+ "event.name": event.eventName,
142
+ "tool.stable_key": event.toolStableKey,
143
+ "call.id": event.callId
144
+ }
145
+ });
146
+ } catch {}
147
+ }).catch(() => {});
148
+ }
149
+
150
+ // src/tracking.ts
151
+ var SESSION_TIMEOUT_MS = 30 * 60 * 1000;
152
+ var ANON_NAMESPACE = "anon";
153
+ function fnv1a(input) {
154
+ let hash = 2166136261;
155
+ for (let i = 0;i < input.length; i++) {
156
+ hash ^= input.charCodeAt(i);
157
+ hash = Math.imul(hash, 16777619);
158
+ }
159
+ return (hash >>> 0).toString(16).padStart(8, "0");
160
+ }
161
+ function storageNamespace(apiKey) {
162
+ if (!apiKey)
163
+ return ANON_NAMESPACE;
164
+ return fnv1a(apiKey);
165
+ }
166
+ var visitorKey = (ns) => `webmcp:${ns}:visitor_id`;
167
+ var sessionKey = (ns) => `webmcp:${ns}:session_id`;
168
+ var lastSeenKey = (ns) => `webmcp:${ns}:last_seen`;
169
+ var memoryVisitor = new Map;
170
+ var memorySession = new Map;
171
+ var MAX_ID_LENGTH = 64;
172
+ function usableId(value) {
173
+ return value !== null && value.length > 0 && value.length <= MAX_ID_LENGTH;
174
+ }
175
+ function storage(kind) {
176
+ try {
177
+ return globalThis[kind] ?? null;
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+ function readItem(store, key) {
183
+ if (!store)
184
+ return null;
185
+ try {
186
+ return store.getItem(key);
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ function writeItem(store, key, value) {
192
+ if (!store)
193
+ return false;
194
+ try {
195
+ store.setItem(key, value);
196
+ return true;
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function fallbackId(cache, namespace, candidate) {
202
+ const cached = cache.get(namespace);
203
+ if (cached)
204
+ return cached;
205
+ cache.set(namespace, candidate);
206
+ return candidate;
207
+ }
208
+ var idSequence = 0;
209
+ function fillPseudoRandom(bytes) {
210
+ if (fillFromMathRandom(bytes))
211
+ return;
212
+ const seq = ++idSequence;
213
+ for (let i = 0;i < bytes.length; i++)
214
+ bytes[i] = seq >>> i % 4 * 8;
215
+ }
216
+ function fillFromMathRandom(bytes) {
217
+ try {
218
+ for (let i = 0;i < bytes.length; i++) {
219
+ const draw = Math.random();
220
+ if (typeof draw !== "number" || !(draw >= 0 && draw < 1))
221
+ return false;
222
+ bytes[i] = Math.floor(draw * 256);
223
+ }
224
+ return true;
225
+ } catch {
226
+ return false;
227
+ }
228
+ }
229
+ function randomId() {
230
+ const bytes = new Uint8Array(16);
231
+ let filled = false;
232
+ try {
233
+ const c = globalThis.crypto;
234
+ if (typeof c?.randomUUID === "function") {
235
+ const uuid = c.randomUUID();
236
+ if (typeof uuid === "string" && usableId(uuid))
237
+ return uuid;
238
+ }
239
+ if (typeof c?.getRandomValues === "function") {
240
+ c.getRandomValues(bytes);
241
+ filled = true;
242
+ }
243
+ } catch {}
244
+ if (!filled)
245
+ fillPseudoRandom(bytes);
246
+ const hex = Array.from(bytes, (byte, i) => {
247
+ const v = i === 6 ? byte & 15 | 64 : i === 8 ? byte & 63 | 128 : byte;
248
+ return v.toString(16).padStart(2, "0");
249
+ }).join("");
250
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
251
+ }
252
+ function getOrCreateVisitorId(namespace) {
253
+ const store = storage("localStorage");
254
+ const key = visitorKey(namespace);
255
+ const existing = readItem(store, key);
256
+ if (usableId(existing))
257
+ return existing;
258
+ const id = randomId();
259
+ if (writeItem(store, key, id))
260
+ return id;
261
+ return fallbackId(memoryVisitor, namespace, id);
262
+ }
263
+ function nowMs() {
264
+ try {
265
+ const now = Date.now();
266
+ return typeof now === "number" && Number.isFinite(now) ? now : undefined;
267
+ } catch {
268
+ return;
269
+ }
270
+ }
271
+ function getOrCreateSessionId(namespace) {
272
+ const store = storage("sessionStorage");
273
+ const now = nowMs();
274
+ const existing = readItem(store, sessionKey(namespace));
275
+ const lastSeen = Number(readItem(store, lastSeenKey(namespace)));
276
+ const elapsed = now === undefined ? Number.NaN : now - lastSeen;
277
+ const active = usableId(existing) && Number.isFinite(lastSeen) && elapsed >= 0 && elapsed < SESSION_TIMEOUT_MS;
278
+ const id = active ? existing : randomId();
279
+ const persisted = writeItem(store, sessionKey(namespace), id) && (now === undefined || writeItem(store, lastSeenKey(namespace), String(now)));
280
+ if (persisted)
281
+ return id;
282
+ return fallbackId(memorySession, namespace, id);
283
+ }
284
+ function trackingOutputs(options) {
285
+ try {
286
+ if (!options || options.disabled)
287
+ return { toBackend: false, toOtel: false };
288
+ return {
289
+ toBackend: typeof options.apiKey === "string" && options.apiKey.trim().length > 0,
290
+ toOtel: options.otel === true
291
+ };
292
+ } catch {
293
+ return { toBackend: false, toOtel: false };
294
+ }
295
+ }
296
+ function pageFields() {
297
+ const fields = {};
298
+ try {
299
+ const loc = globalThis.location;
300
+ if (loc) {
301
+ if (loc.origin)
302
+ fields.siteOrigin = loc.origin;
303
+ if (loc.href)
304
+ fields.url = loc.href;
305
+ if (loc.pathname)
306
+ fields.path = loc.pathname;
307
+ }
308
+ const doc = globalThis.document;
309
+ if (doc) {
310
+ if (doc.referrer !== undefined)
311
+ fields.referrer = doc.referrer;
312
+ if (doc.title !== undefined)
313
+ fields.title = doc.title;
314
+ }
315
+ } catch {}
316
+ return fields;
317
+ }
318
+ function errorMessage(error) {
319
+ try {
320
+ const read = error instanceof Error ? error.message : String(error);
321
+ return typeof read === "string" ? read : "";
322
+ } catch {
323
+ return "";
324
+ }
325
+ }
326
+ function buildEventPayload(params) {
327
+ return {
328
+ eventId: randomId(),
329
+ visitorId: params.visitorId,
330
+ sessionId: params.sessionId,
331
+ eventName: params.eventName,
332
+ ts: new Date().toISOString(),
333
+ ...pageFields(),
334
+ ...params.data
335
+ };
336
+ }
337
+ var MAX_EVENT_BYTES = 64 * 1024;
338
+ var MAX_ERROR_BYTES = 16 * 1024;
339
+ var TRUNCATABLE = ["response", "input", "error"];
340
+ var PAGE_STRINGS = ["url", "referrer", "title", "path", "siteOrigin"];
341
+ var MAX_PAGE_FIELD_BYTES = 4 * 1024;
342
+ var TOOL_ENTRY_KEPT = ["stableKey", "name", "outcome", "schemaHash", "source", "intent"];
343
+ function isMarker(value) {
344
+ return typeof value === "object" && value !== null && value.__truncated === true;
345
+ }
346
+ function byteLength(json) {
347
+ try {
348
+ const TE = globalThis.TextEncoder;
349
+ if (typeof TE === "function")
350
+ return new TE().encode(json).length;
351
+ } catch {}
352
+ return json.length;
353
+ }
354
+ function escapedByteLength(value) {
355
+ return byteLength(JSON.stringify(value)) - 2;
356
+ }
357
+ function sliceToBytes(value, maxBytes) {
358
+ if (escapedByteLength(value) <= maxBytes)
359
+ return value;
360
+ const g = globalThis;
361
+ try {
362
+ if (typeof g.TextEncoder === "function" && typeof g.TextDecoder === "function") {
363
+ const bytes = new g.TextEncoder().encode(value);
364
+ const decoder = new g.TextDecoder("utf-8");
365
+ const decode = (end) => {
366
+ const s = decoder.decode(bytes.subarray(0, end));
367
+ return s.endsWith("�") ? s.slice(0, -1) : s;
368
+ };
369
+ let lo = 0;
370
+ let hi = Math.min(bytes.length, maxBytes);
371
+ while (lo < hi) {
372
+ const mid = Math.ceil((lo + hi) / 2);
373
+ if (escapedByteLength(decode(mid)) <= maxBytes)
374
+ lo = mid;
375
+ else
376
+ hi = mid - 1;
377
+ }
378
+ return decode(lo);
379
+ }
380
+ } catch {}
381
+ return value.slice(0, Math.floor(maxBytes / 6));
382
+ }
383
+ function serializedBytes(value) {
384
+ return fieldBytes(value) ?? undefined;
385
+ }
386
+ function fieldBytes(value) {
387
+ let json;
388
+ try {
389
+ json = JSON.stringify(value);
390
+ } catch {
391
+ return null;
392
+ }
393
+ return json === undefined ? 0 : byteLength(json);
394
+ }
395
+ function underLimit(event) {
396
+ const size = fieldBytes(event);
397
+ return size !== null && size < MAX_EVENT_BYTES;
398
+ }
399
+ function stripToolEntry(entry) {
400
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
401
+ return entry;
402
+ const kept = {};
403
+ for (const key of TOOL_ENTRY_KEPT) {
404
+ const value = entry[key];
405
+ if (value !== undefined)
406
+ kept[key] = value;
407
+ }
408
+ return kept;
409
+ }
410
+ function boundTools(bounded) {
411
+ const tools = bounded.tools;
412
+ if (!Array.isArray(tools))
413
+ return;
414
+ bounded.tools = tools.map(stripToolEntry);
415
+ if (underLimit(bounded))
416
+ return;
417
+ bounded.tools = {
418
+ __truncated: true,
419
+ originalBytes: fieldBytes(tools) ?? 0,
420
+ toolCount: tools.length
421
+ };
422
+ }
423
+ function boundEventPayload(event) {
424
+ try {
425
+ if (underLimit(event))
426
+ return event;
427
+ return bound(event);
428
+ } catch {
429
+ return event;
430
+ }
431
+ }
432
+ function bound(event) {
433
+ const bounded = { ...event };
434
+ for (const field of TRUNCATABLE) {
435
+ if (field in bounded && fieldBytes(bounded[field]) === null) {
436
+ bounded[field] = { __truncated: true, originalBytes: 0 };
437
+ }
438
+ }
439
+ for (const field of ["response", "input"]) {
440
+ if (underLimit(bounded))
441
+ return bounded;
442
+ if (field in bounded && !isMarker(bounded[field])) {
443
+ bounded[field] = { __truncated: true, originalBytes: fieldBytes(bounded[field]) ?? 0 };
444
+ }
445
+ }
446
+ if (underLimit(bounded))
447
+ return bounded;
448
+ if ("error" in bounded && !isMarker(bounded.error)) {
449
+ const err = bounded.error;
450
+ bounded.error = typeof err === "string" ? sliceToBytes(err, MAX_ERROR_BYTES) : { __truncated: true, originalBytes: fieldBytes(err) ?? 0 };
451
+ }
452
+ if (!underLimit(bounded))
453
+ boundTools(bounded);
454
+ for (const field of PAGE_STRINGS) {
455
+ if (underLimit(bounded))
456
+ return bounded;
457
+ const value = bounded[field];
458
+ if (typeof value === "string")
459
+ bounded[field] = sliceToBytes(value, MAX_PAGE_FIELD_BYTES);
460
+ }
461
+ return bounded;
462
+ }
463
+ var defaultSinks = { sendToCollect, emitOtelLog };
464
+ function track(options, eventName, data, sinks = defaultSinks) {
465
+ try {
466
+ const { toBackend, toOtel } = trackingOutputs(options);
467
+ if (!toBackend && !toOtel)
468
+ return;
469
+ const namespace = storageNamespace(options.apiKey);
470
+ const event = boundEventPayload(buildEventPayload({
471
+ visitorId: getOrCreateVisitorId(namespace),
472
+ sessionId: getOrCreateSessionId(namespace),
473
+ eventName,
474
+ data
475
+ }));
476
+ if (toBackend) {
477
+ sinks.sendToCollect(event, {
478
+ apiKey: options.apiKey,
479
+ ...options.endpoint !== undefined ? { endpoint: options.endpoint } : {}
480
+ });
481
+ }
482
+ if (toOtel)
483
+ sinks.emitOtelLog(event);
484
+ } catch {}
485
+ }
486
+
487
+ // src/telemetry-context.ts
488
+ function guarded(read) {
489
+ try {
490
+ return read();
491
+ } catch {
492
+ return;
493
+ }
494
+ }
495
+ var MAX_ROUTE_SEGMENTS = 8;
496
+ var MAX_ROUTE_BYTES = 256;
497
+ var MAX_INPUT_CHARS = 4096;
498
+ var OVERFLOW_SEGMENT = "*";
499
+ var DIGITS_SEGMENT = /^\d+$/;
500
+ var UUID_SEGMENT = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
501
+ var HEX_SEGMENT = /^[0-9a-f]{12,}$/i;
502
+ var SLUG_SEGMENT = /^[A-Za-z0-9]+(?:[-_.][A-Za-z0-9]+)*$/;
503
+ var MAX_SLUG_CHARS = 32;
504
+ var OPAQUE_SEGMENT = ":token";
505
+ var MAX_UPPERCASE_SHARE = 0.3;
506
+ function uppercaseHeavy(run) {
507
+ let uppercase = 0;
508
+ for (let i = 0;i < run.length; i += 1) {
509
+ const code = run.charCodeAt(i);
510
+ if (code >= 65 && code <= 90)
511
+ uppercase += 1;
512
+ }
513
+ return uppercase > run.length * MAX_UPPERCASE_SHARE;
514
+ }
515
+ var MIN_OPAQUE_SLUG_CHARS = 12;
516
+ function pathSegments(pathname) {
517
+ const capped = pathname.length > MAX_INPUT_CHARS ? pathname.slice(0, MAX_INPUT_CHARS) : pathname;
518
+ const cut = capped.search(/[?#]/);
519
+ return (cut === -1 ? capped : capped.slice(0, cut)).split("/").filter((s) => s.length > 0);
520
+ }
521
+ function templateSegment(segment) {
522
+ if (DIGITS_SEGMENT.test(segment))
523
+ return ":id";
524
+ if (UUID_SEGMENT.test(segment))
525
+ return ":uuid";
526
+ if (HEX_SEGMENT.test(segment))
527
+ return ":hash";
528
+ if (segment.length > MAX_SLUG_CHARS || !SLUG_SEGMENT.test(segment))
529
+ return OPAQUE_SEGMENT;
530
+ if (segment.length >= MIN_OPAQUE_SLUG_CHARS && uppercaseHeavy(segment))
531
+ return OPAQUE_SEGMENT;
532
+ return segment;
533
+ }
534
+ function routeTemplate(pathname) {
535
+ if (typeof pathname !== "string")
536
+ return;
537
+ const segments = pathSegments(pathname);
538
+ const kept = segments.slice(0, MAX_ROUTE_SEGMENTS).map(templateSegment);
539
+ if (segments.length > MAX_ROUTE_SEGMENTS)
540
+ kept.push(OVERFLOW_SEGMENT);
541
+ return sliceToBytes(`/${kept.join("/")}`, MAX_ROUTE_BYTES);
542
+ }
543
+ function segmentCount(pathname) {
544
+ if (typeof pathname !== "string")
545
+ return;
546
+ return pathSegments(pathname).length;
547
+ }
548
+ var AI_ASSISTANT_HOSTS = [
549
+ "chatgpt.com",
550
+ "chat.openai.com",
551
+ "openai.com",
552
+ "claude.ai",
553
+ "anthropic.com",
554
+ "perplexity.ai",
555
+ "gemini.google.com",
556
+ "bard.google.com",
557
+ "aistudio.google.com",
558
+ "copilot.microsoft.com",
559
+ "grok.com",
560
+ "x.ai",
561
+ "deepseek.com",
562
+ "meta.ai",
563
+ "poe.com",
564
+ "you.com"
565
+ ];
566
+ var SEARCH_HOSTS = [
567
+ "bing.com",
568
+ "duckduckgo.com",
569
+ "baidu.com",
570
+ "ecosia.org",
571
+ "search.brave.com",
572
+ "startpage.com",
573
+ "qwant.com",
574
+ "naver.com"
575
+ ];
576
+ var SEARCH_PATTERNS = [
577
+ /(^|\.)google\.[a-z]{2,3}(\.[a-z]{2,3})?$/,
578
+ /(^|\.)yahoo\.[a-z]{2,3}(\.[a-z]{2,3})?$/,
579
+ /(^|\.)yandex\.[a-z]{2,3}(\.[a-z]{2,3})?$/
580
+ ];
581
+ var SOCIAL_HOSTS = [
582
+ "facebook.com",
583
+ "instagram.com",
584
+ "x.com",
585
+ "twitter.com",
586
+ "t.co",
587
+ "linkedin.com",
588
+ "reddit.com",
589
+ "pinterest.com",
590
+ "tiktok.com",
591
+ "youtube.com",
592
+ "threads.net",
593
+ "snapchat.com",
594
+ "tumblr.com",
595
+ "discord.com",
596
+ "t.me",
597
+ "whatsapp.com"
598
+ ];
599
+ function hostMatches(hostname, domains) {
600
+ return domains.some((domain) => hostname === domain || hostname.endsWith(`.${domain}`));
601
+ }
602
+ function parsedUrl(value) {
603
+ if (typeof value !== "string" || value.length === 0 || value.length > MAX_INPUT_CHARS) {
604
+ return;
605
+ }
606
+ return guarded(() => new URL(value));
607
+ }
608
+ function originOf(value) {
609
+ return parsedUrl(value)?.origin.toLowerCase();
610
+ }
611
+ function referrerClass(referrer, origin) {
612
+ if (typeof referrer !== "string" || referrer.trim().length === 0)
613
+ return "direct";
614
+ const url = parsedUrl(referrer);
615
+ if (url === undefined)
616
+ return "other";
617
+ const referrerOrigin = url.origin.toLowerCase();
618
+ const own = originOf(origin) ?? (typeof origin === "string" ? origin.trim().toLowerCase() : "");
619
+ if (own.length > 0 && referrerOrigin === own)
620
+ return "internal";
621
+ const hostname = guarded(() => url.hostname.toLowerCase()) ?? "";
622
+ if (hostMatches(hostname, AI_ASSISTANT_HOSTS))
623
+ return "ai_assistant";
624
+ if (hostMatches(hostname, SEARCH_HOSTS))
625
+ return "search";
626
+ if (SEARCH_PATTERNS.some((pattern) => pattern.test(hostname)))
627
+ return "search";
628
+ if (hostMatches(hostname, SOCIAL_HOSTS))
629
+ return "social";
630
+ return "other";
631
+ }
632
+ var MOBILE_MAX_WIDTH = 767;
633
+ function formFactor(scope = globalThis) {
634
+ const g = scope;
635
+ const hint = guarded(() => g.navigator?.userAgentData?.mobile);
636
+ if (typeof hint === "boolean")
637
+ return hint ? "mobile" : "desktop";
638
+ const query = guarded(() => g.matchMedia);
639
+ if (typeof query === "function") {
640
+ const media = guarded(() => query.call(g, "(pointer: coarse) and (hover: none)"));
641
+ const coarse = guarded(() => media?.matches);
642
+ if (typeof coarse === "boolean")
643
+ return coarse ? "mobile" : "desktop";
644
+ }
645
+ const width = guarded(() => g.screen?.width);
646
+ if (typeof width === "number" && Number.isFinite(width)) {
647
+ return width <= MOBILE_MAX_WIDTH ? "mobile" : "desktop";
648
+ }
649
+ return;
650
+ }
651
+ var PRIMARY_SUBTAG = /^[a-z]{2,3}$/;
652
+ function languageSubtag(scope = globalThis) {
653
+ const g = scope;
654
+ const primary = guarded(() => g.navigator?.language);
655
+ const list = guarded(() => g.navigator?.languages);
656
+ const raw = typeof primary === "string" ? primary : Array.isArray(list) ? list[0] : undefined;
657
+ if (typeof raw !== "string")
658
+ return;
659
+ const subtag = raw.trim().toLowerCase().split(/[-_]/)[0] ?? "";
660
+ return PRIMARY_SUBTAG.test(subtag) ? subtag : undefined;
661
+ }
662
+ var RUNTIME_TOKENS = [
663
+ [/chatgpt|gptbot|oai-searchbot|openai/, "chatgpt"],
664
+ [/claude|anthropic/, "claude"],
665
+ [/perplexity/, "perplexity"],
666
+ [/headless|puppeteer|playwright|selenium|phantomjs/, "headless"]
667
+ ];
668
+ var MAX_UA_SCAN = 1024;
669
+ function agentRuntime(scope = globalThis) {
670
+ const g = scope;
671
+ const ua = guarded(() => g.navigator?.userAgent);
672
+ const tokens = typeof ua === "string" ? ua.slice(0, MAX_UA_SCAN).toLowerCase() : undefined;
673
+ if (tokens !== undefined) {
674
+ for (const [pattern, runtime] of RUNTIME_TOKENS) {
675
+ if (pattern.test(tokens))
676
+ return runtime;
677
+ }
678
+ }
679
+ if (guarded(() => g.navigator?.webdriver) === true)
680
+ return "headless";
681
+ return tokens === undefined ? "unknown" : "browser";
682
+ }
683
+ function frameContext(scope = globalThis) {
684
+ const g = scope;
685
+ const self = guarded(() => g.self);
686
+ if (self === undefined)
687
+ return;
688
+ let top;
689
+ try {
690
+ top = g.top;
691
+ } catch {
692
+ return "iframe";
693
+ }
694
+ if (top === undefined)
695
+ return;
696
+ return top === self ? "top" : "iframe";
697
+ }
698
+ var VISIBILITY_STATES = ["visible", "hidden", "prerender"];
699
+ function visibility(scope = globalThis) {
700
+ const g = scope;
701
+ const state = guarded(() => g.document?.visibilityState);
702
+ return VISIBILITY_STATES.find((known) => known === state);
703
+ }
704
+ var PROVENANCE_MARKER = "__webmcpProvenance";
705
+ var CLAIMABLE_PROVENANCES = ["polyfill", "extension"];
706
+ var MAX_SPEC_VERSION_BYTES = 64;
707
+ function surfaceInfo(scope = globalThis) {
708
+ const g = scope;
709
+ const fromDocument = guarded(() => g.document?.modelContext);
710
+ const surface = guarded(() => resolveModelContext(scope));
711
+ if (!surface)
712
+ return { available: false, provenance: "none" };
713
+ const declared = surface;
714
+ const claimed = guarded(() => declared[PROVENANCE_MARKER]);
715
+ const version = guarded(() => declared.specVersion);
716
+ return {
717
+ available: true,
718
+ provenance: CLAIMABLE_PROVENANCES.find((known) => known === claimed) ?? "native",
719
+ global: fromDocument === surface ? "document.modelContext" : "navigator.modelContext",
720
+ ...typeof version === "string" && version.length > 0 ? { specVersion: sliceToBytes(version, MAX_SPEC_VERSION_BYTES) } : {}
721
+ };
722
+ }
723
+ function timeSinceNavigation(scope = globalThis) {
724
+ const perf = guarded(() => scope.performance);
725
+ const now = guarded(() => {
726
+ const clock = perf?.now;
727
+ return typeof clock === "function" ? clock.call(perf) : undefined;
728
+ });
729
+ if (isElapsed(now))
730
+ return Math.round(now);
731
+ const origin = guarded(() => perf?.timeOrigin);
732
+ if (typeof origin !== "number" || !Number.isFinite(origin) || origin <= 0)
733
+ return;
734
+ const elapsed = guarded(() => Date.now() - origin);
735
+ return isElapsed(elapsed) ? Math.round(elapsed) : undefined;
736
+ }
737
+ function isElapsed(value) {
738
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
739
+ }
740
+ var INIT_AT = timeSinceNavigation();
741
+ function initAt() {
742
+ return INIT_AT;
743
+ }
744
+ function timeSinceInit(scope = globalThis) {
745
+ if (INIT_AT === undefined)
746
+ return;
747
+ const now = timeSinceNavigation(scope);
748
+ return now === undefined ? undefined : Math.max(0, Math.round(now - INIT_AT));
749
+ }
750
+ var SESSION_ID = randomId();
751
+ function sessionId() {
752
+ return SESSION_ID;
753
+ }
754
+
755
+ // src/telemetry-events.ts
756
+ var TELEMETRY_SCHEMA_VERSION = 2;
757
+
758
+ // src/telemetry-fields.ts
759
+ var TELEMETRY_FIELDS = {
760
+ schema: true,
761
+ event: true,
762
+ ts: true,
763
+ sessionId: true,
764
+ "sdk.name": true,
765
+ "sdk.version": true,
766
+ "sdk.installMode": true,
767
+ "surface.available": true,
768
+ "surface.provenance": true,
769
+ "surface.global": true,
770
+ "surface.specVersion": true,
771
+ "page.routeTemplate": true,
772
+ "page.segmentCount": true,
773
+ "page.referrerClass": true,
774
+ "page.frameContext": true,
775
+ "page.visibility": true,
776
+ "client.agentRuntime": true,
777
+ "client.browser": true,
778
+ "client.browserMajor": true,
779
+ "client.formFactor": true,
780
+ "client.language": true,
781
+ timeToInitMs: true,
782
+ registrationIndex: true,
783
+ trigger: true,
784
+ settleMs: true,
785
+ "config.trackingEnabled": true,
786
+ "config.otelEnabled": true,
787
+ "config.customEndpoint": true,
788
+ tools: true,
789
+ callId: true,
790
+ callIndex: true,
791
+ toolCallIndex: true,
792
+ precededBy: true,
793
+ tool: true,
794
+ outcome: true,
795
+ durationMs: true,
796
+ "response.bytes": true,
797
+ "response.contentBlocks": true,
798
+ "response.isError": true,
799
+ errorClass: true,
800
+ errorSignature: true,
801
+ routeTemplate: true,
802
+ timeSinceInitMs: true,
803
+ agentRuntime: true
804
+ };
805
+ var TELEMETRY_TOOL_FIELDS = {
806
+ name: true,
807
+ stableKey: true,
808
+ version: true,
809
+ schemaHash: true,
810
+ source: true,
811
+ intent: true,
812
+ outcome: true,
813
+ failureSignature: true,
814
+ descriptionLength: true,
815
+ annotations: true,
816
+ paramCount: true,
817
+ requiredCount: true,
818
+ freeTextParamCount: true,
819
+ enumParamCount: true,
820
+ maxDepth: true,
821
+ describedParamCount: true
822
+ };
823
+
824
+ // src/tool-metrics.ts
825
+ var MAX_CANONICAL_DEPTH = 12;
826
+ var MAX_CANONICAL_NODES = 4096;
827
+ var MAX_CANONICAL_STRING = 1024;
828
+ var MAX_PARAMS = 512;
829
+ var MAX_SCHEMA_DEPTH = 12;
830
+ var MAX_SCHEMA_NODES = 4096;
831
+ var DEPTH_TOKEN = '"~depth"';
832
+ var CYCLE_TOKEN = '"~cycle"';
833
+ var BUDGET_TOKEN = '"~budget"';
834
+ function guarded2(read) {
835
+ try {
836
+ return read();
837
+ } catch {
838
+ return;
839
+ }
840
+ }
841
+ function isSchemaObject(value) {
842
+ return typeof value === "object" && value !== null && !Array.isArray(value);
843
+ }
844
+ function encodeString(value) {
845
+ return JSON.stringify(value.length > MAX_CANONICAL_STRING ? value.slice(0, MAX_CANONICAL_STRING) : value);
846
+ }
847
+ function encode(value, depth, path, budget) {
848
+ if (budget.nodes <= 0)
849
+ return BUDGET_TOKEN;
850
+ budget.nodes--;
851
+ if (depth > MAX_CANONICAL_DEPTH)
852
+ return DEPTH_TOKEN;
853
+ switch (typeof value) {
854
+ case "string":
855
+ return encodeString(value);
856
+ case "number":
857
+ return Number.isFinite(value) ? String(value) : "null";
858
+ case "boolean":
859
+ return value ? "true" : "false";
860
+ case "bigint":
861
+ return encodeString(value.toString());
862
+ case "object":
863
+ break;
864
+ default:
865
+ return "null";
866
+ }
867
+ if (value === null)
868
+ return "null";
869
+ const container = value;
870
+ if (path.has(container))
871
+ return CYCLE_TOKEN;
872
+ path.add(container);
873
+ try {
874
+ if (Array.isArray(value)) {
875
+ const items = [];
876
+ for (const item of value) {
877
+ items.push(encode(item, depth + 1, path, budget));
878
+ if (budget.nodes <= 0)
879
+ break;
880
+ }
881
+ return `[${items.join(",")}]`;
882
+ }
883
+ const keys = guarded2(() => Object.keys(container)) ?? [];
884
+ keys.sort();
885
+ const entries = [];
886
+ for (const key of keys) {
887
+ const child = guarded2(() => container[key]);
888
+ if (child === undefined || typeof child === "function" || typeof child === "symbol")
889
+ continue;
890
+ entries.push(`${encodeString(key)}:${encode(child, depth + 1, path, budget)}`);
891
+ if (budget.nodes <= 0)
892
+ break;
893
+ }
894
+ return `{${entries.join(",")}}`;
895
+ } finally {
896
+ path.delete(container);
897
+ }
898
+ }
899
+ function canonicalize(value, depth = 0) {
900
+ return encode(value, depth, new Set, { nodes: MAX_CANONICAL_NODES });
901
+ }
902
+ function schemaHash(inputSchema) {
903
+ if (!isSchemaObject(inputSchema))
904
+ return;
905
+ const canonical = guarded2(() => canonicalize(inputSchema));
906
+ return canonical === undefined ? undefined : fnv1a(canonical);
907
+ }
908
+ var ZERO_METRICS = {
909
+ paramCount: 0,
910
+ requiredCount: 0,
911
+ freeTextParamCount: 0,
912
+ enumParamCount: 0,
913
+ maxDepth: 0,
914
+ describedParamCount: 0
915
+ };
916
+ function clamp(count) {
917
+ if (!Number.isFinite(count))
918
+ return 0;
919
+ return Math.max(0, Math.min(Math.trunc(count), MAX_PARAMS));
920
+ }
921
+ function childSchemas(schema) {
922
+ const children = [];
923
+ const props = guarded2(() => schema.properties);
924
+ if (isSchemaObject(props)) {
925
+ for (const key of (guarded2(() => Object.keys(props)) ?? []).slice(0, MAX_PARAMS)) {
926
+ const child = guarded2(() => props[key]);
927
+ if (isSchemaObject(child))
928
+ children.push(child);
929
+ }
930
+ }
931
+ const items = guarded2(() => schema.items);
932
+ if (isSchemaObject(items))
933
+ children.push(items);
934
+ else if (Array.isArray(items)) {
935
+ for (const item of items.slice(0, MAX_PARAMS)) {
936
+ if (isSchemaObject(item))
937
+ children.push(item);
938
+ }
939
+ }
940
+ return children.slice(0, MAX_PARAMS);
941
+ }
942
+ function depthOf(schema, depth, path, budget) {
943
+ if (depth >= MAX_SCHEMA_DEPTH || budget.nodes <= 0 || path.has(schema))
944
+ return 0;
945
+ budget.nodes--;
946
+ path.add(schema);
947
+ try {
948
+ const children = childSchemas(schema);
949
+ if (children.length === 0)
950
+ return 0;
951
+ let deepest = 0;
952
+ for (const child of children) {
953
+ deepest = Math.max(deepest, depthOf(child, depth + 1, path, budget));
954
+ if (budget.nodes <= 0)
955
+ break;
956
+ }
957
+ return 1 + deepest;
958
+ } finally {
959
+ path.delete(schema);
960
+ }
961
+ }
962
+ function collect(schema) {
963
+ const props = guarded2(() => schema.properties);
964
+ const allKeys = isSchemaObject(props) ? guarded2(() => Object.keys(props)) ?? [] : [];
965
+ let freeTextParamCount = 0;
966
+ let enumParamCount = 0;
967
+ let describedParamCount = 0;
968
+ for (const key of allKeys.slice(0, MAX_PARAMS)) {
969
+ const param = guarded2(() => props[key]);
970
+ if (!isSchemaObject(param))
971
+ continue;
972
+ const hasEnum = Array.isArray(guarded2(() => param.enum));
973
+ if (hasEnum)
974
+ enumParamCount++;
975
+ const type = guarded2(() => param.type);
976
+ const format = guarded2(() => param.format);
977
+ if (type === "string" && !hasEnum && typeof format !== "string")
978
+ freeTextParamCount++;
979
+ const description = guarded2(() => param.description);
980
+ if (typeof description === "string" && description.trim() !== "")
981
+ describedParamCount++;
982
+ }
983
+ const required = guarded2(() => schema.required);
984
+ const requiredCount = Array.isArray(required) ? required.slice(0, MAX_PARAMS).filter((entry) => typeof entry === "string").length : 0;
985
+ return {
986
+ paramCount: clamp(allKeys.length),
987
+ requiredCount: clamp(requiredCount),
988
+ freeTextParamCount: clamp(freeTextParamCount),
989
+ enumParamCount: clamp(enumParamCount),
990
+ maxDepth: depthOf(schema, 0, new Set, { nodes: MAX_SCHEMA_NODES }),
991
+ describedParamCount: clamp(describedParamCount)
992
+ };
993
+ }
994
+ function shapeMetrics(inputSchema) {
995
+ if (!isSchemaObject(inputSchema))
996
+ return;
997
+ return guarded2(() => collect(inputSchema)) ?? { ...ZERO_METRICS };
998
+ }
999
+
1000
+ // src/telemetry.ts
1001
+ var SDK_NAME = "@nekuda/webmcp-sdk";
1002
+ var SDK_VERSION = "0.4.0-dev.7.3";
1003
+ var INSTALL_MODES = ["npm", "cdn_snippet"];
1004
+ var SDK_INSTALL_MODE = INSTALL_MODES.find((mode) => mode === (typeof __WEBMCP_INSTALL_MODE__ === "string" ? __WEBMCP_INSTALL_MODE__ : "")) ?? "npm";
1005
+ function isFieldParent(value) {
1006
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1007
+ }
1008
+ function safe(read) {
1009
+ try {
1010
+ return read();
1011
+ } catch {
1012
+ return;
1013
+ }
1014
+ }
1015
+ function gpcOptedOut(scope = globalThis) {
1016
+ return safe(() => scope.navigator?.globalPrivacyControl) === true;
1017
+ }
1018
+ function globalOptOut(scope) {
1019
+ return safe(() => scope.__WEBMCP_TELEMETRY__) === false;
1020
+ }
1021
+ function telemetryEnabled(option, scope = globalThis) {
1022
+ if (option === false)
1023
+ return false;
1024
+ if (globalOptOut(scope))
1025
+ return false;
1026
+ if (safe(() => scope.document) === undefined)
1027
+ return false;
1028
+ return !gpcOptedOut(scope);
1029
+ }
1030
+ var GREASE_BRAND = /not[\W_]*a[\W_]*brand/i;
1031
+ var MAX_BRAND_BYTES = 1024;
1032
+ function brandInfo(brands) {
1033
+ if (!Array.isArray(brands))
1034
+ return {};
1035
+ const named = brands.filter((entry) => isFieldParent(entry) && typeof entry.brand === "string" && entry.brand.length > 0 && !GREASE_BRAND.test(entry.brand));
1036
+ const chosen = named.find((entry) => !/chromium/i.test(entry.brand)) ?? named[0];
1037
+ if (!chosen)
1038
+ return {};
1039
+ const major = Number.parseInt(String(chosen.version), 10);
1040
+ return {
1041
+ browser: sliceToBytes(chosen.brand, MAX_BRAND_BYTES),
1042
+ ...Number.isFinite(major) ? { browserMajor: major } : {}
1043
+ };
1044
+ }
1045
+ var MAX_ERROR_CLASS_BYTES = 256;
1046
+ function errorClassOf(error) {
1047
+ const name = safe(() => error?.constructor?.name);
1048
+ if (typeof name !== "string" || name.length === 0)
1049
+ return null;
1050
+ return sliceToBytes(name, MAX_ERROR_CLASS_BYTES);
1051
+ }
1052
+ var MAX_ERROR_SIGNATURE_BYTES = 512;
1053
+ var DIGIT_RUN = /\d+/g;
1054
+ var QUOTED_LITERAL = /"[^\n]*"|`[^\n]*`|(^|[^A-Za-z])'[^\n]*'/g;
1055
+ var LONG_RUN = /[A-Za-z0-9_+/-]{16,}={0,2}/g;
1056
+ function isOpaque(run) {
1057
+ return /\d/.test(run) || uppercaseHeavy(run);
1058
+ }
1059
+ var EMAIL = /(^|\s)[^\s@]*@[^\s]*\.[A-Za-z]{2,24}[^\s]*/g;
1060
+ var CUT_CANDIDATE = /("[^"\n]*"|`[^`\n]*`|(?:^|[^A-Za-z])'[^'\n]*')|"[^"\n]*$|`[^`\n]*$|(^|[^A-Za-z])'[^'\n]*$|(^|\s)[^\s@]*@[^\s]*$/g;
1061
+ var URL_RUN = /[A-Za-z][A-Za-z0-9+.-]{0,15}:\/\/\S*/g;
1062
+ var PATH_RUN = /(?:\/[A-Za-z0-9._~%+-]+){2,}(?:[?#]\S*)?/g;
1063
+ function errorSignature(error) {
1064
+ if (error === undefined || error === null)
1065
+ return;
1066
+ const full = errorMessage(error);
1067
+ if (full.length === 0)
1068
+ return;
1069
+ const truncated = full.length > MAX_INPUT_CHARS;
1070
+ const sliced = truncated ? full.slice(0, MAX_INPUT_CHARS) : full;
1071
+ const closed = truncated ? sliced.replace(CUT_CANDIDATE, (match, closedLiteral, quote, email) => closedLiteral !== undefined ? match : `${quote ?? email ?? ""}*`) : sliced;
1072
+ const templated = closed.replace(QUOTED_LITERAL, (_match, before) => `${before ?? ""}*`).replace(URL_RUN, "*").replace(LONG_RUN, (run) => isOpaque(run) ? "*" : run).replace(EMAIL, "$1*").replace(PATH_RUN, "*").replace(DIGIT_RUN, "*");
1073
+ const capped = sliceToBytes(templated, MAX_ERROR_SIGNATURE_BYTES);
1074
+ return capped.length === 0 ? undefined : capped;
1075
+ }
1076
+ function browserBrand(scope) {
1077
+ const uaData = safe(() => scope.navigator?.userAgentData);
1078
+ return safe(() => brandInfo(uaData?.brands)) ?? {};
1079
+ }
1080
+ function pathname(scope) {
1081
+ return safe(() => scope.location?.pathname);
1082
+ }
1083
+ function pageContext(scope) {
1084
+ const g = scope;
1085
+ const path = pathname(scope);
1086
+ const template = routeTemplate(path);
1087
+ const segments = segmentCount(path);
1088
+ const frame = frameContext(scope);
1089
+ const state = visibility(scope);
1090
+ return {
1091
+ ...template !== undefined ? { routeTemplate: template } : {},
1092
+ ...segments !== undefined ? { segmentCount: segments } : {},
1093
+ referrerClass: referrerClass(safe(() => g.document?.referrer), safe(() => g.location?.origin)),
1094
+ ...frame !== undefined ? { frameContext: frame } : {},
1095
+ ...state !== undefined ? { visibility: state } : {}
1096
+ };
1097
+ }
1098
+ function clientContext(scope) {
1099
+ const factor = formFactor(scope);
1100
+ const language = languageSubtag(scope);
1101
+ return {
1102
+ agentRuntime: agentRuntime(scope),
1103
+ ...browserBrand(scope),
1104
+ ...factor !== undefined ? { formFactor: factor } : {},
1105
+ ...language !== undefined ? { language } : {}
1106
+ };
1107
+ }
1108
+ function buildInitEvent(params = {}) {
1109
+ const scope = params.scope ?? globalThis;
1110
+ const timeToInitMs = initAt();
1111
+ return {
1112
+ schema: TELEMETRY_SCHEMA_VERSION,
1113
+ event: "sdk_init",
1114
+ ts: new Date().toISOString(),
1115
+ sessionId: sessionId(),
1116
+ sdk: { name: SDK_NAME, version: SDK_VERSION, installMode: SDK_INSTALL_MODE },
1117
+ surface: surfaceInfo(scope),
1118
+ page: pageContext(scope),
1119
+ client: clientContext(scope),
1120
+ ...timeToInitMs !== undefined ? { timeToInitMs } : {}
1121
+ };
1122
+ }
1123
+ function configFields(tracking) {
1124
+ const { toBackend, toOtel } = trackingOutputs(tracking);
1125
+ return {
1126
+ trackingEnabled: toBackend,
1127
+ otelEnabled: toOtel,
1128
+ customEndpoint: Boolean(safe(() => tracking?.endpoint))
1129
+ };
1130
+ }
1131
+ var MAX_TOOL_FIELD_BYTES = 4 * 1024;
1132
+ function toolString(value) {
1133
+ return sliceToBytes(value, MAX_TOOL_FIELD_BYTES);
1134
+ }
1135
+ function toolEnum(allowed, value) {
1136
+ return allowed.find((candidate) => candidate === value);
1137
+ }
1138
+ var ANNOTATION_HINTS = ["readOnlyHint", "untrustedContentHint"];
1139
+ function annotationHints(annotations) {
1140
+ const hints = {};
1141
+ for (const hint of ANNOTATION_HINTS) {
1142
+ const value = safe(() => annotations?.[hint]);
1143
+ if (typeof value === "boolean")
1144
+ hints[hint] = value;
1145
+ }
1146
+ return Object.keys(hints).length > 0 ? hints : undefined;
1147
+ }
1148
+ function toolEntry(entry) {
1149
+ const { tool } = entry;
1150
+ const inputSchema = safe(() => tool.inputSchema);
1151
+ const hash = schemaHash(inputSchema);
1152
+ const hints = annotationHints(safe(() => tool.annotations));
1153
+ const source = toolEnum(TOOL_SOURCES, tool.source);
1154
+ const intent = toolEnum(TOOL_INTENTS, tool.intent);
1155
+ const signature = entry.outcome === "failed" ? errorSignature(entry.error) : undefined;
1156
+ return {
1157
+ name: toolString(tool.name),
1158
+ stableKey: toolString(tool.stableKey),
1159
+ ...tool.version !== undefined ? { version: toolString(tool.version) } : {},
1160
+ ...hash !== undefined ? { schemaHash: hash } : {},
1161
+ ...source !== undefined ? { source } : {},
1162
+ ...intent !== undefined ? { intent } : {},
1163
+ outcome: entry.outcome,
1164
+ ...signature !== undefined ? { failureSignature: signature } : {},
1165
+ ...shapeMetrics(inputSchema) ?? {},
1166
+ descriptionLength: typeof tool.description === "string" ? tool.description.length : 0,
1167
+ ...hints !== undefined ? { annotations: hints } : {}
1168
+ };
1169
+ }
1170
+ var registrationCount = 0;
1171
+ var lastRegistrationRoute;
1172
+ function registrationTrigger(route) {
1173
+ if (registrationCount === 0)
1174
+ return "initial";
1175
+ return route === lastRegistrationRoute ? "re_register" : "spa_navigation";
1176
+ }
1177
+ function nextRegistration(scope = globalThis) {
1178
+ const route = routeTemplate(pathname(scope));
1179
+ const trigger = registrationTrigger(route);
1180
+ registrationCount += 1;
1181
+ lastRegistrationRoute = route;
1182
+ return { registrationIndex: registrationCount, trigger };
1183
+ }
1184
+ function buildToolRegistrationEvent(params) {
1185
+ const scope = params.scope ?? globalThis;
1186
+ const template = routeTemplate(pathname(scope));
1187
+ const sinceInit = timeSinceInit(scope);
1188
+ return {
1189
+ schema: TELEMETRY_SCHEMA_VERSION,
1190
+ event: "tool_registration",
1191
+ ts: new Date().toISOString(),
1192
+ sessionId: sessionId(),
1193
+ registrationIndex: params.registrationIndex,
1194
+ trigger: params.trigger,
1195
+ ...template !== undefined ? { routeTemplate: template } : {},
1196
+ ...sinceInit !== undefined ? { timeSinceInitMs: sinceInit } : {},
1197
+ settleMs: params.settleMs,
1198
+ config: configFields(params.tracking),
1199
+ tools: params.tools.map(toolEntry)
1200
+ };
1201
+ }
1202
+ var callCount = 0;
1203
+ var toolCallCounts = new Map;
1204
+ var lastCalledTool;
1205
+ var lastCalledScope;
1206
+ var MAX_TRACKED_TOOLS = 512;
1207
+ function nextCall(stableKey, tenantScope) {
1208
+ callCount += 1;
1209
+ if (toolCallCounts.size >= MAX_TRACKED_TOOLS && !toolCallCounts.has(stableKey)) {
1210
+ toolCallCounts.clear();
1211
+ }
1212
+ const toolCallIndex = (toolCallCounts.get(stableKey) ?? 0) + 1;
1213
+ toolCallCounts.set(stableKey, toolCallIndex);
1214
+ const precededBy = tenantScope === lastCalledScope ? lastCalledTool : undefined;
1215
+ lastCalledTool = stableKey;
1216
+ lastCalledScope = tenantScope;
1217
+ return {
1218
+ callId: randomId(),
1219
+ callIndex: callCount,
1220
+ toolCallIndex,
1221
+ ...precededBy !== undefined ? { precededBy } : {}
1222
+ };
1223
+ }
1224
+ var MAX_CONTENT_BLOCKS = 4096;
1225
+ function responseMetrics(response) {
1226
+ const content = safe(() => response?.content);
1227
+ const blocks = Array.isArray(content) ? content.length : 0;
1228
+ return {
1229
+ bytes: safe(() => serializedBytes(response)) ?? 0,
1230
+ contentBlocks: Math.max(0, Math.min(blocks, MAX_CONTENT_BLOCKS)),
1231
+ isError: safe(() => response?.isError) === true
1232
+ };
1233
+ }
1234
+ function buildToolCallEvent(params) {
1235
+ const scope = params.scope ?? globalThis;
1236
+ const failed = params.outcome === "error";
1237
+ const hash = schemaHash(safe(() => params.tool.inputSchema));
1238
+ const sinceInit = timeSinceInit(scope);
1239
+ const template = routeTemplate(pathname(scope));
1240
+ const signature = failed ? errorSignature(params.error) : undefined;
1241
+ const intent = toolEnum(TOOL_INTENTS, params.tool.intent);
1242
+ return {
1243
+ schema: TELEMETRY_SCHEMA_VERSION,
1244
+ event: "tool_call",
1245
+ ts: new Date().toISOString(),
1246
+ sessionId: sessionId(),
1247
+ callId: params.callId,
1248
+ callIndex: params.callIndex,
1249
+ toolCallIndex: params.toolCallIndex,
1250
+ ...params.precededBy !== undefined ? { precededBy: toolString(params.precededBy) } : {},
1251
+ ...template !== undefined ? { routeTemplate: template } : {},
1252
+ agentRuntime: agentRuntime(scope),
1253
+ ...sinceInit !== undefined ? { timeSinceInitMs: sinceInit } : {},
1254
+ tool: {
1255
+ stableKey: toolString(params.tool.stableKey),
1256
+ ...hash !== undefined ? { schemaHash: hash } : {},
1257
+ ...intent !== undefined ? { intent } : {}
1258
+ },
1259
+ outcome: params.outcome,
1260
+ durationMs: params.durationMs,
1261
+ ...!failed && params.response !== undefined ? { response: responseMetrics(params.response) } : {},
1262
+ errorClass: failed ? errorClassOf(params.error) : null,
1263
+ ...signature !== undefined ? { errorSignature: signature } : {}
1264
+ };
1265
+ }
1266
+ function pruneToolEntry(entry, disabled) {
1267
+ if (!isFieldParent(entry))
1268
+ return entry;
1269
+ let remaining;
1270
+ for (const key of disabled) {
1271
+ if (!(key in entry))
1272
+ continue;
1273
+ remaining ??= { ...entry };
1274
+ delete remaining[key];
1275
+ }
1276
+ return remaining ?? entry;
1277
+ }
1278
+ function applyPruned(event, key, value) {
1279
+ if (value === undefined)
1280
+ delete event[key];
1281
+ else
1282
+ event[key] = value;
1283
+ }
1284
+ function isEmptied(value) {
1285
+ return isFieldParent(value) && Object.keys(value).length === 0;
1286
+ }
1287
+ function pruneToolFields(event, toolFields) {
1288
+ const disabled = Object.entries(toolFields).filter(([, collected]) => !collected).map(([field]) => field);
1289
+ if (disabled.length === 0)
1290
+ return;
1291
+ const tools = event.tools;
1292
+ if (Array.isArray(tools)) {
1293
+ const entries = [];
1294
+ let changed = false;
1295
+ for (const entry of tools) {
1296
+ const next = pruneToolEntry(entry, disabled);
1297
+ if (next !== entry)
1298
+ changed = true;
1299
+ if (isEmptied(next))
1300
+ continue;
1301
+ entries.push(next);
1302
+ }
1303
+ if (changed) {
1304
+ applyPruned(event, "tools", tools.length > 0 && entries.length === 0 ? undefined : entries);
1305
+ }
1306
+ }
1307
+ const tool = event.tool;
1308
+ if (isFieldParent(tool)) {
1309
+ const next = pruneToolEntry(tool, disabled);
1310
+ if (next !== tool)
1311
+ applyPruned(event, "tool", isEmptied(next) ? undefined : next);
1312
+ }
1313
+ }
1314
+ function pruneByAllowlist(event, fields = TELEMETRY_FIELDS, toolFields = TELEMETRY_TOOL_FIELDS) {
1315
+ const pruned = { ...event };
1316
+ for (const [field, collected] of Object.entries(fields)) {
1317
+ if (collected)
1318
+ continue;
1319
+ const dot = field.indexOf(".");
1320
+ if (dot === -1) {
1321
+ delete pruned[field];
1322
+ continue;
1323
+ }
1324
+ const parentKey = field.slice(0, dot);
1325
+ const childKey = field.slice(dot + 1);
1326
+ const parent = pruned[parentKey];
1327
+ if (!isFieldParent(parent) || !(childKey in parent))
1328
+ continue;
1329
+ const remaining = { ...parent };
1330
+ delete remaining[childKey];
1331
+ applyPruned(pruned, parentKey, isEmptied(remaining) ? undefined : remaining);
1332
+ }
1333
+ pruneToolFields(pruned, toolFields);
1334
+ return pruned;
1335
+ }
1336
+ var defaultSinks2 = {
1337
+ sendTelemetry: (event) => sendTelemetry(event, globalThis, undefined, telemetryApiKey())
1338
+ };
1339
+ function batchTelemetrySinks(apiKey) {
1340
+ const own = batchApiKey(apiKey);
1341
+ return {
1342
+ sendTelemetry: (event) => sendTelemetry(event, globalThis, undefined, own ?? telemetryApiKey())
1343
+ };
1344
+ }
1345
+ function batchApiKey(apiKey) {
1346
+ return typeof apiKey === "string" && apiKey.trim().length > 0 ? apiKey : undefined;
1347
+ }
1348
+ function resolveTelemetryKey(apiKey) {
1349
+ return batchApiKey(apiKey) ?? telemetryApiKey();
1350
+ }
1351
+ function telemetryTenantScope(apiKey) {
1352
+ const resolved = resolveTelemetryKey(apiKey);
1353
+ return resolved === undefined ? "" : fnv1a(resolved);
1354
+ }
1355
+ function emitTelemetry(build, sinks = defaultSinks2, fields = TELEMETRY_FIELDS, toolFields = TELEMETRY_TOOL_FIELDS) {
1356
+ try {
1357
+ if (!telemetryEnabled())
1358
+ return;
1359
+ sinks.sendTelemetry(boundEventPayload(pruneByAllowlist({ ...build() }, fields, toolFields)));
1360
+ } catch {}
1361
+ }
1362
+ var initCancelled = false;
1363
+ var initFlushed = false;
1364
+ var capturedApiKey;
1365
+ function captureTelemetryApiKey(apiKey) {
1366
+ if (typeof apiKey === "string" && apiKey.trim().length > 0)
1367
+ capturedApiKey = apiKey;
1368
+ }
1369
+ function telemetryApiKey() {
1370
+ return capturedApiKey;
1371
+ }
1372
+ function cancelInitEvent() {
1373
+ initCancelled = true;
1374
+ }
1375
+ function flushInitEvent(sinks) {
1376
+ if (initFlushed)
1377
+ return;
1378
+ initFlushed = true;
1379
+ if (initCancelled)
1380
+ return;
1381
+ emitTelemetry(() => buildInitEvent(), sinks);
1382
+ }
1383
+ function afterDelay(run, ms) {
1384
+ const schedule = safe(() => globalThis.setTimeout);
1385
+ if (typeof schedule !== "function")
1386
+ return () => {};
1387
+ const handle = safe(() => schedule.call(globalThis, () => run(), ms));
1388
+ return () => {
1389
+ const clear = safe(() => globalThis.clearTimeout);
1390
+ if (typeof clear !== "function")
1391
+ return;
1392
+ safe(() => clear.call(globalThis, handle));
1393
+ };
1394
+ }
1395
+ afterDelay(() => flushInitEvent(), 0);
1396
+
1397
+ // src/register.ts
1398
+ function isContentResult(value) {
1399
+ return typeof value === "object" && value !== null && Array.isArray(value.content);
1400
+ }
1401
+ function normalizeResult(value) {
1402
+ if (isContentResult(value))
1403
+ return value;
1404
+ const text = typeof value === "string" ? value : JSON.stringify(value ?? null);
1405
+ return { content: [{ type: "text", text }] };
1406
+ }
1407
+ function elapsedMs(end, start) {
1408
+ if (typeof end !== "number" || typeof start !== "number")
1409
+ return 0;
1410
+ const elapsed = end - start;
1411
+ return Number.isFinite(elapsed) && elapsed > 0 ? Math.round(elapsed) : 0;
1412
+ }
1413
+ function clock() {
1414
+ const perf = safe(() => globalThis.performance);
1415
+ const now = safe(() => perf?.now);
1416
+ if (typeof now === "function") {
1417
+ const read2 = () => safe(() => now.call(perf));
1418
+ const start2 = read2();
1419
+ if (start2 !== undefined)
1420
+ return () => elapsedMs(read2(), start2);
1421
+ }
1422
+ const read = () => safe(() => Date.now());
1423
+ const start = read();
1424
+ return () => elapsedMs(read(), start);
1425
+ }
1426
+ function trackerFor(tool, tracking) {
1427
+ if (!tracking)
1428
+ return;
1429
+ const { toBackend, toOtel } = trackingOutputs(tracking);
1430
+ if (!toBackend && !toOtel)
1431
+ return;
1432
+ const shared = {
1433
+ toolStableKey: tool.stableKey,
1434
+ toolName: tool.name,
1435
+ callId: randomId(),
1436
+ ...tool.version !== undefined ? { toolVersion: tool.version } : {}
1437
+ };
1438
+ return (eventName, data) => track(tracking, eventName, { ...shared, ...data });
1439
+ }
1440
+ function toSpecTool(tool, channels) {
1441
+ const { tracking, telemetry, telemetrySinks, telemetryKey } = channels;
1442
+ return {
1443
+ name: tool.name,
1444
+ ...tool.title !== undefined ? { title: tool.title } : {},
1445
+ description: tool.description,
1446
+ ...tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {},
1447
+ ...tool.annotations !== undefined ? { annotations: tool.annotations } : {},
1448
+ async execute(input) {
1449
+ const trackCall = trackerFor(tool, tracking);
1450
+ if (!trackCall && !telemetry)
1451
+ return normalizeResult(await tool.execute(input));
1452
+ const callKey = telemetry ? resolveTelemetryKey(telemetryKey) : undefined;
1453
+ const sequence = telemetry ? nextCall(tool.stableKey, telemetryTenantScope(callKey)) : undefined;
1454
+ const callSinks = sequence ? batchTelemetrySinks(callKey) : telemetrySinks;
1455
+ const elapsed = clock();
1456
+ trackCall?.("tool_call_request", { input });
1457
+ try {
1458
+ const response = normalizeResult(await tool.execute(input));
1459
+ const durationMs = elapsed();
1460
+ trackCall?.("tool_call_response", { response, duration_ms: durationMs });
1461
+ if (sequence) {
1462
+ emitTelemetry(() => buildToolCallEvent({ ...sequence, tool, outcome: "success", durationMs, response }), callSinks);
1463
+ }
1464
+ return response;
1465
+ } catch (error) {
1466
+ const durationMs = elapsed();
1467
+ trackCall?.("tool_call_response", {
1468
+ error: errorMessage(error),
1469
+ duration_ms: durationMs
1470
+ });
1471
+ if (sequence) {
1472
+ emitTelemetry(() => buildToolCallEvent({ ...sequence, tool, outcome: "error", durationMs, error }), callSinks);
1473
+ }
1474
+ throw error;
1475
+ }
1476
+ }
1477
+ };
1478
+ }
1479
+ var REGISTRATION_TIMEOUT_MS = 2000;
1480
+ var PAGEHIDE = "pagehide";
1481
+ function onPagehide(run) {
1482
+ const g = globalThis;
1483
+ const add = safe(() => g.addEventListener);
1484
+ const remove = safe(() => g.removeEventListener);
1485
+ if (typeof add !== "function" || typeof remove !== "function")
1486
+ return () => {};
1487
+ const listener = () => run();
1488
+ safe(() => add.call(g, PAGEHIDE, listener, { once: true }));
1489
+ return () => {
1490
+ safe(() => remove.call(g, PAGEHIDE, listener));
1491
+ };
1492
+ }
1493
+ function watchRegistration(tools, tracking, sinks) {
1494
+ const entries = tools.map((tool) => ({ tool, outcome: "pending" }));
1495
+ const elapsed = clock();
1496
+ const { registrationIndex, trigger } = nextRegistration();
1497
+ let emitted = false;
1498
+ const release = [];
1499
+ const emit = () => {
1500
+ if (emitted)
1501
+ return;
1502
+ emitted = true;
1503
+ for (const stop of release)
1504
+ stop();
1505
+ const settleMs = elapsed();
1506
+ emitTelemetry(() => buildToolRegistrationEvent({
1507
+ registrationIndex,
1508
+ trigger,
1509
+ settleMs,
1510
+ tools: entries,
1511
+ tracking
1512
+ }), sinks);
1513
+ };
1514
+ release.push(afterDelay(emit, REGISTRATION_TIMEOUT_MS), onPagehide(emit));
1515
+ return {
1516
+ record(index, result) {
1517
+ const entry = entries[index];
1518
+ if (!entry)
1519
+ return;
1520
+ entry.outcome = result.state;
1521
+ if (result.error !== undefined)
1522
+ entry.error = result.error;
1523
+ },
1524
+ emit
1525
+ };
1526
+ }
1527
+ function assertUniqueIdentities(tools) {
1528
+ const names = new Set;
1529
+ const keys = new Set;
1530
+ for (const tool of tools) {
1531
+ if (names.has(tool.name)) {
1532
+ throw new TypeError(`registerTools: duplicate tool name "${tool.name}"`);
1533
+ }
1534
+ if (keys.has(tool.stableKey)) {
1535
+ throw new TypeError(`registerTools: duplicate stableKey "${tool.stableKey}"`);
1536
+ }
1537
+ names.add(tool.name);
1538
+ keys.add(tool.stableKey);
1539
+ }
1540
+ }
1541
+ function registerTools(tools, options = {}) {
1542
+ const telemetryOption = safe(() => ({ value: options.telemetry }));
1543
+ const tracking = safe(() => options.tracking);
1544
+ const telemetryLive = telemetryOption !== undefined && telemetryEnabled(telemetryOption.value);
1545
+ const apiKey = telemetryLive ? safe(() => tracking?.apiKey) : undefined;
1546
+ const channels = {
1547
+ tracking,
1548
+ telemetry: telemetryLive,
1549
+ ...telemetryLive ? { telemetrySinks: batchTelemetrySinks(apiKey), telemetryKey: apiKey } : {}
1550
+ };
1551
+ if (telemetryOption?.value === false)
1552
+ cancelInitEvent();
1553
+ if (telemetryLive)
1554
+ captureTelemetryApiKey(apiKey);
1555
+ assertUniqueIdentities(tools);
1556
+ const controller = new AbortController;
1557
+ const external = options.signal;
1558
+ if (external) {
1559
+ if (external.aborted)
1560
+ controller.abort(external.reason);
1561
+ else
1562
+ external.addEventListener("abort", () => controller.abort(external.reason), { once: true });
1563
+ }
1564
+ const modelContext = options.modelContext ?? resolveModelContext();
1565
+ const result = (tool, state, error) => ({
1566
+ stableKey: tool.stableKey,
1567
+ name: tool.name,
1568
+ state,
1569
+ ...error !== undefined ? { error } : {}
1570
+ });
1571
+ const watch = channels.telemetry ? watchRegistration(tools, channels.tracking, channels.telemetrySinks) : undefined;
1572
+ const settle = async (tool) => {
1573
+ if (!modelContext)
1574
+ return result(tool, "unsupported");
1575
+ if (controller.signal.aborted)
1576
+ return result(tool, "aborted");
1577
+ try {
1578
+ await Promise.resolve(modelContext.registerTool(toSpecTool(tool, channels), {
1579
+ signal: controller.signal
1580
+ }));
1581
+ return result(tool, "registered");
1582
+ } catch (error) {
1583
+ if (controller.signal.aborted)
1584
+ return result(tool, "aborted");
1585
+ return result(tool, "failed", error);
1586
+ }
1587
+ };
1588
+ const ready = Promise.all(tools.map(async (tool, index) => {
1589
+ const settled = await settle(tool);
1590
+ watch?.record(index, settled);
1591
+ return settled;
1592
+ }));
1593
+ if (watch)
1594
+ ready.then(() => watch.emit());
1595
+ return {
1596
+ ready,
1597
+ signal: controller.signal,
1598
+ unregister() {
1599
+ controller.abort();
1600
+ }
1601
+ };
1602
+ }
1603
+ export {
1604
+ resolveModelContext,
1605
+ registerTools,
1606
+ defineTool
1607
+ };