@proj-airi/trace-kit 0.0.0-stage → 0.15.2
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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-PRESENT Neko Ayaka
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,103 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @proj-airi/trace-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Trace records, clocks, collection, and projections for AIRI agent and pipeline activity.
|
|
4
|
+
|
|
5
|
+
The package depends only on `@proj-airi/stream-kit`, `es-toolkit`, and `valibot`. Then `core-agent`, `pipelines-audio`, and `plugin-protocol` can depend on it without a cycle.
|
|
6
|
+
|
|
7
|
+
## Use it for
|
|
8
|
+
|
|
9
|
+
- Writing what a module does as an ordered stream of records: span begin and end, instants, states, samples, and logs.
|
|
10
|
+
- Joining work across windows, processes, and plugins by domain IDs and typed links, not by parent spans only.
|
|
11
|
+
- Converting times of several clocks, such as windows, the main process, and audio streams, to one trace clock.
|
|
12
|
+
- Keeping recent records in one collector, and feeding live views, files, and tests from it.
|
|
13
|
+
- Turning records into view state with pure projections: timeline, event log, and metrics.
|
|
14
|
+
|
|
15
|
+
## Do not use it for
|
|
16
|
+
|
|
17
|
+
- Product UI. Product UI reads domain projections of eventa events, not traces.
|
|
18
|
+
- Remote telemetry. Upload to a telemetry service is out of scope. A service that sends OpenTelemetry data can still import the names from `@proj-airi/trace-kit/semconv`.
|
|
19
|
+
- Logs that a person reads in a terminal. Use `@guiiai/logg`.
|
|
20
|
+
|
|
21
|
+
## Entry points
|
|
22
|
+
|
|
23
|
+
| Import | Contents |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `@proj-airi/trace-kit/semconv` | Only the OpenTelemetry GenAI names: attribute keys, enum values, and metric names. It imports no other module, so a server can use it without the trace runtime. |
|
|
26
|
+
| `@proj-airi/trace-kit` | Record types, the Valibot schema, the `define*` functions, `ClockGraph`, `TraceSource`, `TraceSpan`, `SeriesWriter` for frequent series such as audio levels, and the conventions |
|
|
27
|
+
| `@proj-airi/trace-kit/collector` | `TraceCollector`: buffer, pinned set, series rings, watermarks, source lifecycle, and the flight recorder |
|
|
28
|
+
| `@proj-airi/trace-kit/projections` | `TraceReader`, the timeline, the event log, and attribute metrics |
|
|
29
|
+
| `@proj-airi/trace-kit/voice` | The voice profile: tracks, ID keys, names, attributes, metrics, and the floor transfer projection |
|
|
30
|
+
| `@proj-airi/trace-kit/agent` | The agent profile: chat turns, model requests, tools, control, and background work |
|
|
31
|
+
| `@proj-airi/trace-kit/views` | Pure view models of a trace: item lookups, related spans, track summaries, pipeline nodes, and `createTraceViewReader`. They return data, not display text. |
|
|
32
|
+
|
|
33
|
+
The root entry does not import a profile.
|
|
34
|
+
|
|
35
|
+
## Write records
|
|
36
|
+
|
|
37
|
+
A producer writes to a `TraceSink`. A module without a sink uses `noopTraceSink`.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { beginSpan, trackPath } from '@proj-airi/trace-kit'
|
|
41
|
+
import { AgentNames, AgentTracks } from '@proj-airi/trace-kit/agent'
|
|
42
|
+
|
|
43
|
+
const span = beginSpan(sink, {
|
|
44
|
+
track: trackPath(AgentTracks.LLM, { agent: 'agent' }),
|
|
45
|
+
name: AgentNames.ModelRequest,
|
|
46
|
+
ids: { conversation: sessionId, turn: turnId },
|
|
47
|
+
})
|
|
48
|
+
span.instant('llm.first_chunk')
|
|
49
|
+
span.end({ attrs: { 'llm.ttft_ms': 412 } })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- `span.begin` is sent at once, so views show open work.
|
|
53
|
+
- Carry the domain IDs that the producer already has. Do not look up an ID from another module.
|
|
54
|
+
- Use a link for a relation that is not nesting, for example `caused-by` to the span that started the work.
|
|
55
|
+
- Give `ts` as the time of the event. A record can be backdated.
|
|
56
|
+
|
|
57
|
+
`TraceSource` stamps `source`, `epoch`, and `seq`, and announces the definitions of its profiles when it starts.
|
|
58
|
+
|
|
59
|
+
## Attribute names
|
|
60
|
+
|
|
61
|
+
Attribute names are constants in the style of `@opentelemetry/semantic-conventions`: `ATTR_<NAME>`.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { ATTR_AIRI_TOPIC_ID, ATTR_GEN_AI_REQUEST_MODEL } from '@proj-airi/trace-kit'
|
|
65
|
+
|
|
66
|
+
span.update({ [ATTR_GEN_AI_REQUEST_MODEL]: model, [ATTR_AIRI_TOPIC_ID]: topicId })
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- The `gen_ai.*` names come from `src/conventions/semconv.ts`. It is generated from a pinned commit of [`open-telemetry/semantic-conventions-genai`](https://github.com/open-telemetry/semantic-conventions-genai). `SEMCONV_GENAI_COMMIT` is that commit.
|
|
70
|
+
- That repository has no release and no npm package. The GenAI exports of `@opentelemetry/semantic-conventions` are deprecated and use the old names.
|
|
71
|
+
- Do not edit `src/conventions/semconv.ts` by hand. To upgrade, change `COMMIT` in `scripts/semconv/generate.ts` and run `pnpm -F @proj-airi/trace-kit generate:semconv`. The script needs network access.
|
|
72
|
+
- Read the diff of the generated file. A removed or renamed name breaks the typecheck of each consumer. If a metric name changed, update the Grafana dashboards in `proj-airi/airi-deploy` in the same release.
|
|
73
|
+
- When OpenTelemetry publishes an npm package for the GenAI conventions, import the names from it and delete the generator.
|
|
74
|
+
- The other constants, for example `ATTR_CENSORED`, `ATTR_DECISION_LATENCY_MS`, and `ATTR_AIRI_TOPIC_ID`, are AIRI names.
|
|
75
|
+
|
|
76
|
+
## Collect and read
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
const collector = new TraceCollector({ now: () => performance.now() })
|
|
80
|
+
const timeline = createTimelineProjection()
|
|
81
|
+
const reader = new TraceReader([timeline], collector.traceClock)
|
|
82
|
+
collector.subscribe(message => reader.handle(message))
|
|
83
|
+
reader.view(timeline).tracks
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A subscription first gets the pinned set, the gaps, the buffer, and the series rings. Then it gets `caughtUp` and each later record. The host calls `collector.tick()` about once a second.
|
|
87
|
+
|
|
88
|
+
The collector builds on the event stream primitives of `@proj-airi/stream-kit`: `RetentionBuffer`, `SequenceTracker`, `Watermark`, `DemandUnion`, `createLeaseTable`, `createCooldown`, and `createCatchUpFeed`. `onWantedChange` runs only when the union of the viewer tracks changes.
|
|
89
|
+
|
|
90
|
+
A viewer that does not own the collector reads a `TraceFeedSource`: the trace clock and a `subscribe` function that sends `TraceFeedMessage` values. A host gives a local collector, a hub in another process, or a fixture as a feed source.
|
|
91
|
+
|
|
92
|
+
To read a file or the records of a test, call `reader.handleRecords(records)`.
|
|
93
|
+
|
|
94
|
+
The timeline keeps each series in a `SeriesRing` with the retention of the collector: the full rate for 5 minutes, then the minimum and the maximum of each second, for 30 minutes. A live view of a long session stays bounded. A file replay keeps every point with `createTimelineProjection({ seriesRetention: false })` or `createTraceViewReader(traceClock, { seriesRetention: false })`.
|
|
95
|
+
|
|
96
|
+
## Clocks
|
|
97
|
+
|
|
98
|
+
Each record names its clock. A source writes `clock.snapshot` records that pair its clock with another clock, and `clock.discontinuity` after sleep or a device change. `ClockGraph` fits offset and drift for each clock pair, and converts through the shortest path of pairs. A media stream clock counts frames. Declare its rate with `setUnitsPerMs`.
|
|
99
|
+
|
|
100
|
+
A discontinuity starts a new segment, and the graph finds the segment of a value by the value. So a clock name must only go forward:
|
|
101
|
+
|
|
102
|
+
- After a forward jump, for example sleep, write `clock.discontinuity` and a new `clock.snapshot`. A backdated value from before the jump still converts with the earlier snapshots.
|
|
103
|
+
- After a backward reset, for example a restarted audio context, start a new clock name, for example `mono:window#2`. Write `clock.discontinuity` for the old name and a `clock.snapshot` for the new name. Otherwise a backdated value from before the reset converts with the new segment.
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
//#region src/core/types.d.ts
|
|
2
|
+
/** Binary or large data, stored outside the record. */
|
|
3
|
+
interface TraceAssetRef {
|
|
4
|
+
$asset: string;
|
|
5
|
+
mediaType: string;
|
|
6
|
+
bytes: number;
|
|
7
|
+
}
|
|
8
|
+
type TraceValue = string | number | boolean | string[] | number[] | TraceAssetRef;
|
|
9
|
+
type TraceAttributes = Record<string, TraceValue>;
|
|
10
|
+
/** Domain IDs by defined ID key, for example `{ turn: 't_1', actor: 'user' }`. */
|
|
11
|
+
type TraceIds = Record<string, string | number>;
|
|
12
|
+
/** Identifies one record of the stream. */
|
|
13
|
+
interface TraceRef {
|
|
14
|
+
source: string;
|
|
15
|
+
epoch: string;
|
|
16
|
+
seq: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A typed relation from one record to another record, a span, or a domain ID.
|
|
20
|
+
*
|
|
21
|
+
* A link to a domain ID lets a producer link to work of another module without the `seq` of its record.
|
|
22
|
+
*/
|
|
23
|
+
interface TraceLink {
|
|
24
|
+
to: TraceRef | {
|
|
25
|
+
spanId: string;
|
|
26
|
+
} | {
|
|
27
|
+
key: string;
|
|
28
|
+
id: string | number;
|
|
29
|
+
};
|
|
30
|
+
/** A relation that `defineLink` declares, for example `caused-by` or `retry-of`. */
|
|
31
|
+
rel: string;
|
|
32
|
+
}
|
|
33
|
+
/** Fields of a record without a track: a system record or an annotation. */
|
|
34
|
+
interface TraceUntrackedRecordBase {
|
|
35
|
+
/**
|
|
36
|
+
* Stable ID of one producer, for example `window:main`, `main`, `collector`, `plugin:minecraft`, or `server:api`.
|
|
37
|
+
* It must not contain `/`, because record, span, and epoch keys join their parts with `/`.
|
|
38
|
+
*/
|
|
39
|
+
source: string;
|
|
40
|
+
/** One run of the source. A window reload or a plugin reconnect starts a new epoch. It must not contain `/`. */
|
|
41
|
+
epoch: string;
|
|
42
|
+
/** Emit order in the epoch, from 0. */
|
|
43
|
+
seq: number;
|
|
44
|
+
/** The raw value of `clock` when the event happened. It is not the emit time, so a producer can backdate it. */
|
|
45
|
+
ts: number;
|
|
46
|
+
clock: string;
|
|
47
|
+
/** The collector sets it on its own monotonic clock when it receives the record. */
|
|
48
|
+
observedTs?: number;
|
|
49
|
+
/** A stream uses it to name its actor. */
|
|
50
|
+
ids?: TraceIds;
|
|
51
|
+
attrs?: TraceAttributes;
|
|
52
|
+
}
|
|
53
|
+
/** Fields of a record about domain activity. */
|
|
54
|
+
interface TraceRecordBase extends TraceUntrackedRecordBase {
|
|
55
|
+
/** A track path that matches a track definition, for example `participants/user/speech`. */
|
|
56
|
+
track: string;
|
|
57
|
+
links?: TraceLink[];
|
|
58
|
+
/** `reported` by the producer, or `observed` from outside, for example from audio. The default is `reported`. */
|
|
59
|
+
evidence?: 'reported' | 'observed';
|
|
60
|
+
}
|
|
61
|
+
/** `status` is closed. The open `reason` gives the detail, for example `interrupted`. */
|
|
62
|
+
type TraceStatus = 'ok' | 'error' | 'cancelled';
|
|
63
|
+
/** Who or what produced an annotation. */
|
|
64
|
+
interface TraceAnnotator {
|
|
65
|
+
/** `rule`: deterministic code. `model`: an LLM or another model. `human`: a reviewer. */
|
|
66
|
+
kind: 'rule' | 'model' | 'human';
|
|
67
|
+
/** Stable name of the annotator, for example `wer` or `turn-judge`. */
|
|
68
|
+
id?: string;
|
|
69
|
+
/** The model ID when `kind` is `model`. */
|
|
70
|
+
model?: string;
|
|
71
|
+
/** A prompt hash, a rule set version, or a package version. */
|
|
72
|
+
version?: string;
|
|
73
|
+
}
|
|
74
|
+
type TraceSourceLostReason = 'ipc-closed' | 'heartbeat-timeout' | 'epoch-replaced';
|
|
75
|
+
interface TraceSourceStarted extends TraceUntrackedRecordBase {
|
|
76
|
+
type: 'source.started';
|
|
77
|
+
format: number;
|
|
78
|
+
/** For example `electron-renderer`, `electron-main`, `browser`, or `node`. */
|
|
79
|
+
runtime: string;
|
|
80
|
+
profiles: {
|
|
81
|
+
key: string;
|
|
82
|
+
version: string;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
interface TraceSourceStopped extends TraceUntrackedRecordBase {
|
|
86
|
+
type: 'source.stopped';
|
|
87
|
+
}
|
|
88
|
+
/** The collector writes it with its own identity. `of` names the lost epoch, so no `seq` of the lost source is used. */
|
|
89
|
+
interface TraceSourceLost extends TraceUntrackedRecordBase {
|
|
90
|
+
type: 'source.lost';
|
|
91
|
+
of: {
|
|
92
|
+
source: string;
|
|
93
|
+
epoch: string;
|
|
94
|
+
lastSeq: number;
|
|
95
|
+
};
|
|
96
|
+
reason: TraceSourceLostReason;
|
|
97
|
+
}
|
|
98
|
+
/** The values of two or more clocks at one instant. */
|
|
99
|
+
interface TraceClockSnapshot extends TraceUntrackedRecordBase {
|
|
100
|
+
type: 'clock.snapshot';
|
|
101
|
+
values: {
|
|
102
|
+
clock: string;
|
|
103
|
+
value: number;
|
|
104
|
+
}[];
|
|
105
|
+
uncertaintyMs: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A clock jumped or stopped, for example on sleep, a device change, or an audio context suspend.
|
|
109
|
+
*
|
|
110
|
+
* Values after a forward jump use only the later snapshots, and a backdated value from before the jump still uses the
|
|
111
|
+
* earlier ones. A backward reset makes old and new values overlap, so a value cannot tell its segment. A source whose
|
|
112
|
+
* clock goes back starts a new clock name, for example `mono:window#2`. It writes this record for the old name and a
|
|
113
|
+
* `clock.snapshot` for the new name.
|
|
114
|
+
*/
|
|
115
|
+
interface TraceClockDiscontinuity extends TraceUntrackedRecordBase {
|
|
116
|
+
type: 'clock.discontinuity';
|
|
117
|
+
of: string;
|
|
118
|
+
reason: string;
|
|
119
|
+
}
|
|
120
|
+
/** A media stream. It has its own clock. A recorded stream has a URI. */
|
|
121
|
+
interface TraceStreamStarted extends TraceUntrackedRecordBase {
|
|
122
|
+
type: 'stream.started';
|
|
123
|
+
streamId: string;
|
|
124
|
+
mediaType: string;
|
|
125
|
+
rate?: number;
|
|
126
|
+
channels?: number;
|
|
127
|
+
uri?: string;
|
|
128
|
+
}
|
|
129
|
+
interface TraceStreamStopped extends TraceUntrackedRecordBase {
|
|
130
|
+
type: 'stream.stopped';
|
|
131
|
+
streamId: string;
|
|
132
|
+
}
|
|
133
|
+
interface TraceSpanBegin extends TraceRecordBase {
|
|
134
|
+
type: 'span.begin';
|
|
135
|
+
spanId: string;
|
|
136
|
+
/** Real nesting only. Any other relation is a link. */
|
|
137
|
+
parentSpanId?: string;
|
|
138
|
+
name: string;
|
|
139
|
+
/** The viewer marks the open span as overdue after this many milliseconds. It does not end the span. */
|
|
140
|
+
deadlineMs?: number;
|
|
141
|
+
}
|
|
142
|
+
/** Adds attributes to an open span. They merge shallowly in `seq` order, and the last write of a key wins. */
|
|
143
|
+
interface TraceSpanUpdate extends TraceRecordBase {
|
|
144
|
+
type: 'span.update';
|
|
145
|
+
spanId: string;
|
|
146
|
+
}
|
|
147
|
+
interface TraceSpanEnd extends TraceRecordBase {
|
|
148
|
+
type: 'span.end';
|
|
149
|
+
spanId: string;
|
|
150
|
+
status: TraceStatus;
|
|
151
|
+
reason?: string;
|
|
152
|
+
message?: string;
|
|
153
|
+
}
|
|
154
|
+
/** A span that the producer measures after it ends. Chrome trace `X`. */
|
|
155
|
+
interface TraceSpanComplete extends TraceRecordBase {
|
|
156
|
+
type: 'span.complete';
|
|
157
|
+
spanId: string;
|
|
158
|
+
parentSpanId?: string;
|
|
159
|
+
name: string;
|
|
160
|
+
durationMs: number;
|
|
161
|
+
status: TraceStatus;
|
|
162
|
+
reason?: string;
|
|
163
|
+
}
|
|
164
|
+
/** With `spanId`, the instant belongs to that span. Without it, the instant belongs to its track only. */
|
|
165
|
+
interface TraceInstant extends TraceRecordBase {
|
|
166
|
+
type: 'instant';
|
|
167
|
+
name: string;
|
|
168
|
+
spanId?: string;
|
|
169
|
+
}
|
|
170
|
+
/** A state change. `from` is the state that it leaves. The trigger is a `caused-by` link. */
|
|
171
|
+
interface TraceState extends TraceRecordBase {
|
|
172
|
+
type: 'state';
|
|
173
|
+
value: string;
|
|
174
|
+
from?: string;
|
|
175
|
+
}
|
|
176
|
+
/** Values that change rarely. A frequent series uses `sample.chunk`. */
|
|
177
|
+
interface TraceSample extends TraceRecordBase {
|
|
178
|
+
type: 'sample';
|
|
179
|
+
values: Record<string, number>;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* About one second of a series. `ts` is the time of the first point.
|
|
183
|
+
* A regular series gives `intervalMs`. An irregular series gives `offsetsMs` from `ts`.
|
|
184
|
+
*/
|
|
185
|
+
interface TraceSampleChunk extends TraceRecordBase {
|
|
186
|
+
type: 'sample.chunk';
|
|
187
|
+
intervalMs?: number;
|
|
188
|
+
offsetsMs?: number[];
|
|
189
|
+
values: Record<string, number[]>;
|
|
190
|
+
}
|
|
191
|
+
interface TraceLog extends TraceRecordBase {
|
|
192
|
+
type: 'log';
|
|
193
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
194
|
+
message: string;
|
|
195
|
+
}
|
|
196
|
+
/** Announces one definition before the first record that uses it, and again when it changes. */
|
|
197
|
+
interface TraceDefinitionRecord extends TraceUntrackedRecordBase {
|
|
198
|
+
type: 'definition';
|
|
199
|
+
kind: TraceDefinitionKind;
|
|
200
|
+
key: string;
|
|
201
|
+
data: unknown;
|
|
202
|
+
}
|
|
203
|
+
/** A label, score, or evaluation that is added later. A producer decision is not an annotation. */
|
|
204
|
+
interface TraceAnnotation extends TraceUntrackedRecordBase {
|
|
205
|
+
type: 'annotation';
|
|
206
|
+
target: {
|
|
207
|
+
ref?: TraceRef;
|
|
208
|
+
spanId?: string;
|
|
209
|
+
ids?: TraceIds;
|
|
210
|
+
};
|
|
211
|
+
name: string;
|
|
212
|
+
value?: TraceValue;
|
|
213
|
+
label?: string;
|
|
214
|
+
explanation?: string;
|
|
215
|
+
annotator: TraceAnnotator;
|
|
216
|
+
}
|
|
217
|
+
type TraceRecord = TraceSourceStarted | TraceSourceStopped | TraceSourceLost | TraceClockSnapshot | TraceClockDiscontinuity | TraceStreamStarted | TraceStreamStopped | TraceSpanBegin | TraceSpanUpdate | TraceSpanEnd | TraceSpanComplete | TraceInstant | TraceState | TraceSample | TraceSampleChunk | TraceLog | TraceDefinitionRecord | TraceAnnotation;
|
|
218
|
+
type TraceRecordType = TraceRecord['type'];
|
|
219
|
+
/** A record with a track. */
|
|
220
|
+
type TraceTrackedRecord = Extract<TraceRecord, {
|
|
221
|
+
track: string;
|
|
222
|
+
}>;
|
|
223
|
+
/** A record that describes the trace itself. */
|
|
224
|
+
type TraceSystemRecord = Extract<TraceRecord, {
|
|
225
|
+
type: 'source.started' | 'source.stopped' | 'source.lost' | 'clock.snapshot' | 'clock.discontinuity' | 'stream.started' | 'stream.stopped' | 'definition';
|
|
226
|
+
}>;
|
|
227
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
228
|
+
/**
|
|
229
|
+
* A record as a producer emits it. The source stamps `source`, `epoch`, and `seq`. The collector sets `observedTs`.
|
|
230
|
+
* `clock` is optional: the source fills in its own monotonic clock.
|
|
231
|
+
*/
|
|
232
|
+
type TraceRecordInput = DistributiveOmit<TraceRecord, 'source' | 'epoch' | 'seq' | 'observedTs' | 'clock'> & {
|
|
233
|
+
clock?: string;
|
|
234
|
+
};
|
|
235
|
+
/** One string, or one string for each BCP 47 locale. */
|
|
236
|
+
type Localizable = string | Record<string, string>;
|
|
237
|
+
type TraceDefinitionKind = 'track' | 'attribute' | 'id-key' | 'link' | 'end-reason' | 'clock' | 'metric' | 'score' | 'pipeline' | 'preset' | 'profile';
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/core/definitions.d.ts
|
|
240
|
+
/** Label and hover text of a key. A key without them still works, and the viewer shows the raw key. */
|
|
241
|
+
interface TraceText {
|
|
242
|
+
label?: Localizable;
|
|
243
|
+
/** What the key means and how to read it. */
|
|
244
|
+
description?: Localizable;
|
|
245
|
+
}
|
|
246
|
+
type TraceTrackRender = 'spans' | 'instants' | 'state' | 'series';
|
|
247
|
+
/**
|
|
248
|
+
* How the spans of a track show on the lane of their entity.
|
|
249
|
+
*
|
|
250
|
+
* - `waiting`: the entity holds a turn or a request and waits, for example for a sub-agent. Drawn hatched, below the rest.
|
|
251
|
+
* - `thinking`: work before output, such as a model request before its first chunk, recognition, or a judgement. Drawn light.
|
|
252
|
+
* - `active`: output or an action, such as speech, generation, playback, or a tool call. Drawn solid, on top.
|
|
253
|
+
*/
|
|
254
|
+
type TraceActivity = 'waiting' | 'thinking' | 'active';
|
|
255
|
+
interface TraceTrackDefinition extends TraceText {
|
|
256
|
+
/** A path pattern. `{key}` matches one segment, named by an ID key, for example `participants/{actor}/speech`. */
|
|
257
|
+
path: string;
|
|
258
|
+
/** An open string. Profiles give their own groups. The viewer groups lanes by it. */
|
|
259
|
+
group: string;
|
|
260
|
+
/** Lane order inside the group. Lower comes first. */
|
|
261
|
+
order: number;
|
|
262
|
+
/** One track can combine kinds, for example `['spans', 'state']`. */
|
|
263
|
+
render: TraceTrackRender[];
|
|
264
|
+
color?: string;
|
|
265
|
+
icon?: string;
|
|
266
|
+
unit?: string;
|
|
267
|
+
/** The values of a state track, in legend order. */
|
|
268
|
+
states?: string[];
|
|
269
|
+
/**
|
|
270
|
+
* `single`: one source writes the state of this track, and a record from another source is a warning.
|
|
271
|
+
* `any`: every source can write it.
|
|
272
|
+
* @default 'single'
|
|
273
|
+
*/
|
|
274
|
+
stateOwner?: 'single' | 'any';
|
|
275
|
+
/** Retention and sampling hints for the collector. */
|
|
276
|
+
retention?: {
|
|
277
|
+
keep: 'latest' | 'window';
|
|
278
|
+
maxHz?: number;
|
|
279
|
+
fullRateS?: number;
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* The `{key}` of the path that names the entity of the track, for example `actor` or `agent`. The viewer draws one
|
|
283
|
+
* lane for each entity value, and tracks of different patterns with the same value share it.
|
|
284
|
+
*/
|
|
285
|
+
entity?: string;
|
|
286
|
+
/** How the spans of the track show on the entity lane. A track without it shows only in the expanded rows. */
|
|
287
|
+
activity?: TraceActivity;
|
|
288
|
+
/** An instant inside a span that changes its activity, for example `llm.first_chunk` from `thinking` to `active`. */
|
|
289
|
+
activitySplit?: {
|
|
290
|
+
instant: string;
|
|
291
|
+
activity: TraceActivity;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* What a span of this track shows on its item in the timeline. Leave it unset when the track name already tells what
|
|
295
|
+
* a span is. Set `name` when the span names differ in a useful way, for example the tool name of a tool call. The
|
|
296
|
+
* hover card shows the name in both cases.
|
|
297
|
+
*/
|
|
298
|
+
itemLabel?: 'name';
|
|
299
|
+
}
|
|
300
|
+
interface TraceAttributeDefinition extends TraceText {
|
|
301
|
+
key: string;
|
|
302
|
+
type: 'string' | 'number' | 'boolean' | 'list' | 'asset' | 'json';
|
|
303
|
+
unit?: string;
|
|
304
|
+
/**
|
|
305
|
+
* `content` is user content. Files on disk and exports remove it unless a developer setting keeps it.
|
|
306
|
+
* `secret` never leaves the process.
|
|
307
|
+
*/
|
|
308
|
+
sensitivity: 'none' | 'content' | 'secret';
|
|
309
|
+
aggregatable?: boolean;
|
|
310
|
+
}
|
|
311
|
+
interface TraceIdKeyDefinition extends TraceText {
|
|
312
|
+
key: string;
|
|
313
|
+
}
|
|
314
|
+
interface TraceLinkDefinition extends TraceText {
|
|
315
|
+
rel: string;
|
|
316
|
+
}
|
|
317
|
+
interface TraceEndReasonDefinition extends TraceText {
|
|
318
|
+
reason: string;
|
|
319
|
+
}
|
|
320
|
+
interface TraceClockDefinition extends TraceText {
|
|
321
|
+
/** A key pattern, for example `mono:window:{window}` or `audio:capture:{stream}`. */
|
|
322
|
+
pattern: string;
|
|
323
|
+
/** `ms` for system clocks. `frames` for media streams. */
|
|
324
|
+
unit: 'ms' | 'frames';
|
|
325
|
+
/** Whether the clock stops during system sleep, when that is known. */
|
|
326
|
+
stopsInSleep?: boolean;
|
|
327
|
+
}
|
|
328
|
+
/** How a metric finds its values. */
|
|
329
|
+
type TraceMetricValue = {
|
|
330
|
+
kind: 'attribute';
|
|
331
|
+
track: string;
|
|
332
|
+
record: TraceRecordType;
|
|
333
|
+
attribute: string;
|
|
334
|
+
} |
|
|
335
|
+
/** A first-party projection computes the value. */
|
|
336
|
+
{
|
|
337
|
+
kind: 'derived';
|
|
338
|
+
projection: string;
|
|
339
|
+
};
|
|
340
|
+
type TraceAggregate = 'p50' | 'p95' | 'p99' | 'mean' | 'sum' | 'count';
|
|
341
|
+
interface TraceMetricDefinition extends TraceText {
|
|
342
|
+
key: string;
|
|
343
|
+
unit?: string;
|
|
344
|
+
/** Whether a larger value is better (`higher`) or a smaller value is better (`lower`). */
|
|
345
|
+
better?: 'higher' | 'lower';
|
|
346
|
+
/** ID keys that split the metric, for example `['from', 'to']` for a matrix of participant pairs. */
|
|
347
|
+
dimensions?: string[];
|
|
348
|
+
/** @default ['p50', 'p95'] */
|
|
349
|
+
aggregate?: TraceAggregate[];
|
|
350
|
+
/**
|
|
351
|
+
* `session` keeps the values of the whole session. `window` uses the buffer only.
|
|
352
|
+
* @default 'window'
|
|
353
|
+
*/
|
|
354
|
+
scope?: 'session' | 'window';
|
|
355
|
+
value: TraceMetricValue;
|
|
356
|
+
}
|
|
357
|
+
/** An annotation name for a score from 0 to 1, where higher is better. */
|
|
358
|
+
interface TraceScoreDefinition extends TraceText {
|
|
359
|
+
key: string;
|
|
360
|
+
/** What usually produces the value. An annotation can name another annotator. */
|
|
361
|
+
annotator?: TraceAnnotator;
|
|
362
|
+
}
|
|
363
|
+
interface TracePipelineStage {
|
|
364
|
+
key: string;
|
|
365
|
+
label: Localizable;
|
|
366
|
+
/** Track path patterns. A pattern with `{key}` gives one stage instance for each value. */
|
|
367
|
+
tracks: string[];
|
|
368
|
+
}
|
|
369
|
+
interface TracePipelineDefinition extends TraceText {
|
|
370
|
+
key: string;
|
|
371
|
+
stages: TracePipelineStage[];
|
|
372
|
+
/** `kind` is open. The conventions define `cmd`, `data`, `audio`, and `signal`. Cycles are allowed. */
|
|
373
|
+
edges: {
|
|
374
|
+
from: string;
|
|
375
|
+
to: string;
|
|
376
|
+
kind: string;
|
|
377
|
+
name: string;
|
|
378
|
+
}[];
|
|
379
|
+
}
|
|
380
|
+
/** A view preset: the track paths to show, and the layout. */
|
|
381
|
+
interface TracePresetDefinition extends TraceText {
|
|
382
|
+
key: string;
|
|
383
|
+
/** Track path patterns. */
|
|
384
|
+
tracks: string[];
|
|
385
|
+
/** @default 'timeline' */
|
|
386
|
+
layout?: 'timeline' | 'pipeline' | 'log';
|
|
387
|
+
}
|
|
388
|
+
interface TraceProfileDefinition extends TraceText {
|
|
389
|
+
/** `voice`, `agent`, or `plugin:<id>`. */
|
|
390
|
+
key: string;
|
|
391
|
+
version: string;
|
|
392
|
+
tracks?: TraceTrackDefinition[];
|
|
393
|
+
attributes?: TraceAttributeDefinition[];
|
|
394
|
+
idKeys?: TraceIdKeyDefinition[];
|
|
395
|
+
links?: TraceLinkDefinition[];
|
|
396
|
+
endReasons?: TraceEndReasonDefinition[];
|
|
397
|
+
clocks?: TraceClockDefinition[];
|
|
398
|
+
metrics?: TraceMetricDefinition[];
|
|
399
|
+
scores?: TraceScoreDefinition[];
|
|
400
|
+
pipelines?: TracePipelineDefinition[];
|
|
401
|
+
presets?: TracePresetDefinition[];
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/core/feed.d.ts
|
|
405
|
+
/** The kept points of one series, on the trace clock. Old points are reduced to the min and max of each second. */
|
|
406
|
+
interface TraceSeriesMessage {
|
|
407
|
+
kind: 'series';
|
|
408
|
+
source: string;
|
|
409
|
+
track: string;
|
|
410
|
+
key: string;
|
|
411
|
+
times: number[];
|
|
412
|
+
values: number[];
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* One message of a collector feed to a viewer.
|
|
416
|
+
*
|
|
417
|
+
* A subscription first sends the pinned set (`pinned: true`), the gaps, the buffered records, and the series rings.
|
|
418
|
+
* Then it sends `caughtUp`, and after that each later record.
|
|
419
|
+
* `gap` and `caughtUp` are feed markers, not trace records. The disk recording does not write them.
|
|
420
|
+
*/
|
|
421
|
+
type TraceFeedMessage = {
|
|
422
|
+
kind: 'record';
|
|
423
|
+
record: TraceRecord;
|
|
424
|
+
/** The record came from the pinned set, which stays outside the buffer limits. */
|
|
425
|
+
pinned?: boolean;
|
|
426
|
+
/** The record arrived after the watermark of its source. It changes the view with a `late` mark. */
|
|
427
|
+
late?: boolean;
|
|
428
|
+
} |
|
|
429
|
+
/** The buffer evicted records in this range of the trace clock. */
|
|
430
|
+
{
|
|
431
|
+
kind: 'gap';
|
|
432
|
+
from: number;
|
|
433
|
+
to: number;
|
|
434
|
+
} | TraceSeriesMessage |
|
|
435
|
+
/**
|
|
436
|
+
* The end of the backlog. `cursor` has the last `seq` of each `source/epoch`. `now` is the trace clock at that moment,
|
|
437
|
+
* so a viewer can run a live clock before the next record arrives.
|
|
438
|
+
*/
|
|
439
|
+
{
|
|
440
|
+
kind: 'caughtUp';
|
|
441
|
+
cursor: Record<string, number>;
|
|
442
|
+
traceClock: string;
|
|
443
|
+
now?: number;
|
|
444
|
+
};
|
|
445
|
+
/**
|
|
446
|
+
* A collector feed that a tracer view reads.
|
|
447
|
+
*
|
|
448
|
+
* The host sets one: a local collector, a hub in another process, or a fixture in a story. The view store subscribes
|
|
449
|
+
* to it and gives each message to its reader.
|
|
450
|
+
*/
|
|
451
|
+
interface TraceFeedSource {
|
|
452
|
+
/** The clock that the collector converts every value to. A reader converts to the same clock. */
|
|
453
|
+
traceClock: string;
|
|
454
|
+
/**
|
|
455
|
+
* Calls `onMessage` with the backlog and then with each later message.
|
|
456
|
+
* `tracks` are the track paths that the view shows, so producers can skip costly series of other tracks.
|
|
457
|
+
*/
|
|
458
|
+
subscribe: (onMessage: (message: TraceFeedMessage) => void, tracks: string[] | '*') => Promise<{
|
|
459
|
+
unsubscribe: () => void;
|
|
460
|
+
setTracks: (tracks: string[] | '*') => void;
|
|
461
|
+
}>;
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
464
|
+
//#region src/core/clock.d.ts
|
|
465
|
+
interface ClockConversion {
|
|
466
|
+
value: number;
|
|
467
|
+
/** A clock on the path has no measured latency, or a pair has only one sample of a non-ms clock. */
|
|
468
|
+
estimated: boolean;
|
|
469
|
+
}
|
|
470
|
+
interface ClockGraphOptions {
|
|
471
|
+
/**
|
|
472
|
+
* Snapshots in the drift fit of each clock pair. A drift of 50 ppm adds 3 ms each minute, so offset alone is not
|
|
473
|
+
* enough for long sessions.
|
|
474
|
+
* @default 16
|
|
475
|
+
*/
|
|
476
|
+
fitWindow?: number;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Converts values of any clock to one trace clock.
|
|
480
|
+
*
|
|
481
|
+
* State model:
|
|
482
|
+
* - Each `clock.snapshot` adds one sample to every clock pair in it, in both directions.
|
|
483
|
+
* - A `clock.discontinuity` starts a new segment for each pair of that clock. Values after it use only later samples.
|
|
484
|
+
* A segment is found by value, so a clock must only jump forward. A clock that goes back gets a new name.
|
|
485
|
+
* - A conversion walks the shortest path of pairs from the clock to the trace clock, and composes the fits.
|
|
486
|
+
*
|
|
487
|
+
* The graph does not read a clock. It is a pure function of the snapshots that it received.
|
|
488
|
+
*/
|
|
489
|
+
declare class ClockGraph {
|
|
490
|
+
readonly traceClock: string;
|
|
491
|
+
private readonly options;
|
|
492
|
+
/** Units of each clock in one millisecond. A media clock in frames at 48 kHz has 48. */
|
|
493
|
+
private readonly unitsPerMs;
|
|
494
|
+
private readonly pairs;
|
|
495
|
+
private readonly paths;
|
|
496
|
+
constructor(traceClock: string, options?: ClockGraphOptions);
|
|
497
|
+
/** Declares the unit of a clock. A clock without a declared unit counts in milliseconds. */
|
|
498
|
+
setUnitsPerMs(clock: string, unitsPerMs: number): void;
|
|
499
|
+
addSnapshot(values: {
|
|
500
|
+
clock: string;
|
|
501
|
+
value: number;
|
|
502
|
+
}[], uncertaintyMs: number, estimated?: boolean): void;
|
|
503
|
+
/** Starts a new segment for every pair of `clock`. Earlier samples stay for values before the jump. */
|
|
504
|
+
addDiscontinuity(clock: string): void;
|
|
505
|
+
/** Forgets every unit, snapshot, and discontinuity, for example before a reader reads another trace. */
|
|
506
|
+
clear(): void;
|
|
507
|
+
/** Converts a value of `clock` to the trace clock. Returns `undefined` when no snapshot path exists. */
|
|
508
|
+
convert(clock: string, value: number): ClockConversion | undefined;
|
|
509
|
+
private addSample;
|
|
510
|
+
/**
|
|
511
|
+
* Fits `to = from * slope + offset` over the samples of the segment that holds `value`.
|
|
512
|
+
*
|
|
513
|
+
* The segment is the latest one that starts at or before `value`. This is correct when a clock only jumps forward.
|
|
514
|
+
* After a backward reset, a value from before the reset can match the new segment. So a clock that goes back must
|
|
515
|
+
* get a new name, as the `clock.discontinuity` record type says.
|
|
516
|
+
*
|
|
517
|
+
* One sample gives the offset only, with the slope from the declared units. Two or more samples give a weighted
|
|
518
|
+
* least squares fit, which also measures drift. Each sample weighs `1 / uncertainty²`.
|
|
519
|
+
*/
|
|
520
|
+
private fit;
|
|
521
|
+
/** Breadth-first search from `clock` to the trace clock. The result is cached until a new pair appears. */
|
|
522
|
+
private pathTo;
|
|
523
|
+
}
|
|
524
|
+
interface ClockPingSample {
|
|
525
|
+
/** The local clock value at the middle of the round trip. */
|
|
526
|
+
local: number;
|
|
527
|
+
/** The remote clock value that the reply carried. */
|
|
528
|
+
remote: number;
|
|
529
|
+
/** Half the round trip. */
|
|
530
|
+
uncertaintyMs: number;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Runs a ping exchange with a remote clock and returns the sample with the smallest round trip.
|
|
534
|
+
*
|
|
535
|
+
* Each ping asks the remote side for its clock value. The local value at the middle of the round trip pairs with it.
|
|
536
|
+
* Half the round trip is the uncertainty. A failed ping is skipped. Returns `undefined` when every ping failed.
|
|
537
|
+
*/
|
|
538
|
+
declare function measureClockOffset(options: {
|
|
539
|
+
ping: () => Promise<number>;
|
|
540
|
+
now: () => number;
|
|
541
|
+
/** @default 5 */
|
|
542
|
+
count?: number;
|
|
543
|
+
}): Promise<ClockPingSample | undefined>;
|
|
544
|
+
//#endregion
|
|
545
|
+
export { TraceSpanUpdate as $, TraceAttributes as A, TraceRecordBase as B, TraceText as C, TraceAnnotation as D, Localizable as E, TraceIds as F, TraceSampleChunk as G, TraceRecordType as H, TraceInstant as I, TraceSourceStarted as J, TraceSourceLost as K, TraceLink as L, TraceClockSnapshot as M, TraceDefinitionKind as N, TraceAnnotator as O, TraceDefinitionRecord as P, TraceSpanEnd as Q, TraceLog as R, TraceScoreDefinition as S, TraceTrackRender as T, TraceRef as U, TraceRecordInput as V, TraceSample as W, TraceSpanBegin as X, TraceSourceStopped as Y, TraceSpanComplete as Z, TraceMetricValue as _, measureClockOffset as a, TraceTrackedRecord as at, TracePresetDefinition as b, TraceSeriesMessage as c, TraceAttributeDefinition as d, TraceState as et, TraceClockDefinition as f, TraceMetricDefinition as g, TraceLinkDefinition as h, ClockPingSample as i, TraceSystemRecord as it, TraceClockDiscontinuity as j, TraceAssetRef as k, TraceActivity as l, TraceIdKeyDefinition as m, ClockGraph as n, TraceStreamStarted as nt, TraceFeedMessage as o, TraceUntrackedRecordBase as ot, TraceEndReasonDefinition as p, TraceSourceLostReason as q, ClockGraphOptions as r, TraceStreamStopped as rt, TraceFeedSource as s, TraceValue as st, ClockConversion as t, TraceStatus as tt, TraceAggregate as u, TracePipelineDefinition as v, TraceTrackDefinition as w, TraceProfileDefinition as x, TracePipelineStage as y, TraceRecord as z };
|