@proj-airi/trace-kit 0.0.0-stage → 0.15.3
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/LICENSE +21 -0
- package/README.md +102 -2
- package/dist/clock-CewQJzn8.d.mts +545 -0
- package/dist/clock-hjTPEs21.mjs +212 -0
- package/dist/collector/index.d.mts +207 -0
- package/dist/collector/index.mjs +601 -0
- package/dist/conventions-DLiC8rkL.mjs +285 -0
- package/dist/index-DV04qeax.d.mts +49 -0
- package/dist/index.d.mts +782 -0
- package/dist/index.mjs +6 -0
- package/dist/keys-DD_VE8Fk.mjs +34 -0
- package/dist/profiles/agent.d.mts +70 -0
- package/dist/profiles/agent.mjs +295 -0
- package/dist/profiles/voice.d.mts +128 -0
- package/dist/profiles/voice.mjs +669 -0
- package/dist/projections/index.d.mts +3 -0
- package/dist/projections/index.mjs +1 -0
- package/dist/semconv-BOVWnou1.d.mts +832 -0
- package/dist/semconv.d.mts +2 -0
- package/dist/semconv.mjs +832 -0
- package/dist/series-ring-CaKfvyWQ.mjs +118 -0
- package/dist/series-ring-DCb7czkl.d.mts +49 -0
- package/dist/sink-BoftsA-W.mjs +501 -0
- package/dist/testing/index.d.mts +79 -0
- package/dist/testing/index.mjs +880 -0
- package/dist/timeline-B4Vc6qQf.mjs +766 -0
- package/dist/types-Fp54Yi6b.d.mts +319 -0
- package/dist/views/index.d.mts +97 -0
- package/dist/views/index.mjs +219 -0
- package/package.json +71 -3
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { ATTR_GEN_AI_INPUT_MESSAGES, ATTR_GEN_AI_MEMORY_QUERY_TEXT, ATTR_GEN_AI_OUTPUT_MESSAGES, ATTR_GEN_AI_SYSTEM_INSTRUCTIONS, ATTR_GEN_AI_TOOL_CALL_ARGUMENTS, ATTR_GEN_AI_TOOL_CALL_RESULT } from "./semconv.mjs";
|
|
2
|
+
import { clamp, mean, percentile, sum } from "es-toolkit";
|
|
3
|
+
//#region src/core/define.ts
|
|
4
|
+
function defineTrack(definition) {
|
|
5
|
+
return definition;
|
|
6
|
+
}
|
|
7
|
+
function defineAttribute(definition) {
|
|
8
|
+
return definition;
|
|
9
|
+
}
|
|
10
|
+
function defineIdKey(definition) {
|
|
11
|
+
return definition;
|
|
12
|
+
}
|
|
13
|
+
function defineLink(definition) {
|
|
14
|
+
return definition;
|
|
15
|
+
}
|
|
16
|
+
function defineEndReason(definition) {
|
|
17
|
+
return definition;
|
|
18
|
+
}
|
|
19
|
+
function defineClock(definition) {
|
|
20
|
+
return definition;
|
|
21
|
+
}
|
|
22
|
+
function defineMetric(definition) {
|
|
23
|
+
return definition;
|
|
24
|
+
}
|
|
25
|
+
function defineScore(definition) {
|
|
26
|
+
return definition;
|
|
27
|
+
}
|
|
28
|
+
function definePipeline(definition) {
|
|
29
|
+
return definition;
|
|
30
|
+
}
|
|
31
|
+
function definePreset(definition) {
|
|
32
|
+
return definition;
|
|
33
|
+
}
|
|
34
|
+
function defineProfile(definition) {
|
|
35
|
+
return definition;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The definition records that announce a profile, in the order that a viewer needs them:
|
|
39
|
+
* the profile first, then each of its definitions.
|
|
40
|
+
*/
|
|
41
|
+
function definitionRecordsOf(profile, ts) {
|
|
42
|
+
const records = [];
|
|
43
|
+
const push = (kind, key, data) => records.push({
|
|
44
|
+
type: "definition",
|
|
45
|
+
ts,
|
|
46
|
+
kind,
|
|
47
|
+
key,
|
|
48
|
+
data
|
|
49
|
+
});
|
|
50
|
+
const { tracks, attributes, idKeys, links, endReasons, clocks, metrics, scores, pipelines, presets, ...head } = profile;
|
|
51
|
+
push("profile", profile.key, head);
|
|
52
|
+
for (const track of tracks ?? []) push("track", track.path, track);
|
|
53
|
+
for (const attribute of attributes ?? []) push("attribute", attribute.key, attribute);
|
|
54
|
+
for (const idKey of idKeys ?? []) push("id-key", idKey.key, idKey);
|
|
55
|
+
for (const link of links ?? []) push("link", link.rel, link);
|
|
56
|
+
for (const endReason of endReasons ?? []) push("end-reason", endReason.reason, endReason);
|
|
57
|
+
for (const clock of clocks ?? []) push("clock", clock.pattern, clock);
|
|
58
|
+
for (const metric of metrics ?? []) push("metric", metric.key, metric);
|
|
59
|
+
for (const score of scores ?? []) push("score", score.key, score);
|
|
60
|
+
for (const pipeline of pipelines ?? []) push("pipeline", pipeline.key, pipeline);
|
|
61
|
+
for (const preset of presets ?? []) push("preset", preset.key, preset);
|
|
62
|
+
return records;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Matches a track path against a path pattern.
|
|
66
|
+
*
|
|
67
|
+
* Returns the values of the `{key}` segments, or `undefined` when the path does not match.
|
|
68
|
+
* A literal segment matches only itself. A `{key}` segment matches exactly one segment, and so does `*`, without a name.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* matchTrackPath('participants/{actor}/speech', 'participants/user/speech')
|
|
72
|
+
* // => { actor: 'user' }
|
|
73
|
+
*/
|
|
74
|
+
function matchTrackPath(pattern, path) {
|
|
75
|
+
const patternSegments = pattern.split("/");
|
|
76
|
+
const pathSegments = path.split("/");
|
|
77
|
+
if (patternSegments.length !== pathSegments.length) return void 0;
|
|
78
|
+
const params = {};
|
|
79
|
+
for (const [index, segment] of patternSegments.entries()) {
|
|
80
|
+
const value = pathSegments[index];
|
|
81
|
+
if (segment.startsWith("{") && segment.endsWith("}")) params[segment.slice(1, -1)] = value;
|
|
82
|
+
else if (segment !== "*" && segment !== value) return void 0;
|
|
83
|
+
}
|
|
84
|
+
return params;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Fills the `{key}` segments of a track path pattern.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* trackPath('participants/{actor}/speech', { actor: 'user' })
|
|
91
|
+
* // => 'participants/user/speech'
|
|
92
|
+
*/
|
|
93
|
+
function trackPath(pattern, params) {
|
|
94
|
+
return pattern.replace(/\{(\w+)\}/g, (_match, key) => {
|
|
95
|
+
const value = params[key];
|
|
96
|
+
if (value === void 0) throw new Error(`Track path ${pattern} needs the ID key ${key}`);
|
|
97
|
+
return String(value);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
/** Returns the text for a locale, then English, then the first entry. */
|
|
101
|
+
function localize(text, locale) {
|
|
102
|
+
if (text === void 0 || typeof text === "string") return text;
|
|
103
|
+
return text[locale] ?? text[locale.split("-")[0]] ?? text.en ?? Object.values(text)[0];
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/conventions/index.ts
|
|
107
|
+
/** Version of the conventions. Exports write it into their metadata. */
|
|
108
|
+
const TRACE_CONVENTIONS_VERSION = "1";
|
|
109
|
+
/** Relations between records. Profiles add more. */
|
|
110
|
+
const TraceLinkRelations = {
|
|
111
|
+
CausedBy: "caused-by",
|
|
112
|
+
CancelledBy: "cancelled-by",
|
|
113
|
+
RetryOf: "retry-of",
|
|
114
|
+
Supersedes: "supersedes",
|
|
115
|
+
Revises: "revises",
|
|
116
|
+
FollowsFrom: "follows-from",
|
|
117
|
+
SameEntity: "same-entity"
|
|
118
|
+
};
|
|
119
|
+
/** Common values of `span.end.reason`. The field is open. */
|
|
120
|
+
const TraceEndReasons = {
|
|
121
|
+
Interrupted: "interrupted",
|
|
122
|
+
Timeout: "timeout",
|
|
123
|
+
Superseded: "superseded",
|
|
124
|
+
Aborted: "aborted",
|
|
125
|
+
NoAudio: "no-audio"
|
|
126
|
+
};
|
|
127
|
+
/** Kinds of pipeline edges. The field is open. */
|
|
128
|
+
const TraceEdgeKinds = {
|
|
129
|
+
Command: "cmd",
|
|
130
|
+
Data: "data",
|
|
131
|
+
Audio: "audio",
|
|
132
|
+
Signal: "signal"
|
|
133
|
+
};
|
|
134
|
+
/** Attribute names of AIRI that apply to any domain. The `gen_ai.*` names are in `./semconv`. */
|
|
135
|
+
const ATTR_PROVIDER_REQUEST_ID = "provider.request_id";
|
|
136
|
+
const ATTR_PROVIDER_RESPONSE_ID = "provider.response_id";
|
|
137
|
+
const ATTR_PROVIDER_ITEM_ID = "provider.item_id";
|
|
138
|
+
const ATTR_RETRY_COUNT = "retry.count";
|
|
139
|
+
const ATTR_FALLBACK_INDEX = "fallback.index";
|
|
140
|
+
/** An interruption cut the measurement short. Percentiles leave it out. */
|
|
141
|
+
const ATTR_CENSORED = "censored";
|
|
142
|
+
/** A cold start. Percentiles leave it out. */
|
|
143
|
+
const ATTR_WARMUP = "warmup";
|
|
144
|
+
const ATTR_DECISION_CHOICES = "decision.choices";
|
|
145
|
+
const ATTR_DECISION_VERDICT = "decision.verdict";
|
|
146
|
+
const ATTR_DECISION_P = "decision.p";
|
|
147
|
+
const ATTR_DECISION_THRESHOLD = "decision.threshold";
|
|
148
|
+
const ATTR_DECISION_REASON = "decision.reason";
|
|
149
|
+
const ATTR_DECISION_LATENCY_MS = "decision.latency_ms";
|
|
150
|
+
const ATTR_DECIDER = "decider";
|
|
151
|
+
const ATTR_MSG_KIND = "msg.kind";
|
|
152
|
+
const ATTR_MSG_NAME = "msg.name";
|
|
153
|
+
const ATTR_MSG_TO = "msg.to";
|
|
154
|
+
const ATTR_WORK_INTERRUPTIBLE = "work.interruptible";
|
|
155
|
+
const ATTR_STREAM_ID = "stream.id";
|
|
156
|
+
const ATTR_STREAM_START_FRAME = "stream.start_frame";
|
|
157
|
+
const ATTR_STREAM_END_FRAME = "stream.end_frame";
|
|
158
|
+
/** The rule that decided a state change. */
|
|
159
|
+
const ATTR_RULE = "rule";
|
|
160
|
+
/** An external input to a policy. A replay harness feeds it to a new policy. */
|
|
161
|
+
const ATTR_REPLAY_INPUT = "replay.input";
|
|
162
|
+
/** Only the `conversation` ID key maps to `gen_ai.conversation.id`. The `topic` ID key maps to this attribute. */
|
|
163
|
+
const ATTR_AIRI_TOPIC_ID = "airi.topic.id";
|
|
164
|
+
/** Attribute definitions of the conventions. The content policy reads their sensitivity. */
|
|
165
|
+
const conventionAttributes = [
|
|
166
|
+
ATTR_GEN_AI_SYSTEM_INSTRUCTIONS,
|
|
167
|
+
ATTR_GEN_AI_INPUT_MESSAGES,
|
|
168
|
+
ATTR_GEN_AI_OUTPUT_MESSAGES,
|
|
169
|
+
ATTR_GEN_AI_TOOL_CALL_ARGUMENTS,
|
|
170
|
+
ATTR_GEN_AI_TOOL_CALL_RESULT,
|
|
171
|
+
ATTR_GEN_AI_MEMORY_QUERY_TEXT
|
|
172
|
+
].map((key) => defineAttribute({
|
|
173
|
+
key,
|
|
174
|
+
type: "json",
|
|
175
|
+
sensitivity: "content"
|
|
176
|
+
}));
|
|
177
|
+
/**
|
|
178
|
+
* Patterns of secret values. Tool arguments and provider errors can hold API keys, so the collector scrubs every
|
|
179
|
+
* string attribute, also attributes that are not content.
|
|
180
|
+
*/
|
|
181
|
+
const SECRET_PATTERNS = [
|
|
182
|
+
/\b(?:sk|pk|rk)-[\w-]{16,}/g,
|
|
183
|
+
/\bBearer\s+[\w.~+/-]{16,}=*/gi,
|
|
184
|
+
/\b(?:api[_-]?key|access[_-]?token|secret|password)\s*[:=]\s*["']?[^\s"',}]{8,}/gi,
|
|
185
|
+
/\bAKIA[0-9A-Z]{16}\b/g
|
|
186
|
+
];
|
|
187
|
+
const SCRUBBED = "[secret]";
|
|
188
|
+
/**
|
|
189
|
+
* Removes secrets from a string.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* scrubSecrets('Authorization: Bearer abcdefghijklmnop1234')
|
|
193
|
+
* // => 'Authorization: [secret]'
|
|
194
|
+
*/
|
|
195
|
+
function scrubSecrets(text) {
|
|
196
|
+
let result = text;
|
|
197
|
+
for (const pattern of SECRET_PATTERNS) result = result.replace(pattern, SCRUBBED);
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
function applyToAttributes(attrs, policy) {
|
|
201
|
+
let changed = false;
|
|
202
|
+
const result = {};
|
|
203
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
204
|
+
const sensitivity = policy.sensitivityOf(key);
|
|
205
|
+
if (sensitivity === "secret" || sensitivity === "content" && !policy.keepContent) {
|
|
206
|
+
changed = true;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (typeof value === "string") {
|
|
210
|
+
const scrubbed = scrubSecrets(value);
|
|
211
|
+
changed ||= scrubbed !== value;
|
|
212
|
+
result[key] = scrubbed;
|
|
213
|
+
} else if (Array.isArray(value) && value.some((item) => typeof item === "string")) {
|
|
214
|
+
const scrubbed = value.map(scrubSecrets);
|
|
215
|
+
changed ||= scrubbed.some((item, index) => item !== value[index]);
|
|
216
|
+
result[key] = scrubbed;
|
|
217
|
+
} else result[key] = value;
|
|
218
|
+
}
|
|
219
|
+
return changed ? result : void 0;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Applies the content policy to one record. The collector applies it before the buffer and before the disk.
|
|
223
|
+
*
|
|
224
|
+
* Returns the same object when nothing changes, so the common path does not copy.
|
|
225
|
+
*/
|
|
226
|
+
function applyContentPolicy(record, policy) {
|
|
227
|
+
let result = record;
|
|
228
|
+
if (record.attrs) {
|
|
229
|
+
const attrs = applyToAttributes(record.attrs, policy);
|
|
230
|
+
if (attrs) result = {
|
|
231
|
+
...result,
|
|
232
|
+
attrs
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (record.type === "log") {
|
|
236
|
+
const message = scrubSecrets(record.message);
|
|
237
|
+
if (message !== record.message) result = {
|
|
238
|
+
...result,
|
|
239
|
+
message
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (record.type === "span.end" && record.message) {
|
|
243
|
+
const message = scrubSecrets(record.message);
|
|
244
|
+
if (message !== record.message) result = {
|
|
245
|
+
...result,
|
|
246
|
+
message
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
/** Fewest samples for a p95. A p95 of fewer samples is noise. */
|
|
252
|
+
const MIN_P95_SAMPLES = 100;
|
|
253
|
+
/**
|
|
254
|
+
* Computes the nearest-rank percentile of measurements.
|
|
255
|
+
* Returns `undefined` for an empty list, and for p95 and p99 with fewer than 100 samples.
|
|
256
|
+
*/
|
|
257
|
+
function percentile$1(values, p) {
|
|
258
|
+
if (values.length === 0 || p >= 95 && values.length < MIN_P95_SAMPLES) return void 0;
|
|
259
|
+
return percentile(values, clamp(p, 0, 100));
|
|
260
|
+
}
|
|
261
|
+
/** Aggregates measurements. A ratio metric gives sums, so its ratio is a ratio of sums, not a mean of ratios. */
|
|
262
|
+
function aggregate(values, kinds) {
|
|
263
|
+
const result = {};
|
|
264
|
+
for (const kind of kinds) switch (kind) {
|
|
265
|
+
case "p50":
|
|
266
|
+
result.p50 = percentile$1(values, 50);
|
|
267
|
+
break;
|
|
268
|
+
case "p95":
|
|
269
|
+
result.p95 = percentile$1(values, 95);
|
|
270
|
+
break;
|
|
271
|
+
case "p99":
|
|
272
|
+
result.p99 = percentile$1(values, 99);
|
|
273
|
+
break;
|
|
274
|
+
case "mean":
|
|
275
|
+
result.mean = values.length > 0 ? mean(values) : void 0;
|
|
276
|
+
break;
|
|
277
|
+
case "sum":
|
|
278
|
+
result.sum = sum(values);
|
|
279
|
+
break;
|
|
280
|
+
case "count": result.count = values.length;
|
|
281
|
+
}
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { applyContentPolicy as A, definePipeline as B, ATTR_WARMUP as C, TraceEndReasons as D, TraceEdgeKinds as E, defineClock as F, definitionRecordsOf as G, defineProfile as H, defineEndReason as I, trackPath as J, localize as K, defineIdKey as L, percentile$1 as M, scrubSecrets as N, TraceLinkRelations as O, defineAttribute as P, defineLink as R, ATTR_STREAM_START_FRAME as S, TRACE_CONVENTIONS_VERSION as T, defineScore as U, definePreset as V, defineTrack as W, ATTR_REPLAY_INPUT as _, ATTR_DECISION_LATENCY_MS as a, ATTR_STREAM_END_FRAME as b, ATTR_DECISION_THRESHOLD as c, ATTR_MSG_KIND as d, ATTR_MSG_NAME as f, ATTR_PROVIDER_RESPONSE_ID as g, ATTR_PROVIDER_REQUEST_ID as h, ATTR_DECISION_CHOICES as i, conventionAttributes as j, aggregate as k, ATTR_DECISION_VERDICT as l, ATTR_PROVIDER_ITEM_ID as m, ATTR_CENSORED as n, ATTR_DECISION_P as o, ATTR_MSG_TO as p, matchTrackPath as q, ATTR_DECIDER as r, ATTR_DECISION_REASON as s, ATTR_AIRI_TOPIC_ID as t, ATTR_FALLBACK_INDEX as u, ATTR_RETRY_COUNT as v, ATTR_WORK_INTERRUPTIBLE as w, ATTR_STREAM_ID as x, ATTR_RULE as y, defineMetric as z };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { L as TraceLink, g as TraceMetricDefinition, z as TraceRecord } from "./clock-CewQJzn8.mjs";
|
|
2
|
+
import { a as MetricSamples, f as TimelineSpan, g as TraceApplyContext, i as MetricSample, l as TimelineProjectionOptions, o as MetricSummary, p as TimelineState, r as EventLogState, v as TraceProjection } from "./types-Fp54Yi6b.mjs";
|
|
3
|
+
//#region src/projections/event-log.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A list of records in apply order, with their converted time. The viewer filters it.
|
|
6
|
+
*
|
|
7
|
+
* Series records are left out, because they are many and the timeline draws them.
|
|
8
|
+
*/
|
|
9
|
+
declare function createEventLogProjection(options?: {
|
|
10
|
+
/** @default 20000 */
|
|
11
|
+
limit?: number;
|
|
12
|
+
}): TraceProjection<EventLogState>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/projections/metrics.d.ts
|
|
15
|
+
interface MetricsState {
|
|
16
|
+
definitions: Map<string, TraceMetricDefinition>;
|
|
17
|
+
samples: MetricSamples;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Collects the samples of metrics of kind `attribute`.
|
|
21
|
+
*
|
|
22
|
+
* A metric definition record names a track pattern, a record type, and an attribute. Each matching record with a number
|
|
23
|
+
* value adds one sample. A metric of kind `derived` comes from its own projection.
|
|
24
|
+
*/
|
|
25
|
+
declare function createAttributeMetricsProjection(): TraceProjection<MetricsState, MetricSamples>;
|
|
26
|
+
/** Summarizes samples with the aggregates of their definition. The default aggregates are p50 and p95. */
|
|
27
|
+
declare function summarizeMetric(key: string, samples: readonly MetricSample[], definition?: TraceMetricDefinition): MetricSummary;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/projections/timeline.d.ts
|
|
30
|
+
/**
|
|
31
|
+
* The timeline: tracks with spans, instants, state changes, and series, plus global markers and gaps.
|
|
32
|
+
*
|
|
33
|
+
* Display times are converted times. A span keeps the sub-lane that it got at begin, so lanes do not jump while the
|
|
34
|
+
* view is live. Each series is a {@link SeriesRing} with the retention of the collector, so a live view of a long
|
|
35
|
+
* session stays bounded. A file replay passes `seriesRetention: false`.
|
|
36
|
+
*/
|
|
37
|
+
declare function createTimelineProjection(options?: TimelineProjectionOptions): TraceProjection<TimelineState>;
|
|
38
|
+
/**
|
|
39
|
+
* The start of the provisional region: the earliest watermark of the live sources.
|
|
40
|
+
* A watermark trails the newest record of its source by the allowed lateness.
|
|
41
|
+
*/
|
|
42
|
+
declare function provisionalFrom(state: TimelineState, allowedLatenessMs?: number): number | undefined;
|
|
43
|
+
/** Records that link to a span, and the targets that the span links to. */
|
|
44
|
+
declare function linksOfSpan(span: TimelineSpan, indexes: TraceApplyContext['indexes']): {
|
|
45
|
+
incoming: TraceRecord[];
|
|
46
|
+
outgoing: readonly TraceLink[];
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { summarizeMetric as a, createAttributeMetricsProjection as i, linksOfSpan as n, createEventLogProjection as o, provisionalFrom as r, createTimelineProjection as t };
|