@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/CHANGELOG.md +8 -0
- package/LICENSE +209 -0
- package/README.md +75 -2
- package/dist/browser-node.d.ts +16 -0
- package/dist/browser-node.js +676 -0
- package/dist/browser-node.js.map +1 -0
- package/dist/client-rVwo4XAO.d.ts +85 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +720 -0
- package/dist/index.js.map +1 -0
- package/dist/node.d.ts +19 -0
- package/dist/node.js +851 -0
- package/dist/node.js.map +1 -0
- package/package.json +47 -3
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
|