@kortyx/usage-telemetry 0.3.0 → 0.4.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 CHANGED
@@ -23,8 +23,10 @@ __export(node_exports, {
23
23
  configureUsageTelemetry: () => configureUsageTelemetry,
24
24
  createNodeUsageTelemetry: () => createNodeUsageTelemetry,
25
25
  createUsageTelemetry: () => createUsageTelemetry,
26
+ flushUsageTelemetry: () => flushUsageTelemetry,
26
27
  getUsageTelemetry: () => getUsageTelemetry,
27
28
  publicUsageConfiguration: () => publicUsageConfiguration,
29
+ runtimeUsageContext: () => runtimeUsageContext,
28
30
  scale: () => scale,
29
31
  usageDisabled: () => usageDisabled
30
32
  });
@@ -76,6 +78,10 @@ var operations = [
76
78
  "studio.settings",
77
79
  "studio.update",
78
80
  "studio.ingest",
81
+ "cli.command",
82
+ "studio.eval.execution",
83
+ "studio.diagnostic",
84
+ "studio.table.layout",
79
85
  "cli.topology",
80
86
  "cli.eval"
81
87
  ];
@@ -90,9 +96,143 @@ var outcomes = [
90
96
  "ungraded"
91
97
  ];
92
98
  var publicCatalog = {
93
- products: ["sdk", "oss_studio"],
99
+ products: ["sdk", "oss_studio", "cli"],
94
100
  operations,
95
101
  properties: {
102
+ measurement_version: ["2"],
103
+ measurement_kind: [
104
+ "invocation",
105
+ "stage",
106
+ "execution",
107
+ "attempt",
108
+ "chunk",
109
+ "output",
110
+ "update",
111
+ "poll",
112
+ "request",
113
+ "call",
114
+ "feature",
115
+ "observation"
116
+ ],
117
+ traffic_source: ["internal", "synthetic", "external", "unknown"],
118
+ layout_action: [
119
+ "visibility",
120
+ "show_all",
121
+ "reset",
122
+ "apply",
123
+ "reset_width",
124
+ "pin",
125
+ "unpin",
126
+ "resize",
127
+ "reorder"
128
+ ],
129
+ eval_phase: [
130
+ "params",
131
+ "setup",
132
+ "execute",
133
+ "responder",
134
+ "reference",
135
+ "grading",
136
+ "verification",
137
+ "cleanup",
138
+ "reporting"
139
+ ],
140
+ cli_resource: ["evals.runs", "evals.suites"],
141
+ retryable: "boolean",
142
+ retried: "boolean",
143
+ recovered: "boolean",
144
+ retry_attempt: ["1", "2-5", "6-20", "21-100", "101+"],
145
+ batch_size: ["1", "2-5", "6-20", "21-100", "101+"],
146
+ http_status_class: ["1xx", "2xx", "3xx", "4xx", "5xx"],
147
+ cause_category: [
148
+ "configuration",
149
+ "provider",
150
+ "validation",
151
+ "domain",
152
+ "workflow",
153
+ "persistence",
154
+ "request",
155
+ "transport",
156
+ "internal",
157
+ "unknown"
158
+ ],
159
+ cli_command: [
160
+ "studio.start",
161
+ "studio.stop",
162
+ "studio.restart",
163
+ "studio.status",
164
+ "studio.logs",
165
+ "studio.credentials",
166
+ "studio.reset",
167
+ "studio.prompts",
168
+ "studio.evals",
169
+ "studio.runs",
170
+ "studio.sessions",
171
+ "studio.interrupts",
172
+ "studio.workflows",
173
+ "studio.diagnostics",
174
+ "studio.inspect",
175
+ "studio.doctor",
176
+ "studio.catalogs",
177
+ "connections",
178
+ "evals.run",
179
+ "evals.list",
180
+ "topology.push",
181
+ "help",
182
+ "unknown"
183
+ ],
184
+ cli_action: [
185
+ "start",
186
+ "stop",
187
+ "restart",
188
+ "status",
189
+ "logs",
190
+ "credentials",
191
+ "reset",
192
+ "run",
193
+ "list",
194
+ "get",
195
+ "show",
196
+ "inspect",
197
+ "analyze",
198
+ "download",
199
+ "doctor",
200
+ "cancel",
201
+ "compare",
202
+ "create",
203
+ "update",
204
+ "delete",
205
+ "import",
206
+ "export",
207
+ "pull",
208
+ "push",
209
+ "add",
210
+ "remove",
211
+ "use",
212
+ "verify",
213
+ "versions",
214
+ "diff",
215
+ "validate",
216
+ "action",
217
+ "tag",
218
+ "untag",
219
+ "rollback",
220
+ "promote",
221
+ "archive",
222
+ "restore",
223
+ "test",
224
+ "copy",
225
+ "apply",
226
+ "wait",
227
+ "catalogs",
228
+ "help",
229
+ "unknown"
230
+ ],
231
+ dry_run: "boolean",
232
+ json_output: "boolean",
233
+ settings_override: "boolean",
234
+ case_timeout_override: "boolean",
235
+ run_timeout_override: "boolean",
96
236
  phase: [
97
237
  "admission",
98
238
  "execution",
@@ -237,6 +377,7 @@ var publicCatalog = {
237
377
  "pending",
238
378
  "expired"
239
379
  ],
380
+ match: ["all", "any"],
240
381
  filter: [
241
382
  "search",
242
383
  "status",
@@ -269,6 +410,7 @@ var publicCatalog = {
269
410
  "401",
270
411
  "403",
271
412
  "404",
413
+ "408",
272
414
  "409",
273
415
  "413",
274
416
  "422",
@@ -292,6 +434,47 @@ var publicCatalog = {
292
434
  },
293
435
  // Built-in contracts only; application codes and provider response text are excluded.
294
436
  errorCodes: [
437
+ "UNCLASSIFIED_FAILURE",
438
+ "HTTP_UNAUTHORIZED",
439
+ "HTTP_FORBIDDEN",
440
+ "HTTP_NOT_FOUND",
441
+ "HTTP_TIMEOUT",
442
+ "HTTP_CONFLICT",
443
+ "HTTP_TOO_LARGE",
444
+ "HTTP_RATE_LIMITED",
445
+ "HTTP_SERVER_ERROR",
446
+ "HTTP_INVALID_REQUEST",
447
+ "NETWORK_DNS_FAILED",
448
+ "NETWORK_CONNECTION_REFUSED",
449
+ "NETWORK_CONNECTION_RESET",
450
+ "NETWORK_TIMEOUT",
451
+ "NETWORK_TLS_FAILED",
452
+ "CLI_ARGUMENT_INVALID",
453
+ "CLI_EXIT_FAILED",
454
+ "STUDIO_NOT_CONFIGURED",
455
+ "STUDIO_RESPONSE_INVALID",
456
+ "EVAL_CONCURRENCY_CHANGED",
457
+ "EVAL_CONSUMER_FAILED",
458
+ "EVAL_CONTRACT_CHANGED",
459
+ "EVAL_DISCOVERY_REJECTED",
460
+ "EVAL_EXECUTION_CONFLICT",
461
+ "EVAL_EXECUTION_STORE_CHANGED",
462
+ "EVAL_EXECUTION_UNKNOWN",
463
+ "EVAL_EXECUTOR_BUSY",
464
+ "EVAL_EXECUTOR_FAILED",
465
+ "EVAL_EXECUTOR_REJECTED",
466
+ "EVAL_EXECUTOR_UNAUTHORIZED",
467
+ "EVAL_GRADING_FAILED",
468
+ "EVAL_GRADING_TIMEOUT",
469
+ "EVAL_JUDGE_UNAVAILABLE",
470
+ "EVAL_LEASE_LOST",
471
+ "EVAL_RETRY_DELAY_EXCEEDED",
472
+ "EVAL_SETUP_TIMEOUT",
473
+ "EVAL_TRANSPORT_UNAVAILABLE",
474
+ "EVAL_VERIFICATION_TIMEOUT",
475
+ "EVAL_RESULT_INVALID",
476
+ "EVAL_RUN_TIMEOUT",
477
+ "EVAL_SETTINGS_INVALID",
295
478
  "EXECUTION_FAILED",
296
479
  "INVALID_REQUEST",
297
480
  "INVALID_RESUME",
@@ -359,8 +542,8 @@ function scale(value) {
359
542
  return value <= 1 ? "1" : value <= 5 ? "2-5" : value <= 20 ? "6-20" : value <= 100 ? "21-100" : "101+";
360
543
  }
361
544
 
362
- // src/client.ts
363
- function property(value, key) {
545
+ // src/failure.ts
546
+ function usageErrorProperty(value, key) {
364
547
  try {
365
548
  if (!value || typeof value !== "object") return void 0;
366
549
  const descriptor = Object.getOwnPropertyDescriptor(value, key);
@@ -369,40 +552,201 @@ function property(value, key) {
369
552
  return void 0;
370
553
  }
371
554
  }
372
- function classifyUsageError(error, catalog = publicCatalog) {
373
- const name = property(error, "name");
374
- const code = property(error, "code") ?? property(property(error, "failure"), "code");
375
- if (name === "AbortError" || code === "EXECUTION_CANCELLED")
555
+ var networkCodes = {
556
+ ENOTFOUND: "NETWORK_DNS_FAILED",
557
+ EAI_AGAIN: "NETWORK_DNS_FAILED",
558
+ ECONNREFUSED: "NETWORK_CONNECTION_REFUSED",
559
+ ECONNRESET: "NETWORK_CONNECTION_RESET",
560
+ EPIPE: "NETWORK_CONNECTION_RESET",
561
+ ETIMEDOUT: "NETWORK_TIMEOUT",
562
+ UND_ERR_CONNECT_TIMEOUT: "NETWORK_TIMEOUT",
563
+ UND_ERR_HEADERS_TIMEOUT: "NETWORK_TIMEOUT",
564
+ UND_ERR_BODY_TIMEOUT: "NETWORK_TIMEOUT",
565
+ CERT_HAS_EXPIRED: "NETWORK_TLS_FAILED",
566
+ DEPTH_ZERO_SELF_SIGNED_CERT: "NETWORK_TLS_FAILED",
567
+ UNABLE_TO_VERIFY_LEAF_SIGNATURE: "NETWORK_TLS_FAILED"
568
+ };
569
+ function classifyUsageHttpStatus(status) {
570
+ if (!Number.isInteger(status) || status < 100 || status > 599)
571
+ return {
572
+ outcome: "failed",
573
+ properties: {
574
+ error_code: "UNCLASSIFIED_FAILURE",
575
+ error_category: "unknown"
576
+ }
577
+ };
578
+ if (status < 400)
579
+ return {
580
+ outcome: "success",
581
+ properties: { http_status_class: `${Math.floor(status / 100)}xx` }
582
+ };
583
+ const code = status === 401 ? "HTTP_UNAUTHORIZED" : status === 403 ? "HTTP_FORBIDDEN" : status === 404 ? "HTTP_NOT_FOUND" : status === 408 || status === 504 ? "HTTP_TIMEOUT" : status === 409 ? "HTTP_CONFLICT" : status === 413 ? "HTTP_TOO_LARGE" : status === 429 ? "HTTP_RATE_LIMITED" : status >= 500 ? "HTTP_SERVER_ERROR" : "HTTP_INVALID_REQUEST";
584
+ return {
585
+ outcome: "failed",
586
+ properties: {
587
+ error_code: code,
588
+ error_category: status >= 500 || status === 408 ? "transport" : "request",
589
+ http_status: String(status),
590
+ http_status_class: `${Math.floor(status / 100)}xx`,
591
+ retryable: status === 408 || status === 429 || status >= 500
592
+ }
593
+ };
594
+ }
595
+ function nativeExceptionName(value) {
596
+ try {
597
+ if (typeof DOMException !== "undefined")
598
+ return Object.getOwnPropertyDescriptor(
599
+ DOMException.prototype,
600
+ "name"
601
+ )?.get?.call(value);
602
+ } catch {
603
+ }
604
+ return void 0;
605
+ }
606
+ function classifyOne(error, catalog) {
607
+ const read = (key) => usageErrorProperty(error, key);
608
+ let name = read("name") ?? nativeExceptionName(error);
609
+ try {
610
+ if (name === void 0 && error instanceof SyntaxError)
611
+ name = "SyntaxError";
612
+ } catch {
613
+ }
614
+ const code = read("code");
615
+ if (name === "AbortError" || code === "EXECUTION_CANCELLED" || code === "ABORT_ERR")
376
616
  return { outcome: "cancelled", properties: {} };
377
617
  if (code === "EXECUTION_LIMIT_REACHED" || name === "ExecutionLimitReached")
378
618
  return { outcome: "limit_reached", properties: {} };
379
- if (name === "GraphInterrupt" || name === "NodeInterrupt" || name === "Interrupt")
619
+ if (typeof name === "string" && [
620
+ "GraphInterrupt",
621
+ "NodeInterrupt",
622
+ "Interrupt",
623
+ "ParallelChildWaiting"
624
+ ].includes(name))
380
625
  return { outcome: "suspended", properties: {} };
381
- const known = typeof code === "string" && catalog.errorCodes.includes(code);
382
- const category = known && code !== "EXECUTION_FAILED" ? property(error, "category") ?? property(property(error, "failure"), "category") ?? "unknown" : "unknown";
383
- const status = property(error, "status") ?? property(property(error, "failure"), "status");
384
- return {
385
- outcome: "failed",
386
- properties: {
387
- error_code: known ? code : "other",
388
- error_category: category,
389
- ...typeof status === "number" ? { http_status: String(status) } : {}
626
+ if (name === "EvalConfigurationError")
627
+ return {
628
+ outcome: "failed",
629
+ properties: {
630
+ error_code: "EVAL_SETTINGS_INVALID",
631
+ error_category: "configuration",
632
+ retryable: false
633
+ }
634
+ };
635
+ if (name === "TimeoutError")
636
+ return {
637
+ outcome: "failed",
638
+ properties: {
639
+ error_code: "NETWORK_TIMEOUT",
640
+ error_category: "transport",
641
+ retryable: true
642
+ }
643
+ };
644
+ if (typeof code === "string" && Object.hasOwn(networkCodes, code))
645
+ return {
646
+ outcome: "failed",
647
+ properties: {
648
+ error_code: networkCodes[code] ?? "UNCLASSIFIED_FAILURE",
649
+ error_category: "transport",
650
+ retryable: true
651
+ }
652
+ };
653
+ if (typeof code === "string" && catalog.errorCodes.includes(code)) {
654
+ const failure = usageErrorProperty(error, "failure");
655
+ const category = read("category") ?? usageErrorProperty(failure, "category");
656
+ const retryable = read("retryable") ?? usageErrorProperty(failure, "retryable");
657
+ const status2 = read("status") ?? usageErrorProperty(failure, "status");
658
+ return {
659
+ outcome: "failed",
660
+ properties: {
661
+ ...typeof status2 === "number" ? classifyUsageHttpStatus(status2).properties : {},
662
+ // The framework code is more specific than its HTTP projection.
663
+ error_code: code,
664
+ error_category: code !== "EXECUTION_FAILED" && typeof category === "string" && Array.isArray(catalog.properties.error_category) && catalog.properties.error_category.includes(category) ? category : "unknown",
665
+ ...typeof retryable === "boolean" ? { retryable } : {}
666
+ }
667
+ };
668
+ }
669
+ const status = read("status") ?? read("statusCode");
670
+ if (typeof status === "number" && Number.isInteger(status) && status >= 400 && status <= 599)
671
+ return classifyUsageHttpStatus(status);
672
+ if (name === "ZodError" || name === "SyntaxError")
673
+ return {
674
+ outcome: "failed",
675
+ properties: {
676
+ error_code: "SCHEMA_VALIDATION",
677
+ error_category: "validation"
678
+ }
679
+ };
680
+ return void 0;
681
+ }
682
+ function classifyUsageError(error, catalog = publicCatalog) {
683
+ const seen = /* @__PURE__ */ new Set();
684
+ const pending = [{ value: error, depth: 0 }];
685
+ let result;
686
+ while (pending.length && seen.size < 8) {
687
+ const current = pending.shift();
688
+ if (!current || seen.has(current.value)) continue;
689
+ seen.add(current.value);
690
+ const found = classifyOne(current.value, catalog);
691
+ if (found) {
692
+ if (!result) result = found;
693
+ else if (found.properties.error_code && found.outcome === "failed" && found.properties.error_code !== result.properties.error_code) {
694
+ result.properties.cause_code = found.properties.error_code;
695
+ result.properties.cause_category = found.properties.error_category ?? "unknown";
696
+ if (result.properties.retryable === void 0 && typeof found.properties.retryable === "boolean")
697
+ result.properties.retryable = found.properties.retryable;
698
+ break;
699
+ }
700
+ if (result === found && found.outcome !== "failed") return found;
390
701
  }
702
+ if (current.depth < 3)
703
+ for (const key of ["failure", "cause", "error"]) {
704
+ const value = usageErrorProperty(current.value, key);
705
+ if (value && typeof value === "object")
706
+ pending.push({ value, depth: current.depth + 1 });
707
+ }
708
+ }
709
+ return result ?? {
710
+ outcome: "failed",
711
+ properties: { error_code: "other", error_category: "unknown" }
391
712
  };
392
713
  }
714
+
715
+ // src/measurement.ts
716
+ function usageMeasurement(event, operation, input) {
717
+ if (event === "usage.feature")
718
+ return usageErrorProperty(input, "origin") === "background" || usageErrorProperty(input, "origin") === "server" ? "observation" : "feature";
719
+ if (operation === "cli.command" || operation === "sdk.eval")
720
+ return "invocation";
721
+ if (operation === "studio.setup") return "stage";
722
+ if (operation === "sdk.execution" || operation === "studio.eval.execution")
723
+ return "execution";
724
+ if (operation === "sdk.export" || operation === "sdk.model") return "attempt";
725
+ if (operation === "sdk.structured") {
726
+ const format = usageErrorProperty(input, "format");
727
+ return format === "text-delta" ? "chunk" : format === "final" ? "output" : "update";
728
+ }
729
+ if (usageErrorProperty(input, "origin") === "background") return "poll";
730
+ if (operation.startsWith("studio.")) return "request";
731
+ return "call";
732
+ }
733
+
734
+ // src/client.ts
393
735
  function sanitizeProperties(input, catalog = publicCatalog) {
394
736
  const safe = {};
395
737
  for (const [key, rule] of Object.entries(catalog.properties)) {
396
- const value = property(input, key);
738
+ const value = usageErrorProperty(input, key);
397
739
  if (value === void 0) continue;
398
740
  if (rule === "boolean") {
399
741
  if (typeof value === "boolean") safe[key] = value;
400
742
  } else if (typeof value === "string")
401
743
  safe[key] = rule.includes(value) ? value : "other";
402
744
  }
403
- const code = property(input, "error_code");
404
- if (typeof code === "string")
405
- safe.error_code = catalog.errorCodes.includes(code) ? code : "other";
745
+ for (const key of ["error_code", "cause_code"]) {
746
+ const code = usageErrorProperty(input, key);
747
+ if (typeof code === "string")
748
+ safe[key] = catalog.errorCodes.includes(code) ? code : "other";
749
+ }
406
750
  return safe;
407
751
  }
408
752
  var durationBounds = [10, 100, 1e3, 1e4, 6e4];
@@ -423,6 +767,16 @@ function createUsageTelemetry(options) {
423
767
  let disposed = false;
424
768
  let dropped = 0;
425
769
  let delivered = 0;
770
+ let lostObservations = 0;
771
+ let failedBatches = 0;
772
+ let reportedLoss = 0;
773
+ let reportedFailures = 0;
774
+ const lose = (count) => {
775
+ lostObservations = Math.min(
776
+ Number.MAX_SAFE_INTEGER,
777
+ lostObservations + count
778
+ );
779
+ };
426
780
  let hour = Math.floor(now() / 36e5);
427
781
  let sent = 0;
428
782
  const active = () => {
@@ -458,6 +812,10 @@ function createUsageTelemetry(options) {
458
812
  pending.clear();
459
813
  if (!records.length) return;
460
814
  let attempted = records.length;
815
+ let attemptedObservations = records.reduce(
816
+ (total, record2) => total + record2.count,
817
+ 0
818
+ );
461
819
  flight = (async () => {
462
820
  try {
463
821
  if (options.debug?.()) {
@@ -475,7 +833,14 @@ function createUsageTelemetry(options) {
475
833
  );
476
834
  const batch = records.slice(0, allowance);
477
835
  dropped += records.length - batch.length;
836
+ lose(
837
+ records.slice(allowance).reduce((total, record2) => total + record2.count, 0)
838
+ );
478
839
  attempted = batch.length;
840
+ attemptedObservations = batch.reduce(
841
+ (total, record2) => total + record2.count,
842
+ 0
843
+ );
479
844
  if (!batch.length || !active()) return;
480
845
  const identified = await options.identity();
481
846
  const identity2 = typeof identified === "string" ? identified : identified.id;
@@ -486,13 +851,25 @@ function createUsageTelemetry(options) {
486
851
  identity2
487
852
  )) {
488
853
  dropped += batch.length;
854
+ lose(attemptedObservations);
489
855
  return;
490
856
  }
491
857
  sent += batch.length;
858
+ const lossSnapshot = lostObservations;
859
+ const failureSnapshot = failedBatches;
860
+ if (batch[0] && (lossSnapshot > reportedLoss || failureSnapshot > reportedFailures))
861
+ batch[0].delivery_health = {
862
+ dropped_observations: lossSnapshot - reportedLoss,
863
+ failed_batches: failureSnapshot - reportedFailures
864
+ };
492
865
  await options.transport(batch, identity2);
866
+ reportedLoss = lossSnapshot;
867
+ reportedFailures = failureSnapshot;
493
868
  delivered += batch.length;
494
869
  } catch {
495
870
  dropped += attempted;
871
+ lose(attemptedObservations);
872
+ failedBatches = Math.min(Number.MAX_SAFE_INTEGER, failedBatches + 1);
496
873
  }
497
874
  })().finally(() => {
498
875
  flight = void 0;
@@ -516,12 +893,29 @@ function createUsageTelemetry(options) {
516
893
  }
517
894
  if (!configured || !catalog.operations.includes(operation) || !outcomes.includes(outcome))
518
895
  return;
519
- const properties = sanitizeProperties(input, catalog);
896
+ const failed = outcome === "failed";
897
+ const status = usageErrorProperty(input, "http_status");
898
+ const http = typeof status === "string" && /^\d{3}$/.test(status) ? classifyUsageHttpStatus(Number(status)).properties : {};
899
+ const properties = sanitizeProperties(
900
+ {
901
+ measurement_version: "2",
902
+ measurement_kind: usageMeasurement(event, operation, input),
903
+ ...failed ? {
904
+ error_code: "UNCLASSIFIED_FAILURE",
905
+ error_category: "unknown",
906
+ ...http
907
+ } : {},
908
+ ...sanitizeProperties(input, catalog),
909
+ traffic_source: options.context.trafficSource ?? "unknown"
910
+ },
911
+ catalog
912
+ );
520
913
  const key = JSON.stringify([event, operation, outcome, properties]);
521
914
  let existing = pending.get(key);
522
915
  if (!existing) {
523
916
  if (pending.size >= (options.maxGroups ?? 64)) {
524
917
  dropped++;
918
+ lose(1);
525
919
  return;
526
920
  }
527
921
  existing = {
@@ -533,7 +927,9 @@ function createUsageTelemetry(options) {
533
927
  component: options.context.component,
534
928
  version: options.context.version,
535
929
  analytics_environment: options.context.environment ?? "production",
536
- runtime_mode: options.context.runtimeMode ?? "unknown",
930
+ runtime_mode: ["development", "production", "ci"].includes(
931
+ options.context.runtimeMode ?? ""
932
+ ) ? options.context.runtimeMode ?? "unknown" : "unknown",
537
933
  identity_scope: options.context.identityScope ?? "installation",
538
934
  operation,
539
935
  outcome,
@@ -578,8 +974,11 @@ function createUsageTelemetry(options) {
578
974
  if (ended) return;
579
975
  ended = true;
580
976
  try {
977
+ const status = usageErrorProperty(extra, "http_status");
978
+ const http = outcome === "failed" && typeof status === "string" && /^\d{3}$/.test(status) ? classifyUsageHttpStatus(Number(status)).properties : {};
581
979
  record("usage.operation", operation, outcome, now() - began, {
582
980
  ...initial,
981
+ ...http,
583
982
  ...sanitizeProperties(extra, catalog)
584
983
  });
585
984
  } catch {
@@ -666,6 +1065,10 @@ function createPostHogTransport(options) {
666
1065
  count: record.count,
667
1066
  duration_total_ms: record.duration_total_ms,
668
1067
  duration_buckets: record.duration_buckets,
1068
+ ...record.delivery_health ? {
1069
+ usage_dropped_observations: record.delivery_health.dropped_observations,
1070
+ usage_failed_batches: record.delivery_health.failed_batches
1071
+ } : {},
669
1072
  $process_person_profile: false,
670
1073
  $geoip_disable: true,
671
1074
  $ip: null
@@ -761,6 +1164,13 @@ async function installationIdentity() {
761
1164
  })();
762
1165
  return identity;
763
1166
  }
1167
+ function runtimeUsageContext() {
1168
+ const traffic = process.env.KORTYX_USAGE_TELEMETRY_TRAFFIC_SOURCE;
1169
+ return {
1170
+ runtimeMode: process.env.CI && !["0", "false"].includes(process.env.CI.toLowerCase()) ? "ci" : process.env.NODE_ENV === "production" ? "production" : process.env.NODE_ENV === "development" ? "development" : "unknown",
1171
+ trafficSource: traffic === "internal" || traffic === "synthetic" || traffic === "external" ? traffic : "unknown"
1172
+ };
1173
+ }
764
1174
  function usageDisabled() {
765
1175
  return ["1", "true"].includes(process.env.KORTYX_USAGE_TELEMETRY_DISABLED ?? "") || process.env.NODE_ENV === "test";
766
1176
  }
@@ -784,6 +1194,11 @@ function createNodeUsageTelemetry(options) {
784
1194
  }
785
1195
  return createUsageTelemetry({
786
1196
  ...options,
1197
+ context: {
1198
+ ...options.context,
1199
+ runtimeMode: options.context.runtimeMode ?? runtimeUsageContext().runtimeMode,
1200
+ trafficSource: options.context.trafficSource ?? runtimeUsageContext().trafficSource
1201
+ },
787
1202
  transport,
788
1203
  identity: options.identity ?? installationIdentity,
789
1204
  enabled: () => valid && !usageDisabled() && (options.enabled?.() ?? true),
@@ -792,6 +1207,9 @@ function createNodeUsageTelemetry(options) {
792
1207
  });
793
1208
  }
794
1209
  var clients = /* @__PURE__ */ new Map();
1210
+ async function flushUsageTelemetry() {
1211
+ await Promise.all([...clients.values()].map((client) => client.flush()));
1212
+ }
795
1213
  function configureUsageTelemetry(component, client) {
796
1214
  if (clients.get(component) === client) return;
797
1215
  clients.get(component)?.dispose();
@@ -806,7 +1224,7 @@ function getUsageTelemetry(component, version = "unknown", product = "sdk") {
806
1224
  component,
807
1225
  version,
808
1226
  environment: process.env.KORTYX_USAGE_TELEMETRY_ENVIRONMENT === "staging" ? "staging" : "production",
809
- runtimeMode: process.env.CI ? "ci" : process.env.NODE_ENV === "production" ? "production" : process.env.NODE_ENV === "development" ? "development" : "unknown"
1227
+ ...runtimeUsageContext()
810
1228
  }
811
1229
  });
812
1230
  clients.set(component, client);
@@ -832,6 +1250,7 @@ function publicUsageConfiguration() {
832
1250
  }
833
1251
  return {
834
1252
  enabled: valid && !usageDisabled(),
1253
+ ...runtimeUsageContext(),
835
1254
  debug: process.env.KORTYX_USAGE_TELEMETRY_DEBUG === "1",
836
1255
  environment: process.env.KORTYX_USAGE_TELEMETRY_ENVIRONMENT === "staging" ? "staging" : "production",
837
1256
  // A mistakenly supplied personal token or credential-bearing URL must never reach HTML.
@@ -845,8 +1264,10 @@ function publicUsageConfiguration() {
845
1264
  configureUsageTelemetry,
846
1265
  createNodeUsageTelemetry,
847
1266
  createUsageTelemetry,
1267
+ flushUsageTelemetry,
848
1268
  getUsageTelemetry,
849
1269
  publicUsageConfiguration,
1270
+ runtimeUsageContext,
850
1271
  scale,
851
1272
  usageDisabled
852
1273
  });