@jeffjassky/telemetry 0.3.0 → 0.5.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/index.cjs +1583 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1567 -66
- 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/ui/_assets/{index-CXiDp_v6.css → index-COswHpSX.css} +1 -1
- package/dist/ui/_assets/index-kvwB9_3A.js +41 -0
- package/dist/ui/_assets/index-kvwB9_3A.js.map +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/types/index.d.ts +572 -11
- package/types/test-d.ts +193 -2
- 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
|
@@ -34,8 +34,10 @@ import type {
|
|
|
34
34
|
MetricsOf,
|
|
35
35
|
Registry,
|
|
36
36
|
RollupSpec,
|
|
37
|
+
LinkSubjects,
|
|
37
38
|
Scoped,
|
|
38
39
|
SubjectInput,
|
|
40
|
+
SubjectLinker,
|
|
39
41
|
Telemetry,
|
|
40
42
|
TelemetryCounters,
|
|
41
43
|
} from './index.js';
|
|
@@ -61,6 +63,8 @@ import {
|
|
|
61
63
|
RETENTION_DAYS,
|
|
62
64
|
SAMPLE_RATE,
|
|
63
65
|
SCHEMA_VERSION,
|
|
66
|
+
SUBJECT_LINK_TIMEOUT_MS,
|
|
67
|
+
SUBJECT_MAX,
|
|
64
68
|
TelemetryKind,
|
|
65
69
|
} from './index.js';
|
|
66
70
|
|
|
@@ -101,6 +105,15 @@ const registry = defineRegistry({
|
|
|
101
105
|
|
|
102
106
|
declare const mongooseish: CreateTelemetryConfig['connection'];
|
|
103
107
|
|
|
108
|
+
/** WRITE-time: the desktop's machine ref, resolved to the account that owns it */
|
|
109
|
+
const subjectLinker: SubjectLinker = {
|
|
110
|
+
link: async (subjects, { name, tenantId }) => {
|
|
111
|
+
void name, tenantId;
|
|
112
|
+
const machine = subjects.find((s) => s.type === 'machine');
|
|
113
|
+
return machine ? [{ type: 'user', id: `u_${machine.id}`, role: 'owner' }] : [];
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
104
117
|
const t = createTelemetry({
|
|
105
118
|
registry,
|
|
106
119
|
connection: mongooseish,
|
|
@@ -108,8 +121,15 @@ const t = createTelemetry({
|
|
|
108
121
|
platforms: ['watchos'], // EXTENDS the builtins; 'web' still validates
|
|
109
122
|
bodyMax: 4096,
|
|
110
123
|
globalSubjectRefs: true, // refs name one party in every tenant — forget() reaches '*' views
|
|
124
|
+
subjectLinker,
|
|
125
|
+
subjectLinkTimeoutMs: SUBJECT_LINK_TIMEOUT_MS,
|
|
111
126
|
});
|
|
112
127
|
|
|
128
|
+
// the guarded linker the instance resolved, shared with the router factories
|
|
129
|
+
const linkSubjects: LinkSubjects | null = t.linkSubjects;
|
|
130
|
+
const subjectCap: number = SUBJECT_MAX;
|
|
131
|
+
void linkSubjects, subjectCap;
|
|
132
|
+
|
|
113
133
|
// ── emit is typed against the registry ──
|
|
114
134
|
async function writes() {
|
|
115
135
|
await t.emit('user.signed_up', {
|
|
@@ -185,6 +205,10 @@ async function reads() {
|
|
|
185
205
|
|
|
186
206
|
const c: TelemetryCounters = t.counters;
|
|
187
207
|
void (c.rejected + c.defaulted + c.sampled + c.capped + c.rollupSkipped + c.deduped + c.truncated);
|
|
208
|
+
// the same drops, attributed — `${family}|${dim}` and `${name}|${attrKey}`
|
|
209
|
+
const skippedBy: number | undefined = c.rollupSkippedBy['llm_cost|feature'];
|
|
210
|
+
const undeclared: number | undefined = c.undeclaredAttrs['llm.completion|codec'];
|
|
211
|
+
void skippedBy, undeclared;
|
|
188
212
|
|
|
189
213
|
t.models.telemetry.find();
|
|
190
214
|
t.models.byKind.usage.countDocuments();
|
|
@@ -412,6 +436,129 @@ async function drain() {
|
|
|
412
436
|
await c.shutdown();
|
|
413
437
|
}
|
|
414
438
|
|
|
439
|
+
// ── catalog (reports §3) ──
|
|
440
|
+
// Pure inference over the registry: every name a page would otherwise hardcode
|
|
441
|
+
// comes back typed, with its domain and whether reading it is a lookup.
|
|
442
|
+
import type {
|
|
443
|
+
Catalog, DeriveCatalogOptions, DimFacet, EventFacet, FamilyFacet, MeasureFacet,
|
|
444
|
+
RegistryProjection, RegistryProjectionEntry,
|
|
445
|
+
} from './index.js';
|
|
446
|
+
import { deriveCatalog, projectRegistry } from './index.js';
|
|
447
|
+
|
|
448
|
+
const catalogOpts: DeriveCatalogOptions = { platforms: ['watchos'] };
|
|
449
|
+
const catalog: Catalog = deriveCatalog(registry, catalogOpts);
|
|
450
|
+
const facet: EventFacet = catalog.events['llm.completion']!;
|
|
451
|
+
const familyFacet: FamilyFacet = catalog.families['llm_cost']!;
|
|
452
|
+
const envelopeDim: DimFacet = catalog.envelope[0]!;
|
|
453
|
+
const measure: MeasureFacet = facet.measures[0]!;
|
|
454
|
+
const exactly: string[] = measure.exactVia;
|
|
455
|
+
const grain: DimSource[] = familyFacet.by;
|
|
456
|
+
const lifetime: boolean = familyFacet.lifetime;
|
|
457
|
+
const domain: string[] | undefined = envelopeDim.values;
|
|
458
|
+
const namespaced: string[] = catalog.namespaces['llm'] ?? [];
|
|
459
|
+
const everySubject: string[] = catalog.subjectTypes;
|
|
460
|
+
const projection: RegistryProjection = projectRegistry(catalog);
|
|
461
|
+
const entry: RegistryProjectionEntry = projection['llm.completion']!;
|
|
462
|
+
const stillAttrKeys: string[] = entry.attrKeys;
|
|
463
|
+
void exactly, grain, lifetime, domain, namespaced, everySubject, stillAttrKeys;
|
|
464
|
+
|
|
465
|
+
// ── suggestions (reports §9) — the same inference, run backwards ──
|
|
466
|
+
import type { DeriveSuggestionsInput, Suggestion, TelemetryCounters as Counters } from './index.js';
|
|
467
|
+
import { COUNTER_OVERFLOW_KEY, MAX_SUGGESTIONS, deriveSuggestions } from './index.js';
|
|
468
|
+
|
|
469
|
+
declare const liveCounters: Counters;
|
|
470
|
+
const suggestInput: DeriveSuggestionsInput = {
|
|
471
|
+
counters: liveCounters,
|
|
472
|
+
catalog,
|
|
473
|
+
quarantine: [{ at: new Date(), name: 'video.exported', reason: 'unregistered event' }],
|
|
474
|
+
};
|
|
475
|
+
const suggestions: Suggestion[] = deriveSuggestions(suggestInput);
|
|
476
|
+
const suggestKind: Suggestion['kind'] = suggestions[0]?.kind ?? 'undeclared_attr';
|
|
477
|
+
const pasteable: string | undefined = suggestions[0]?.fix;
|
|
478
|
+
const capped: 50 = MAX_SUGGESTIONS;
|
|
479
|
+
const overflow: '(other)|(other)' = COUNTER_OVERFLOW_KEY;
|
|
480
|
+
void suggestKind, pasteable, capped, overflow;
|
|
481
|
+
|
|
482
|
+
// ── reports (reports §4, §6) ──
|
|
483
|
+
// The resolver is a value, and its return is a discriminated union: a caller
|
|
484
|
+
// that forgets to check `unavailable` cannot reach `primitive`.
|
|
485
|
+
import type {
|
|
486
|
+
LegacyQuery, Plan, PlanPrimitive, PlanShape, Report, ReportFilter, ReportRange,
|
|
487
|
+
ReportSource, ResolveOptions, Unavailable,
|
|
488
|
+
} from './index.js';
|
|
489
|
+
import type { ExecuteOptions, FoldedRollups, ReportResult, RollupDoc } from './index.js';
|
|
490
|
+
import {
|
|
491
|
+
executeReport, foldRollups, intervalForRange, normalizeQuery, parseReportQuery, rangeOf,
|
|
492
|
+
reportToQuery, resolveReport,
|
|
493
|
+
} from './index.js';
|
|
494
|
+
|
|
495
|
+
const reportSource: ReportSource = { family: 'llm_cost' };
|
|
496
|
+
const reportRange: ReportRange = { from: '2026-07-01T00:00:00Z', to: '2026-07-08T00:00:00Z' };
|
|
497
|
+
const reportFilter: ReportFilter = { dim: 'attr:gen_ai_request_model', op: 'in', value: ['opus', 'sonnet'] };
|
|
498
|
+
const report: Report = {
|
|
499
|
+
source: reportSource,
|
|
500
|
+
range: reportRange,
|
|
501
|
+
interval: 'day',
|
|
502
|
+
measure: 'sum:cost_usd',
|
|
503
|
+
groupBy: ['attr:gen_ai_request_model'],
|
|
504
|
+
filters: [reportFilter],
|
|
505
|
+
compare: 'previous',
|
|
506
|
+
};
|
|
507
|
+
const funnelReport: Report = {
|
|
508
|
+
source: { kind: 'event' },
|
|
509
|
+
range: '90d',
|
|
510
|
+
measure: 'funnel',
|
|
511
|
+
stages: ['account.signed_up', 'account.converted'],
|
|
512
|
+
anchor: 'account.signed_up',
|
|
513
|
+
exits: ['account.churned'],
|
|
514
|
+
subjectType: 'account',
|
|
515
|
+
};
|
|
516
|
+
const resolveOpts: ResolveOptions = { now: new Date(), limits: { breakdown: 20 } };
|
|
517
|
+
const planned: Plan | Unavailable = resolveReport(report, catalog, resolveOpts);
|
|
518
|
+
if ('unavailable' in planned) {
|
|
519
|
+
const refused: true = planned.unavailable;
|
|
520
|
+
const because: string = planned.why;
|
|
521
|
+
void refused, because;
|
|
522
|
+
} else {
|
|
523
|
+
const primitive: PlanPrimitive = planned.primitive;
|
|
524
|
+
const planArgs: unknown[] = planned.args;
|
|
525
|
+
const exactness: 'exact' | 'raw' | 'scan' = planned.exactness;
|
|
526
|
+
const shape: PlanShape | undefined = planned.shape;
|
|
527
|
+
const previous: unknown[] | undefined = planned.previous?.args;
|
|
528
|
+
void primitive, planArgs, exactness, shape, previous;
|
|
529
|
+
}
|
|
530
|
+
const legacy: LegacyQuery = { range: '7d', filters: { name: 'llm.completion' }, groupBy: 'attr:feature' };
|
|
531
|
+
const lifted: Report | null = normalizeQuery(legacy);
|
|
532
|
+
const window: TimeRange = rangeOf('7d', new Date());
|
|
533
|
+
const grainFor: 'hour' | 'day' | 'week' | 'month' = intervalForRange('90d');
|
|
534
|
+
void funnelReport, lifted, window, grainFor;
|
|
535
|
+
|
|
536
|
+
// a Report is a URL, and the two encoders are inverses. `filter` is the one key
|
|
537
|
+
// that can repeat, which is why the query value is a union rather than a string.
|
|
538
|
+
const asQuery: Record<string, string | string[]> = reportToQuery(report);
|
|
539
|
+
const backAgain: Report = parseReportQuery(asQuery);
|
|
540
|
+
void backAgain.source, backAgain.range;
|
|
541
|
+
|
|
542
|
+
// ── the executor (reports §6) ──
|
|
543
|
+
const execOpts: ExecuteOptions = {
|
|
544
|
+
now: new Date(),
|
|
545
|
+
limits: { rollups: 100 },
|
|
546
|
+
redact: (items) => items.map((r) => ({ ...r, data: '[redacted]' })),
|
|
547
|
+
};
|
|
548
|
+
async function runOne(q: Queries) {
|
|
549
|
+
const out: ReportResult = await executeReport(q, 'acc_9', report, catalog, execOpts);
|
|
550
|
+
const ranPlan: Plan = out.plan;
|
|
551
|
+
const store: 'raw' | 'rollups' | 'raw+rollups' = out.dataSource;
|
|
552
|
+
const before: unknown = out.previous;
|
|
553
|
+
void ranPlan, store, before, out.report, out.result;
|
|
554
|
+
}
|
|
555
|
+
// the fold is pure — same rows, same shape, no database
|
|
556
|
+
const foldShape: PlanShape = { groupBy: ['attr:gen_ai_request_model'], labels: ['gen_ai_request_model'], measure: 'sum:cost_usd' };
|
|
557
|
+
const docs: RollupDoc[] = [{ dims: ['gen_ai_request_model=opus'], count: 2, sums: { cost_usd: 4 } }];
|
|
558
|
+
const folded: FoldedRollups = foldRollups(docs, foldShape, false);
|
|
559
|
+
const foldedValue: number | undefined = folded.rows[0]?.value;
|
|
560
|
+
void runOne, foldedValue, folded.groups, folded.truncated, folded.dataSource;
|
|
561
|
+
|
|
415
562
|
// ── dashboard surface ──
|
|
416
563
|
import type {
|
|
417
564
|
CohortSubject,
|
|
@@ -429,12 +576,15 @@ import type {
|
|
|
429
576
|
ResolvedView,
|
|
430
577
|
SubjectAdapter,
|
|
431
578
|
TimeRange,
|
|
579
|
+
Values,
|
|
580
|
+
ValuesParams,
|
|
581
|
+
ValuesResult,
|
|
432
582
|
Viewer,
|
|
433
583
|
ViewerAdapter,
|
|
434
584
|
ViewSpec,
|
|
435
585
|
} from './index.js';
|
|
436
586
|
import {
|
|
437
|
-
createDashboard, createQueries, defaultSpaDir, deriveViews, findFamily,
|
|
587
|
+
createDashboard, createQueries, createValues, defaultSpaDir, deriveViews, findFamily,
|
|
438
588
|
median, requireMilestoneFamily, summarizeStages, DEFAULT_LIMITS,
|
|
439
589
|
} from './index.js';
|
|
440
590
|
|
|
@@ -452,7 +602,7 @@ const view: ViewSpec = {
|
|
|
452
602
|
// the sidebar renders this when present and falls back to the origin badge
|
|
453
603
|
icon: '⚑',
|
|
454
604
|
page: 'errors',
|
|
455
|
-
query: { range: '24h', filters: { severity: '
|
|
605
|
+
query: { source: { kind: 'error' }, range: '24h', filters: [{ dim: 'field:severity', op: 'eq', value: 'error' }] },
|
|
456
606
|
};
|
|
457
607
|
const dashOpts: CreateDashboardOptions = {
|
|
458
608
|
telemetry: t,
|
|
@@ -523,6 +673,24 @@ async function primitives() {
|
|
|
523
673
|
const next: string | null = page.nextCursor;
|
|
524
674
|
const ser = await q.series('acc_9', range, f, { measure: 'sum:cost_usd', interval: 'day' });
|
|
525
675
|
void ser.buckets[0]?.value;
|
|
676
|
+
// the cap is on GROUPS returned, so `truncated` means "there were more top
|
|
677
|
+
// groups", not "the scan stopped early"
|
|
678
|
+
const bd = await q.breakdown('acc_9', range, f, {
|
|
679
|
+
groupBy: ['attr:gen_ai_request_model', 'field:env'],
|
|
680
|
+
measure: 'sum:cost_usd',
|
|
681
|
+
interval: 'day',
|
|
682
|
+
limit: 10,
|
|
683
|
+
});
|
|
684
|
+
const dimValue: string | null = bd.rows[0]?.dims[0] ?? null;
|
|
685
|
+
const bucketAt: Date | undefined = bd.rows[0]?.at;
|
|
686
|
+
const groupCount: number = bd.groups;
|
|
687
|
+
// two flags, two axes: groups dropped vs. buckets dropped from a group shown
|
|
688
|
+
const missingBuckets: boolean = bd.bucketsTruncated;
|
|
689
|
+
void dimValue, bucketAt, groupCount, missingBuckets, bd.truncated, caps.breakdown;
|
|
690
|
+
// a name SET is one $in, not N reads — a namespace or a family is several names
|
|
691
|
+
await q.records('acc_9', range, { name: ['llm.completion', 'billing.ai_tokens'] });
|
|
692
|
+
// a span's duration is on the envelope, and the measure grammar knows it
|
|
693
|
+
await q.series('acc_9', range, f, { measure: 'avg:durationMs' });
|
|
526
694
|
// the sample is complete; the computation is capped, and says so
|
|
527
695
|
const dist = await q.distribution('acc_9', range, f);
|
|
528
696
|
const scanCut: boolean = dist.truncated;
|
|
@@ -575,6 +743,29 @@ async function primitives() {
|
|
|
575
743
|
await q.journey(PLATFORM_SCOPE, 'user:u_1', range);
|
|
576
744
|
await q.distinctCount(PLATFORM_SCOPE, { as: 'activity', range });
|
|
577
745
|
await q.funnel(PLATFORM_SCOPE, { stages: [{ as: 'user.signed_up' }], cohort: range });
|
|
746
|
+
|
|
747
|
+
// ── values: the lookup, not a primitive (reports §5) ──
|
|
748
|
+
const values: Values = createValues({
|
|
749
|
+
catalog,
|
|
750
|
+
TelemetryModel: t.models.telemetry,
|
|
751
|
+
RollupModel: t.models.rollups,
|
|
752
|
+
limits: { values: 50 },
|
|
753
|
+
});
|
|
754
|
+
const params: ValuesParams = {
|
|
755
|
+
dim: 'attr:gen_ai_request_model',
|
|
756
|
+
names: ['llm.completion'],
|
|
757
|
+
range,
|
|
758
|
+
limit: 20,
|
|
759
|
+
};
|
|
760
|
+
const domain: ValuesResult = await values('acc_9', params);
|
|
761
|
+
const observed: string[] = domain.values;
|
|
762
|
+
// counts are absent on the catalog answer — a declared enum has no tally
|
|
763
|
+
const tally: number[] | undefined = domain.counts;
|
|
764
|
+
const answeredBy: 'catalog' | 'rollups' | 'raw' | 'none' = domain.source;
|
|
765
|
+
const readFrom: string | undefined = domain.via;
|
|
766
|
+
void observed, tally, answeredBy, readFrom, domain.truncated, domain.dataSource, caps.values;
|
|
767
|
+
// no range is not an error: the raw step drops off and `none` says so
|
|
768
|
+
await values(PLATFORM_SCOPE, { dim: 'field:client.platform' });
|
|
578
769
|
}
|
|
579
770
|
|
|
580
771
|
// ── the funnel math, usable without a database ──
|