@jeffjassky/telemetry 0.2.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/README.md +3 -1
- package/dist/cli.cjs +12 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/core.cjs +12 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +12 -1
- package/dist/core.js.map +1 -1
- package/dist/electron.cjs +12 -1
- package/dist/electron.cjs.map +1 -1
- package/dist/electron.js +12 -1
- package/dist/electron.js.map +1 -1
- package/dist/index.cjs +1437 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1423 -64
- package/dist/index.js.map +1 -1
- package/dist/mcp.cjs +1468 -78
- package/dist/mcp.cjs.map +1 -1
- package/dist/mcp.js +1468 -78
- package/dist/mcp.js.map +1 -1
- package/dist/react.cjs +12 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +12 -1
- package/dist/react.js.map +1 -1
- package/dist/ui/_assets/{index-CXiDp_v6.css → index-COswHpSX.css} +1 -1
- package/dist/ui/_assets/index-Deqhu-hT.js +41 -0
- package/dist/ui/_assets/index-Deqhu-hT.js.map +1 -0
- package/dist/ui/index.html +2 -2
- package/dist/vue.cjs +12 -1
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +12 -1
- package/dist/vue.js.map +1 -1
- package/dist/web.cjs +34 -2
- package/dist/web.cjs.map +1 -1
- package/dist/web.js +34 -3
- package/dist/web.js.map +1 -1
- package/package.json +1 -1
- package/types/core.d.ts +6 -0
- package/types/index.d.ts +484 -11
- package/types/test-d.ts +173 -2
- package/types/web.d.ts +8 -0
- package/dist/ui/_assets/index-3jcToTuP.js +0 -41
- package/dist/ui/_assets/index-3jcToTuP.js.map +0 -1
package/types/test-d.ts
CHANGED
|
@@ -185,6 +185,10 @@ async function reads() {
|
|
|
185
185
|
|
|
186
186
|
const c: TelemetryCounters = t.counters;
|
|
187
187
|
void (c.rejected + c.defaulted + c.sampled + c.capped + c.rollupSkipped + c.deduped + c.truncated);
|
|
188
|
+
// the same drops, attributed — `${family}|${dim}` and `${name}|${attrKey}`
|
|
189
|
+
const skippedBy: number | undefined = c.rollupSkippedBy['llm_cost|feature'];
|
|
190
|
+
const undeclared: number | undefined = c.undeclaredAttrs['llm.completion|codec'];
|
|
191
|
+
void skippedBy, undeclared;
|
|
188
192
|
|
|
189
193
|
t.models.telemetry.find();
|
|
190
194
|
t.models.byKind.usage.countDocuments();
|
|
@@ -412,6 +416,129 @@ async function drain() {
|
|
|
412
416
|
await c.shutdown();
|
|
413
417
|
}
|
|
414
418
|
|
|
419
|
+
// ── catalog (reports §3) ──
|
|
420
|
+
// Pure inference over the registry: every name a page would otherwise hardcode
|
|
421
|
+
// comes back typed, with its domain and whether reading it is a lookup.
|
|
422
|
+
import type {
|
|
423
|
+
Catalog, DeriveCatalogOptions, DimFacet, EventFacet, FamilyFacet, MeasureFacet,
|
|
424
|
+
RegistryProjection, RegistryProjectionEntry,
|
|
425
|
+
} from './index.js';
|
|
426
|
+
import { deriveCatalog, projectRegistry } from './index.js';
|
|
427
|
+
|
|
428
|
+
const catalogOpts: DeriveCatalogOptions = { platforms: ['watchos'] };
|
|
429
|
+
const catalog: Catalog = deriveCatalog(registry, catalogOpts);
|
|
430
|
+
const facet: EventFacet = catalog.events['llm.completion']!;
|
|
431
|
+
const familyFacet: FamilyFacet = catalog.families['llm_cost']!;
|
|
432
|
+
const envelopeDim: DimFacet = catalog.envelope[0]!;
|
|
433
|
+
const measure: MeasureFacet = facet.measures[0]!;
|
|
434
|
+
const exactly: string[] = measure.exactVia;
|
|
435
|
+
const grain: DimSource[] = familyFacet.by;
|
|
436
|
+
const lifetime: boolean = familyFacet.lifetime;
|
|
437
|
+
const domain: string[] | undefined = envelopeDim.values;
|
|
438
|
+
const namespaced: string[] = catalog.namespaces['llm'] ?? [];
|
|
439
|
+
const everySubject: string[] = catalog.subjectTypes;
|
|
440
|
+
const projection: RegistryProjection = projectRegistry(catalog);
|
|
441
|
+
const entry: RegistryProjectionEntry = projection['llm.completion']!;
|
|
442
|
+
const stillAttrKeys: string[] = entry.attrKeys;
|
|
443
|
+
void exactly, grain, lifetime, domain, namespaced, everySubject, stillAttrKeys;
|
|
444
|
+
|
|
445
|
+
// ── suggestions (reports §9) — the same inference, run backwards ──
|
|
446
|
+
import type { DeriveSuggestionsInput, Suggestion, TelemetryCounters as Counters } from './index.js';
|
|
447
|
+
import { COUNTER_OVERFLOW_KEY, MAX_SUGGESTIONS, deriveSuggestions } from './index.js';
|
|
448
|
+
|
|
449
|
+
declare const liveCounters: Counters;
|
|
450
|
+
const suggestInput: DeriveSuggestionsInput = {
|
|
451
|
+
counters: liveCounters,
|
|
452
|
+
catalog,
|
|
453
|
+
quarantine: [{ at: new Date(), name: 'video.exported', reason: 'unregistered event' }],
|
|
454
|
+
};
|
|
455
|
+
const suggestions: Suggestion[] = deriveSuggestions(suggestInput);
|
|
456
|
+
const suggestKind: Suggestion['kind'] = suggestions[0]?.kind ?? 'undeclared_attr';
|
|
457
|
+
const pasteable: string | undefined = suggestions[0]?.fix;
|
|
458
|
+
const capped: 50 = MAX_SUGGESTIONS;
|
|
459
|
+
const overflow: '(other)|(other)' = COUNTER_OVERFLOW_KEY;
|
|
460
|
+
void suggestKind, pasteable, capped, overflow;
|
|
461
|
+
|
|
462
|
+
// ── reports (reports §4, §6) ──
|
|
463
|
+
// The resolver is a value, and its return is a discriminated union: a caller
|
|
464
|
+
// that forgets to check `unavailable` cannot reach `primitive`.
|
|
465
|
+
import type {
|
|
466
|
+
LegacyQuery, Plan, PlanPrimitive, PlanShape, Report, ReportFilter, ReportRange,
|
|
467
|
+
ReportSource, ResolveOptions, Unavailable,
|
|
468
|
+
} from './index.js';
|
|
469
|
+
import type { ExecuteOptions, FoldedRollups, ReportResult, RollupDoc } from './index.js';
|
|
470
|
+
import {
|
|
471
|
+
executeReport, foldRollups, intervalForRange, normalizeQuery, parseReportQuery, rangeOf,
|
|
472
|
+
reportToQuery, resolveReport,
|
|
473
|
+
} from './index.js';
|
|
474
|
+
|
|
475
|
+
const reportSource: ReportSource = { family: 'llm_cost' };
|
|
476
|
+
const reportRange: ReportRange = { from: '2026-07-01T00:00:00Z', to: '2026-07-08T00:00:00Z' };
|
|
477
|
+
const reportFilter: ReportFilter = { dim: 'attr:gen_ai_request_model', op: 'in', value: ['opus', 'sonnet'] };
|
|
478
|
+
const report: Report = {
|
|
479
|
+
source: reportSource,
|
|
480
|
+
range: reportRange,
|
|
481
|
+
interval: 'day',
|
|
482
|
+
measure: 'sum:cost_usd',
|
|
483
|
+
groupBy: ['attr:gen_ai_request_model'],
|
|
484
|
+
filters: [reportFilter],
|
|
485
|
+
compare: 'previous',
|
|
486
|
+
};
|
|
487
|
+
const funnelReport: Report = {
|
|
488
|
+
source: { kind: 'event' },
|
|
489
|
+
range: '90d',
|
|
490
|
+
measure: 'funnel',
|
|
491
|
+
stages: ['account.signed_up', 'account.converted'],
|
|
492
|
+
anchor: 'account.signed_up',
|
|
493
|
+
exits: ['account.churned'],
|
|
494
|
+
subjectType: 'account',
|
|
495
|
+
};
|
|
496
|
+
const resolveOpts: ResolveOptions = { now: new Date(), limits: { breakdown: 20 } };
|
|
497
|
+
const planned: Plan | Unavailable = resolveReport(report, catalog, resolveOpts);
|
|
498
|
+
if ('unavailable' in planned) {
|
|
499
|
+
const refused: true = planned.unavailable;
|
|
500
|
+
const because: string = planned.why;
|
|
501
|
+
void refused, because;
|
|
502
|
+
} else {
|
|
503
|
+
const primitive: PlanPrimitive = planned.primitive;
|
|
504
|
+
const planArgs: unknown[] = planned.args;
|
|
505
|
+
const exactness: 'exact' | 'raw' | 'scan' = planned.exactness;
|
|
506
|
+
const shape: PlanShape | undefined = planned.shape;
|
|
507
|
+
const previous: unknown[] | undefined = planned.previous?.args;
|
|
508
|
+
void primitive, planArgs, exactness, shape, previous;
|
|
509
|
+
}
|
|
510
|
+
const legacy: LegacyQuery = { range: '7d', filters: { name: 'llm.completion' }, groupBy: 'attr:feature' };
|
|
511
|
+
const lifted: Report | null = normalizeQuery(legacy);
|
|
512
|
+
const window: TimeRange = rangeOf('7d', new Date());
|
|
513
|
+
const grainFor: 'hour' | 'day' | 'week' | 'month' = intervalForRange('90d');
|
|
514
|
+
void funnelReport, lifted, window, grainFor;
|
|
515
|
+
|
|
516
|
+
// a Report is a URL, and the two encoders are inverses. `filter` is the one key
|
|
517
|
+
// that can repeat, which is why the query value is a union rather than a string.
|
|
518
|
+
const asQuery: Record<string, string | string[]> = reportToQuery(report);
|
|
519
|
+
const backAgain: Report = parseReportQuery(asQuery);
|
|
520
|
+
void backAgain.source, backAgain.range;
|
|
521
|
+
|
|
522
|
+
// ── the executor (reports §6) ──
|
|
523
|
+
const execOpts: ExecuteOptions = {
|
|
524
|
+
now: new Date(),
|
|
525
|
+
limits: { rollups: 100 },
|
|
526
|
+
redact: (items) => items.map((r) => ({ ...r, data: '[redacted]' })),
|
|
527
|
+
};
|
|
528
|
+
async function runOne(q: Queries) {
|
|
529
|
+
const out: ReportResult = await executeReport(q, 'acc_9', report, catalog, execOpts);
|
|
530
|
+
const ranPlan: Plan = out.plan;
|
|
531
|
+
const store: 'raw' | 'rollups' | 'raw+rollups' = out.dataSource;
|
|
532
|
+
const before: unknown = out.previous;
|
|
533
|
+
void ranPlan, store, before, out.report, out.result;
|
|
534
|
+
}
|
|
535
|
+
// the fold is pure — same rows, same shape, no database
|
|
536
|
+
const foldShape: PlanShape = { groupBy: ['attr:gen_ai_request_model'], labels: ['gen_ai_request_model'], measure: 'sum:cost_usd' };
|
|
537
|
+
const docs: RollupDoc[] = [{ dims: ['gen_ai_request_model=opus'], count: 2, sums: { cost_usd: 4 } }];
|
|
538
|
+
const folded: FoldedRollups = foldRollups(docs, foldShape, false);
|
|
539
|
+
const foldedValue: number | undefined = folded.rows[0]?.value;
|
|
540
|
+
void runOne, foldedValue, folded.groups, folded.truncated, folded.dataSource;
|
|
541
|
+
|
|
415
542
|
// ── dashboard surface ──
|
|
416
543
|
import type {
|
|
417
544
|
CohortSubject,
|
|
@@ -429,12 +556,15 @@ import type {
|
|
|
429
556
|
ResolvedView,
|
|
430
557
|
SubjectAdapter,
|
|
431
558
|
TimeRange,
|
|
559
|
+
Values,
|
|
560
|
+
ValuesParams,
|
|
561
|
+
ValuesResult,
|
|
432
562
|
Viewer,
|
|
433
563
|
ViewerAdapter,
|
|
434
564
|
ViewSpec,
|
|
435
565
|
} from './index.js';
|
|
436
566
|
import {
|
|
437
|
-
createDashboard, createQueries, defaultSpaDir, deriveViews, findFamily,
|
|
567
|
+
createDashboard, createQueries, createValues, defaultSpaDir, deriveViews, findFamily,
|
|
438
568
|
median, requireMilestoneFamily, summarizeStages, DEFAULT_LIMITS,
|
|
439
569
|
} from './index.js';
|
|
440
570
|
|
|
@@ -452,7 +582,7 @@ const view: ViewSpec = {
|
|
|
452
582
|
// the sidebar renders this when present and falls back to the origin badge
|
|
453
583
|
icon: '⚑',
|
|
454
584
|
page: 'errors',
|
|
455
|
-
query: { range: '24h', filters: { severity: '
|
|
585
|
+
query: { source: { kind: 'error' }, range: '24h', filters: [{ dim: 'field:severity', op: 'eq', value: 'error' }] },
|
|
456
586
|
};
|
|
457
587
|
const dashOpts: CreateDashboardOptions = {
|
|
458
588
|
telemetry: t,
|
|
@@ -523,6 +653,24 @@ async function primitives() {
|
|
|
523
653
|
const next: string | null = page.nextCursor;
|
|
524
654
|
const ser = await q.series('acc_9', range, f, { measure: 'sum:cost_usd', interval: 'day' });
|
|
525
655
|
void ser.buckets[0]?.value;
|
|
656
|
+
// the cap is on GROUPS returned, so `truncated` means "there were more top
|
|
657
|
+
// groups", not "the scan stopped early"
|
|
658
|
+
const bd = await q.breakdown('acc_9', range, f, {
|
|
659
|
+
groupBy: ['attr:gen_ai_request_model', 'field:env'],
|
|
660
|
+
measure: 'sum:cost_usd',
|
|
661
|
+
interval: 'day',
|
|
662
|
+
limit: 10,
|
|
663
|
+
});
|
|
664
|
+
const dimValue: string | null = bd.rows[0]?.dims[0] ?? null;
|
|
665
|
+
const bucketAt: Date | undefined = bd.rows[0]?.at;
|
|
666
|
+
const groupCount: number = bd.groups;
|
|
667
|
+
// two flags, two axes: groups dropped vs. buckets dropped from a group shown
|
|
668
|
+
const missingBuckets: boolean = bd.bucketsTruncated;
|
|
669
|
+
void dimValue, bucketAt, groupCount, missingBuckets, bd.truncated, caps.breakdown;
|
|
670
|
+
// a name SET is one $in, not N reads — a namespace or a family is several names
|
|
671
|
+
await q.records('acc_9', range, { name: ['llm.completion', 'billing.ai_tokens'] });
|
|
672
|
+
// a span's duration is on the envelope, and the measure grammar knows it
|
|
673
|
+
await q.series('acc_9', range, f, { measure: 'avg:durationMs' });
|
|
526
674
|
// the sample is complete; the computation is capped, and says so
|
|
527
675
|
const dist = await q.distribution('acc_9', range, f);
|
|
528
676
|
const scanCut: boolean = dist.truncated;
|
|
@@ -575,6 +723,29 @@ async function primitives() {
|
|
|
575
723
|
await q.journey(PLATFORM_SCOPE, 'user:u_1', range);
|
|
576
724
|
await q.distinctCount(PLATFORM_SCOPE, { as: 'activity', range });
|
|
577
725
|
await q.funnel(PLATFORM_SCOPE, { stages: [{ as: 'user.signed_up' }], cohort: range });
|
|
726
|
+
|
|
727
|
+
// ── values: the lookup, not a primitive (reports §5) ──
|
|
728
|
+
const values: Values = createValues({
|
|
729
|
+
catalog,
|
|
730
|
+
TelemetryModel: t.models.telemetry,
|
|
731
|
+
RollupModel: t.models.rollups,
|
|
732
|
+
limits: { values: 50 },
|
|
733
|
+
});
|
|
734
|
+
const params: ValuesParams = {
|
|
735
|
+
dim: 'attr:gen_ai_request_model',
|
|
736
|
+
names: ['llm.completion'],
|
|
737
|
+
range,
|
|
738
|
+
limit: 20,
|
|
739
|
+
};
|
|
740
|
+
const domain: ValuesResult = await values('acc_9', params);
|
|
741
|
+
const observed: string[] = domain.values;
|
|
742
|
+
// counts are absent on the catalog answer — a declared enum has no tally
|
|
743
|
+
const tally: number[] | undefined = domain.counts;
|
|
744
|
+
const answeredBy: 'catalog' | 'rollups' | 'raw' | 'none' = domain.source;
|
|
745
|
+
const readFrom: string | undefined = domain.via;
|
|
746
|
+
void observed, tally, answeredBy, readFrom, domain.truncated, domain.dataSource, caps.values;
|
|
747
|
+
// no range is not an error: the raw step drops off and `none` says so
|
|
748
|
+
await values(PLATFORM_SCOPE, { dim: 'field:client.platform' });
|
|
578
749
|
}
|
|
579
750
|
|
|
580
751
|
// ── the funnel math, usable without a database ──
|
package/types/web.d.ts
CHANGED
|
@@ -3,11 +3,19 @@ import type { CreateClientOptions, Registry, TelemetryClient } from './core.js';
|
|
|
3
3
|
export { createClient } from './core.js';
|
|
4
4
|
export type { TelemetryClient } from './core.js';
|
|
5
5
|
|
|
6
|
+
/** browser-raised non-errors, dropped by default (see `captureBenignErrors`) */
|
|
7
|
+
export declare const BENIGN_BROWSER_ERRORS: readonly RegExp[];
|
|
8
|
+
|
|
6
9
|
export interface WebTelemetryOptions extends Omit<CreateClientOptions, 'storage' | 'consent'> {
|
|
7
10
|
/** host consent (cookie banner etc). ANDed with DNT/GPC — those always win. */
|
|
8
11
|
consent?: () => boolean;
|
|
9
12
|
/** auto-capture window.onerror / unhandledrejection (default true) */
|
|
10
13
|
captureGlobalErrors?: boolean;
|
|
14
|
+
/** drop error records by message — strings match by substring, RegExp by test.
|
|
15
|
+
* ADDED to `BENIGN_BROWSER_ERRORS`, not replacing it. */
|
|
16
|
+
ignoreErrors?: Array<string | RegExp>;
|
|
17
|
+
/** keep the `BENIGN_BROWSER_ERRORS` records instead of dropping them */
|
|
18
|
+
captureBenignErrors?: boolean;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
/**
|