@kortyx/usage-telemetry 0.0.0-stage → 0.2.0

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/node.js ADDED
@@ -0,0 +1,851 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/node.ts
21
+ var node_exports = {};
22
+ __export(node_exports, {
23
+ configureUsageTelemetry: () => configureUsageTelemetry,
24
+ createNodeUsageTelemetry: () => createNodeUsageTelemetry,
25
+ createUsageTelemetry: () => createUsageTelemetry,
26
+ getUsageTelemetry: () => getUsageTelemetry,
27
+ publicUsageConfiguration: () => publicUsageConfiguration,
28
+ scale: () => scale,
29
+ usageDisabled: () => usageDisabled
30
+ });
31
+ module.exports = __toCommonJS(node_exports);
32
+ var import_node_crypto = require("crypto");
33
+ var import_promises = require("fs/promises");
34
+ var import_node_os = require("os");
35
+ var import_node_path = require("path");
36
+
37
+ // src/catalog.ts
38
+ var operations = [
39
+ "sdk.initialize",
40
+ "sdk.registry",
41
+ "sdk.execute",
42
+ "sdk.resume",
43
+ "sdk.chat.admission",
44
+ "sdk.execution",
45
+ "sdk.response",
46
+ "sdk.model",
47
+ "sdk.tool",
48
+ "sdk.node",
49
+ "sdk.parallel",
50
+ "sdk.child",
51
+ "sdk.structured",
52
+ "sdk.complete_response",
53
+ "sdk.prompt.resolve",
54
+ "sdk.prompt.refresh",
55
+ "sdk.eval",
56
+ "sdk.mcp.connect",
57
+ "sdk.mcp.discover",
58
+ "sdk.mcp.close",
59
+ "sdk.checkpoint.read",
60
+ "sdk.checkpoint.list",
61
+ "sdk.checkpoint.fork",
62
+ "sdk.checkpoint.rollback",
63
+ "sdk.export",
64
+ "studio.setup",
65
+ "studio.connection",
66
+ "studio.read",
67
+ "studio.render",
68
+ "studio.navigation",
69
+ "studio.inspect",
70
+ "studio.filter",
71
+ "studio.saved_view",
72
+ "studio.live",
73
+ "studio.prompt",
74
+ "studio.eval",
75
+ "studio.review",
76
+ "studio.settings",
77
+ "studio.update",
78
+ "studio.ingest",
79
+ "cli.topology",
80
+ "cli.eval"
81
+ ];
82
+ var outcomes = [
83
+ "success",
84
+ "failed",
85
+ "cancelled",
86
+ "suspended",
87
+ "denied",
88
+ "limit_reached",
89
+ "quality_failed",
90
+ "ungraded"
91
+ ];
92
+ var publicCatalog = {
93
+ products: ["sdk", "oss_studio"],
94
+ operations,
95
+ properties: {
96
+ phase: [
97
+ "admission",
98
+ "execution",
99
+ "delivery",
100
+ "parse",
101
+ "network",
102
+ "http",
103
+ "configuration",
104
+ "preflight",
105
+ "state",
106
+ "pull",
107
+ "ports",
108
+ "compose",
109
+ "rollback",
110
+ "backup",
111
+ "migration",
112
+ "health"
113
+ ],
114
+ source: [
115
+ "memory",
116
+ "file",
117
+ "custom",
118
+ "local",
119
+ "studio",
120
+ "cache",
121
+ "fallback",
122
+ "eval",
123
+ "mcp",
124
+ "native"
125
+ ],
126
+ storage: ["in-memory", "redis", "postgres", "custom"],
127
+ calling_mode: ["direct", "model"],
128
+ provider: [
129
+ "openai",
130
+ "anthropic",
131
+ "google",
132
+ "mistral",
133
+ "groq",
134
+ "deepseek",
135
+ "openrouter"
136
+ ],
137
+ transport: ["stdio", "sse", "http", "custom"],
138
+ format: [
139
+ "text",
140
+ "json",
141
+ "json_schema",
142
+ "set",
143
+ "append",
144
+ "text-delta",
145
+ "final"
146
+ ],
147
+ resource: [
148
+ "runs",
149
+ "sessions",
150
+ "interrupts",
151
+ "workflows",
152
+ "prompts",
153
+ "evals",
154
+ "diagnostics",
155
+ "settings",
156
+ "home"
157
+ ],
158
+ surface: [
159
+ "runs",
160
+ "sessions",
161
+ "interrupts",
162
+ "workflows",
163
+ "prompts",
164
+ "evals",
165
+ "settings",
166
+ "other"
167
+ ],
168
+ action: [
169
+ "view",
170
+ "select",
171
+ "retry",
172
+ "first_use",
173
+ "observed",
174
+ "copy",
175
+ "create",
176
+ "draft",
177
+ "save",
178
+ "discard-draft",
179
+ "update",
180
+ "promote",
181
+ "review",
182
+ "policy",
183
+ "tag-set",
184
+ "tag-remove",
185
+ "bulk-update",
186
+ "category-create",
187
+ "category-update",
188
+ "category-delete",
189
+ "group-create",
190
+ "group-update",
191
+ "group-delete",
192
+ "import",
193
+ "export",
194
+ "launch",
195
+ "cancel",
196
+ "compare",
197
+ "inspect",
198
+ "clear",
199
+ "apply",
200
+ "delete",
201
+ "enable",
202
+ "disable",
203
+ "manual_refresh",
204
+ "fallback_refresh",
205
+ "refresh",
206
+ "change"
207
+ ],
208
+ tab: [
209
+ "overview",
210
+ "trace",
211
+ "events",
212
+ "model-io",
213
+ "calls",
214
+ "feedback",
215
+ "content",
216
+ "versions",
217
+ "reviews",
218
+ "usage",
219
+ "diff",
220
+ "cases",
221
+ "results",
222
+ "definition"
223
+ ],
224
+ origin: ["foreground", "background", "server", "browser", "cli"],
225
+ status: [
226
+ "empty",
227
+ "nonempty",
228
+ "off",
229
+ "connecting",
230
+ "live",
231
+ "reconnecting",
232
+ "paused",
233
+ "running",
234
+ "completed",
235
+ "failed",
236
+ "interrupted",
237
+ "pending",
238
+ "expired"
239
+ ],
240
+ filter: [
241
+ "search",
242
+ "status",
243
+ "provider",
244
+ "model",
245
+ "environment",
246
+ "version",
247
+ "duration",
248
+ "cost",
249
+ "tokens",
250
+ "time",
251
+ "sort",
252
+ "other"
253
+ ],
254
+ scale: ["1", "2-5", "6-20", "21-100", "101+"],
255
+ error_category: [
256
+ "configuration",
257
+ "provider",
258
+ "validation",
259
+ "domain",
260
+ "workflow",
261
+ "persistence",
262
+ "request",
263
+ "transport",
264
+ "internal",
265
+ "unknown"
266
+ ],
267
+ http_status: [
268
+ "400",
269
+ "401",
270
+ "403",
271
+ "404",
272
+ "409",
273
+ "413",
274
+ "422",
275
+ "429",
276
+ "500",
277
+ "502",
278
+ "503",
279
+ "504"
280
+ ],
281
+ prompts: "boolean",
282
+ observability: "boolean",
283
+ tools: "boolean",
284
+ streaming: "boolean",
285
+ requested_streaming: "boolean",
286
+ executed: "boolean",
287
+ cache: "boolean",
288
+ reused: "boolean",
289
+ continue_on_disconnect: "boolean",
290
+ write_available: "boolean"
291
+ },
292
+ // Built-in contracts only; application codes and provider response text are excluded.
293
+ errorCodes: [
294
+ "EXECUTION_FAILED",
295
+ "INVALID_REQUEST",
296
+ "INVALID_RESUME",
297
+ "INVALID_RESPONSE",
298
+ "INVALID_SESSION",
299
+ "RESUME_MISMATCH",
300
+ "RESUME_FAILED",
301
+ "CONTRACT_MISMATCH",
302
+ "INVALID_INPUT",
303
+ "INVALID_OUTPUT",
304
+ "MISSING_CONTRACT",
305
+ "UNKNOWN_WORKFLOW",
306
+ "DOMAIN_ERROR",
307
+ "PERSISTENCE_ERROR",
308
+ "WORKFLOW_CONTRACT_ERROR",
309
+ "PROVIDER_REFUSAL",
310
+ "PROVIDER_HTTP_ERROR",
311
+ "PROVIDER_INVALID_RESPONSE",
312
+ "PROVIDER_TRANSPORT_ERROR",
313
+ "PROVIDER_REQUEST_FAILED",
314
+ "INVALID_MODEL_JSON",
315
+ "MODEL_OUTPUT_SCHEMA",
316
+ "OUTPUT_TRUNCATED",
317
+ "SCHEMA_VALIDATION",
318
+ "PARALLEL_FAILED",
319
+ "EVAL_TIMEOUT",
320
+ "EVAL_HOOK_FAILED",
321
+ "EVAL_INTERRUPT_CLEANUP_FAILED",
322
+ "PROMPT_SOURCE_NOT_CONFIGURED",
323
+ "PROMPT_REASON_CONFLICT",
324
+ "PROMPT_REASON_INVALID",
325
+ "REASON_OUTPUT_BUDGET_EXHAUSTED",
326
+ "REASON_OUTPUT_CONTRACT_CORRECTION_EXHAUSTED",
327
+ "PROMPT_CACHE_INVALID",
328
+ "PROMPT_ENVIRONMENT_MISMATCH",
329
+ "PROMPT_EVAL_PIN_CONFLICT",
330
+ "PROMPT_EVAL_SNAPSHOT_INVALID",
331
+ "PROMPT_NOT_ASSIGNED",
332
+ "PROMPT_NOT_FOUND",
333
+ "PROMPT_NOT_REGISTERED",
334
+ "PROMPT_PIN_MISMATCH",
335
+ "PROMPT_REFERENCE_DUPLICATE",
336
+ "PROMPT_SNAPSHOT_INVALID",
337
+ "PROMPT_SNAPSHOT_LIMIT",
338
+ "PROMPT_SOURCE_HTTP",
339
+ "PROMPT_SOURCE_INVALID",
340
+ "PROMPT_TAG_MISMATCH",
341
+ "PROMPT_CONTRACT_MISMATCH",
342
+ "PROMPT_REFERENCE_INVALID",
343
+ "PROMPT_SCHEMA_INVALID",
344
+ "PROMPT_VARIABLE_CONFIG_OVERLAP",
345
+ "PROMPT_VARIABLE_MISSING",
346
+ "PROMPT_VARIABLE_UNKNOWN",
347
+ "PROMPT_DEPENDENCY_CYCLE",
348
+ "PROMPT_DEPENDENCY_MISMATCH",
349
+ "PROMPT_HASH_MISMATCH",
350
+ "PROMPT_REFERENCE_LIMIT",
351
+ "PROMPT_REFERENCE_MISSING",
352
+ "PROMPT_REFERENCE_ROLE",
353
+ "STUDIO_PROTOCOL_MISMATCH"
354
+ ]
355
+ };
356
+ function scale(value) {
357
+ return value <= 1 ? "1" : value <= 5 ? "2-5" : value <= 20 ? "6-20" : value <= 100 ? "21-100" : "101+";
358
+ }
359
+
360
+ // src/client.ts
361
+ function property(value, key) {
362
+ try {
363
+ if (!value || typeof value !== "object") return void 0;
364
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
365
+ return descriptor && "value" in descriptor ? descriptor.value : void 0;
366
+ } catch {
367
+ return void 0;
368
+ }
369
+ }
370
+ function classifyUsageError(error, catalog = publicCatalog) {
371
+ const name = property(error, "name");
372
+ const code = property(error, "code") ?? property(property(error, "failure"), "code");
373
+ if (name === "AbortError" || code === "EXECUTION_CANCELLED")
374
+ return { outcome: "cancelled", properties: {} };
375
+ if (code === "EXECUTION_LIMIT_REACHED" || name === "ExecutionLimitReached")
376
+ return { outcome: "limit_reached", properties: {} };
377
+ if (name === "GraphInterrupt" || name === "NodeInterrupt" || name === "Interrupt")
378
+ return { outcome: "suspended", properties: {} };
379
+ const known = typeof code === "string" && catalog.errorCodes.includes(code);
380
+ const category = known && code !== "EXECUTION_FAILED" ? property(error, "category") ?? property(property(error, "failure"), "category") ?? "unknown" : "unknown";
381
+ const status = property(error, "status") ?? property(property(error, "failure"), "status");
382
+ return {
383
+ outcome: "failed",
384
+ properties: {
385
+ error_code: known ? code : "other",
386
+ error_category: category,
387
+ ...typeof status === "number" ? { http_status: String(status) } : {}
388
+ }
389
+ };
390
+ }
391
+ function sanitizeProperties(input, catalog = publicCatalog) {
392
+ const safe = {};
393
+ for (const [key, rule] of Object.entries(catalog.properties)) {
394
+ const value = property(input, key);
395
+ if (value === void 0) continue;
396
+ if (rule === "boolean") {
397
+ if (typeof value === "boolean") safe[key] = value;
398
+ } else if (typeof value === "string")
399
+ safe[key] = rule.includes(value) ? value : "other";
400
+ }
401
+ const code = property(input, "error_code");
402
+ if (typeof code === "string")
403
+ safe.error_code = catalog.errorCodes.includes(code) ? code : "other";
404
+ return safe;
405
+ }
406
+ var durationBounds = [10, 100, 1e3, 1e4, 6e4];
407
+ var noopOperation = { end() {
408
+ }, fail() {
409
+ } };
410
+ function schedule(callback, ms) {
411
+ const timer = setTimeout(callback, ms);
412
+ timer.unref?.();
413
+ return { cancel: () => clearTimeout(timer) };
414
+ }
415
+ function createUsageTelemetry(options) {
416
+ const catalog = options.catalog ?? publicCatalog;
417
+ const now = options.now ?? Date.now;
418
+ const pending = /* @__PURE__ */ new Map();
419
+ let timer;
420
+ let flight;
421
+ let disposed = false;
422
+ let dropped = 0;
423
+ let delivered = 0;
424
+ let hour = Math.floor(now() / 36e5);
425
+ let sent = 0;
426
+ const active = () => {
427
+ try {
428
+ return !disposed && (options.enabled?.() ?? true);
429
+ } catch {
430
+ return false;
431
+ }
432
+ };
433
+ const clear = () => {
434
+ pending.clear();
435
+ try {
436
+ timer?.cancel();
437
+ } catch {
438
+ }
439
+ timer = void 0;
440
+ };
441
+ const configured = catalog.products.includes(options.context.product) && /^[a-z][a-z0-9_]{0,31}$/.test(options.context.component) && /^(?:\d+\.\d+\.\d+(?:-[a-z0-9.-]+)?|development|unknown)$/.test(
442
+ options.context.version
443
+ ) && options.context.version.length <= 64;
444
+ const flush = async () => {
445
+ if (flight) return flight;
446
+ if (!active() || !configured) {
447
+ clear();
448
+ return;
449
+ }
450
+ try {
451
+ timer?.cancel();
452
+ } catch {
453
+ }
454
+ timer = void 0;
455
+ const records = [...pending.values()];
456
+ pending.clear();
457
+ if (!records.length) return;
458
+ let attempted = records.length;
459
+ flight = (async () => {
460
+ try {
461
+ if (options.debug?.()) {
462
+ options.inspect?.(records);
463
+ return;
464
+ }
465
+ const currentHour = Math.floor(now() / 36e5);
466
+ if (hour !== currentHour) {
467
+ hour = currentHour;
468
+ sent = 0;
469
+ }
470
+ const allowance = Math.max(
471
+ 0,
472
+ (options.maxRecordsPerHour ?? 120) - sent
473
+ );
474
+ const batch = records.slice(0, allowance);
475
+ dropped += records.length - batch.length;
476
+ attempted = batch.length;
477
+ if (!batch.length || !active()) return;
478
+ const identified = await options.identity();
479
+ const identity2 = typeof identified === "string" ? identified : identified.id;
480
+ if (typeof identified !== "string")
481
+ for (const record2 of batch) record2.identity_scope = identified.scope;
482
+ if (!active() || options.debug?.()) return;
483
+ if (!/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(
484
+ identity2
485
+ )) {
486
+ dropped += batch.length;
487
+ return;
488
+ }
489
+ sent += batch.length;
490
+ await options.transport(batch, identity2);
491
+ delivered += batch.length;
492
+ } catch {
493
+ dropped += attempted;
494
+ }
495
+ })().finally(() => {
496
+ flight = void 0;
497
+ try {
498
+ if (pending.size && active() && !timer)
499
+ timer = (options.schedule ?? schedule)(
500
+ () => void flush(),
501
+ options.flushIntervalMs ?? 6e4
502
+ );
503
+ } catch {
504
+ clear();
505
+ }
506
+ });
507
+ return flight;
508
+ };
509
+ const record = (event, operation, outcome, duration, input) => {
510
+ try {
511
+ if (!active()) {
512
+ clear();
513
+ return;
514
+ }
515
+ if (!configured || !catalog.operations.includes(operation) || !outcomes.includes(outcome))
516
+ return;
517
+ const properties = sanitizeProperties(input, catalog);
518
+ const key = JSON.stringify([event, operation, outcome, properties]);
519
+ let existing = pending.get(key);
520
+ if (!existing) {
521
+ if (pending.size >= (options.maxGroups ?? 64)) {
522
+ dropped++;
523
+ return;
524
+ }
525
+ existing = {
526
+ schema_version: 1,
527
+ event_id: crypto.randomUUID(),
528
+ timestamp: new Date(now()).toISOString(),
529
+ event,
530
+ product: options.context.product,
531
+ component: options.context.component,
532
+ version: options.context.version,
533
+ analytics_environment: options.context.environment ?? "production",
534
+ runtime_mode: options.context.runtimeMode ?? "unknown",
535
+ identity_scope: options.context.identityScope ?? "installation",
536
+ operation,
537
+ outcome,
538
+ properties,
539
+ count: 0,
540
+ duration_total_ms: 0,
541
+ duration_buckets: [0, 0, 0, 0, 0, 0]
542
+ };
543
+ pending.set(key, existing);
544
+ }
545
+ const elapsed = Math.max(
546
+ 0,
547
+ Math.min(864e5, Number.isFinite(duration) ? duration : 0)
548
+ );
549
+ existing.count = Math.min(Number.MAX_SAFE_INTEGER, existing.count + 1);
550
+ existing.duration_total_ms = Math.min(
551
+ Number.MAX_SAFE_INTEGER,
552
+ existing.duration_total_ms + elapsed
553
+ );
554
+ const index = durationBounds.findIndex((bound) => elapsed <= bound);
555
+ const bucket = index === -1 ? durationBounds.length : index;
556
+ existing.duration_buckets[bucket] = (existing.duration_buckets[bucket] ?? 0) + 1;
557
+ if (!timer)
558
+ timer = (options.schedule ?? schedule)(
559
+ () => void flush(),
560
+ options.flushIntervalMs ?? 6e4
561
+ );
562
+ } catch {
563
+ }
564
+ };
565
+ const start = (operation, properties = {}) => {
566
+ if (!active()) {
567
+ clear();
568
+ return noopOperation;
569
+ }
570
+ try {
571
+ const initial = sanitizeProperties(properties, catalog);
572
+ const began = now();
573
+ let ended = false;
574
+ return {
575
+ end(outcome = "success", extra = {}) {
576
+ if (ended) return;
577
+ ended = true;
578
+ try {
579
+ record("usage.operation", operation, outcome, now() - began, {
580
+ ...initial,
581
+ ...sanitizeProperties(extra, catalog)
582
+ });
583
+ } catch {
584
+ }
585
+ },
586
+ fail(error, extra = {}) {
587
+ if (ended) return;
588
+ try {
589
+ const failure = classifyUsageError(error, catalog);
590
+ this.end(failure.outcome, {
591
+ ...sanitizeProperties(extra, catalog),
592
+ ...failure.properties
593
+ });
594
+ } catch {
595
+ }
596
+ }
597
+ };
598
+ } catch {
599
+ return noopOperation;
600
+ }
601
+ };
602
+ return {
603
+ start,
604
+ feature(operation, properties = {}) {
605
+ record("usage.feature", operation, "success", 0, properties);
606
+ },
607
+ async measure(operation, callback, properties = {}, outcome) {
608
+ const operationScope = start(operation, properties);
609
+ try {
610
+ const result = await callback();
611
+ let status = "success";
612
+ try {
613
+ status = outcome?.(result) ?? status;
614
+ } catch {
615
+ }
616
+ operationScope.end(status);
617
+ return result;
618
+ } catch (error) {
619
+ operationScope.fail(error);
620
+ throw error;
621
+ }
622
+ },
623
+ flush,
624
+ dispose() {
625
+ disposed = true;
626
+ clear();
627
+ },
628
+ stats: () => ({ pending: pending.size, dropped, delivered })
629
+ };
630
+ }
631
+
632
+ // src/release.ts
633
+ var RELEASE_POSTHOG_KEY = "phc_xdiyTz8ezvRoCWFVefMELsEoULSWnxts6DnTqkaKiE5E";
634
+ var RELEASE_POSTHOG_HOST = "https://eu.i.posthog.com";
635
+
636
+ // src/transport.ts
637
+ function createPostHogTransport(options) {
638
+ if (!/^phc_[a-zA-Z0-9]+$/.test(options.key))
639
+ throw new Error("Usage telemetry requires a public PostHog capture key.");
640
+ const host = new URL(options.host ?? "https://eu.i.posthog.com");
641
+ if (host.username || host.password || host.search || host.hash)
642
+ throw new Error(
643
+ "Usage endpoint must not contain credentials or query parameters."
644
+ );
645
+ if (host.protocol !== "https:" && host.hostname !== "localhost" && host.hostname !== "127.0.0.1")
646
+ throw new Error("Usage telemetry requires HTTPS.");
647
+ return async (records, identity2) => {
648
+ const batch = records.map((record) => ({
649
+ event: record.event,
650
+ timestamp: record.timestamp,
651
+ distinct_id: identity2,
652
+ uuid: record.event_id,
653
+ properties: {
654
+ ...record.properties,
655
+ schema_version: record.schema_version,
656
+ product: record.product,
657
+ component: record.component,
658
+ version: record.version,
659
+ analytics_environment: record.analytics_environment,
660
+ runtime_mode: record.runtime_mode,
661
+ identity_scope: record.identity_scope,
662
+ operation: record.operation,
663
+ outcome: record.outcome,
664
+ count: record.count,
665
+ duration_total_ms: record.duration_total_ms,
666
+ duration_buckets: record.duration_buckets,
667
+ $process_person_profile: false,
668
+ $geoip_disable: true,
669
+ $ip: null
670
+ }
671
+ }));
672
+ const controller = new AbortController();
673
+ const timeout = setTimeout(
674
+ () => controller.abort(),
675
+ options.timeoutMs ?? 2e3
676
+ );
677
+ timeout.unref?.();
678
+ try {
679
+ const response = await (options.fetch ?? globalThis.fetch)(
680
+ new URL("/batch/", host),
681
+ {
682
+ method: "POST",
683
+ credentials: "omit",
684
+ referrerPolicy: "no-referrer",
685
+ headers: { "content-type": "application/json" },
686
+ body: JSON.stringify({ api_key: options.key, batch }),
687
+ signal: controller.signal
688
+ }
689
+ );
690
+ await response.body?.cancel();
691
+ if (!response.ok) throw new Error("Usage delivery failed.");
692
+ } finally {
693
+ clearTimeout(timeout);
694
+ }
695
+ };
696
+ }
697
+ function createCollectorTransport(options) {
698
+ const endpoint = new URL(options.endpoint);
699
+ if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash)
700
+ throw new Error(
701
+ "Usage endpoint must not contain credentials or query parameters."
702
+ );
703
+ if (endpoint.protocol !== "https:" && !["localhost", "127.0.0.1"].includes(endpoint.hostname))
704
+ throw new Error("Usage telemetry requires HTTPS.");
705
+ return async (records, identity2) => {
706
+ const response = await (options.fetch ?? globalThis.fetch)(endpoint, {
707
+ method: "POST",
708
+ credentials: "omit",
709
+ referrerPolicy: "no-referrer",
710
+ headers: { "content-type": "application/json" },
711
+ body: JSON.stringify({ identity: identity2, records }),
712
+ signal: AbortSignal.timeout(2e3)
713
+ });
714
+ await response.body?.cancel();
715
+ if (!response.ok) throw new Error("Usage delivery failed.");
716
+ };
717
+ }
718
+
719
+ // src/node.ts
720
+ var identity;
721
+ async function installationIdentity() {
722
+ const supplied = process.env.KORTYX_USAGE_TELEMETRY_ID;
723
+ if (supplied && /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(
724
+ supplied
725
+ ))
726
+ return { id: supplied, scope: "installation" };
727
+ identity ??= (async () => {
728
+ const directory = (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "kortyx");
729
+ const file = (0, import_node_path.join)(directory, "usage-id");
730
+ try {
731
+ const existing = (await (0, import_promises.readFile)(file, "utf8")).trim();
732
+ if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(
733
+ existing
734
+ ))
735
+ return { id: existing, scope: "installation" };
736
+ } catch {
737
+ }
738
+ const candidate = (0, import_node_crypto.randomUUID)();
739
+ try {
740
+ await (0, import_promises.mkdir)(directory, { recursive: true, mode: 448 });
741
+ const handle = await (0, import_promises.open)(file, "wx", 384);
742
+ try {
743
+ await handle.writeFile(candidate);
744
+ } finally {
745
+ await handle.close();
746
+ }
747
+ return { id: candidate, scope: "installation" };
748
+ } catch {
749
+ try {
750
+ const winner = (await (0, import_promises.readFile)(file, "utf8")).trim();
751
+ if (/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/.test(
752
+ winner
753
+ ))
754
+ return { id: winner, scope: "installation" };
755
+ } catch {
756
+ }
757
+ return { id: candidate, scope: "process" };
758
+ }
759
+ })();
760
+ return identity;
761
+ }
762
+ function usageDisabled() {
763
+ return ["1", "true"].includes(process.env.KORTYX_USAGE_TELEMETRY_DISABLED ?? "") || process.env.NODE_ENV === "test";
764
+ }
765
+ function createNodeUsageTelemetry(options) {
766
+ const env = process.env;
767
+ let transport = options.transport;
768
+ let valid = true;
769
+ if (!transport) {
770
+ try {
771
+ transport = env.KORTYX_USAGE_TELEMETRY_ENDPOINT ? createCollectorTransport({
772
+ endpoint: env.KORTYX_USAGE_TELEMETRY_ENDPOINT
773
+ }) : createPostHogTransport({
774
+ key: env.KORTYX_USAGE_TELEMETRY_POSTHOG_KEY || RELEASE_POSTHOG_KEY,
775
+ host: env.KORTYX_USAGE_TELEMETRY_POSTHOG_HOST || RELEASE_POSTHOG_HOST
776
+ });
777
+ } catch {
778
+ valid = false;
779
+ transport = async () => {
780
+ };
781
+ }
782
+ }
783
+ return createUsageTelemetry({
784
+ ...options,
785
+ transport,
786
+ identity: options.identity ?? installationIdentity,
787
+ enabled: () => valid && !usageDisabled() && (options.enabled?.() ?? true),
788
+ debug: () => env.KORTYX_USAGE_TELEMETRY_DEBUG === "1" || (options.debug?.() ?? false),
789
+ inspect: options.inspect ?? ((records) => console.error("[kortyx:usage]", JSON.stringify(records)))
790
+ });
791
+ }
792
+ var clients = /* @__PURE__ */ new Map();
793
+ function configureUsageTelemetry(component, client) {
794
+ if (clients.get(component) === client) return;
795
+ clients.get(component)?.dispose();
796
+ clients.set(component, client);
797
+ }
798
+ function getUsageTelemetry(component, version = "unknown", product = "sdk") {
799
+ let client = clients.get(component);
800
+ if (!client) {
801
+ client = createNodeUsageTelemetry({
802
+ context: {
803
+ product,
804
+ component,
805
+ version,
806
+ environment: process.env.KORTYX_USAGE_TELEMETRY_ENVIRONMENT === "staging" ? "staging" : "production",
807
+ runtimeMode: process.env.CI ? "ci" : process.env.NODE_ENV === "production" ? "production" : process.env.NODE_ENV === "development" ? "development" : "unknown"
808
+ }
809
+ });
810
+ clients.set(component, client);
811
+ }
812
+ return client;
813
+ }
814
+ function publicUsageConfiguration() {
815
+ const key = process.env.KORTYX_USAGE_TELEMETRY_POSTHOG_KEY || RELEASE_POSTHOG_KEY;
816
+ const host = process.env.KORTYX_USAGE_TELEMETRY_POSTHOG_HOST || RELEASE_POSTHOG_HOST;
817
+ const endpoint = process.env.KORTYX_USAGE_TELEMETRY_ENDPOINT;
818
+ let publicHost = RELEASE_POSTHOG_HOST;
819
+ try {
820
+ createPostHogTransport({ key: RELEASE_POSTHOG_KEY, host });
821
+ publicHost = host;
822
+ } catch {
823
+ }
824
+ let valid = true;
825
+ try {
826
+ if (endpoint) createCollectorTransport({ endpoint });
827
+ else createPostHogTransport({ key, host });
828
+ } catch {
829
+ valid = false;
830
+ }
831
+ return {
832
+ enabled: valid && !usageDisabled(),
833
+ debug: process.env.KORTYX_USAGE_TELEMETRY_DEBUG === "1",
834
+ environment: process.env.KORTYX_USAGE_TELEMETRY_ENVIRONMENT === "staging" ? "staging" : "production",
835
+ // A mistakenly supplied personal token or credential-bearing URL must never reach HTML.
836
+ key: valid && /^phc_[a-zA-Z0-9]+$/.test(key) ? key : "",
837
+ host: publicHost,
838
+ ...valid && endpoint ? { endpoint } : {}
839
+ };
840
+ }
841
+ // Annotate the CommonJS export names for ESM import in node:
842
+ 0 && (module.exports = {
843
+ configureUsageTelemetry,
844
+ createNodeUsageTelemetry,
845
+ createUsageTelemetry,
846
+ getUsageTelemetry,
847
+ publicUsageConfiguration,
848
+ scale,
849
+ usageDisabled
850
+ });
851
+ //# sourceMappingURL=node.js.map