@runtypelabs/flue-otel 0.1.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/index.cjs +939 -0
- package/dist/index.d.cts +576 -0
- package/dist/index.d.ts +576 -0
- package/dist/index.mjs +911 -0
- package/package.json +75 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Flue observation surface this package reads, declared structurally.
|
|
5
|
+
*
|
|
6
|
+
* ## Why these are hand-declared rather than imported from `@flue/runtime`
|
|
7
|
+
*
|
|
8
|
+
* Two reasons, and the second is the load-bearing one.
|
|
9
|
+
*
|
|
10
|
+
* 1. `@flue/runtime` is a PEER dependency with no dev counterpart: its tree is
|
|
11
|
+
* ~43 packages, and pulling it into the root lockfile for type resolution
|
|
12
|
+
* alone is the cost this monorepo already refuses for `examples/flue-persona`
|
|
13
|
+
* (root `CLAUDE.md`, "examples/* is deliberately NOT a pnpm workspace").
|
|
14
|
+
*
|
|
15
|
+
* 2. The package supports BOTH the 1.x and 2.x lines from one entry point, and
|
|
16
|
+
* their declarations are not the same type. 1.x has `run_start`/`run_end`,
|
|
17
|
+
* envelope `runId`/`dispatchId`, and `FlueObservationDetail.toolType`; 2.x
|
|
18
|
+
* removes all of those and adds `toolcall_delta`, the `submission_*` family,
|
|
19
|
+
* and `FlueErrorInfo.meta`/`.stack`. Importing EITHER line's types would make
|
|
20
|
+
* the compiler enforce a shape the other line does not have. What this
|
|
21
|
+
* package actually consumes is the INTERSECTION — the observation plane Flue
|
|
22
|
+
* publishes as stable — so that is what is declared here.
|
|
23
|
+
*
|
|
24
|
+
* ## What is safe to read
|
|
25
|
+
*
|
|
26
|
+
* Flue's published stability boundary (flueframework.com/docs/reference/events)
|
|
27
|
+
* covers the event type names, the envelope/correlation fields, and the
|
|
28
|
+
* normalized `turn_request` / `turn` / `tool_*` / `task` / `operation` /
|
|
29
|
+
* `compaction` / `log` / `submission_settled` payloads. It EXPLICITLY excludes
|
|
30
|
+
* `AgentMessage`, which appears on `message_start` / `message_end` /
|
|
31
|
+
* `turn_messages` / `agent_end` — so none of those four are read here, and the
|
|
32
|
+
* type below does not even name the field. That exclusion is why the stock
|
|
33
|
+
* projection survived the 1.x → 2.x rewrite unchanged, and it is the same
|
|
34
|
+
* reason this one will.
|
|
35
|
+
*
|
|
36
|
+
* Fields the two lines disagree on are declared OPTIONAL and probed at runtime
|
|
37
|
+
* (`compat.ts`), never version-compared.
|
|
38
|
+
*/
|
|
39
|
+
/** Correlation fields stamped onto every delivered observation. */
|
|
40
|
+
interface FlueEventEnvelope {
|
|
41
|
+
/** Durable event-format version. `3` on both supported lines. */
|
|
42
|
+
v?: number;
|
|
43
|
+
eventIndex?: number;
|
|
44
|
+
timestamp?: string;
|
|
45
|
+
instanceId?: string;
|
|
46
|
+
submissionId?: string;
|
|
47
|
+
agentName?: string;
|
|
48
|
+
conversationId?: string;
|
|
49
|
+
session?: string;
|
|
50
|
+
parentSession?: string;
|
|
51
|
+
taskId?: string;
|
|
52
|
+
harness?: string;
|
|
53
|
+
operationId?: string;
|
|
54
|
+
turnId?: string;
|
|
55
|
+
}
|
|
56
|
+
/** Token counts and cost for one model turn. Byte-identical on both lines. */
|
|
57
|
+
interface FluePromptUsage {
|
|
58
|
+
input: number;
|
|
59
|
+
output: number;
|
|
60
|
+
cacheRead: number;
|
|
61
|
+
cacheWrite: number;
|
|
62
|
+
totalTokens: number;
|
|
63
|
+
cost?: {
|
|
64
|
+
input: number;
|
|
65
|
+
output: number;
|
|
66
|
+
cacheRead: number;
|
|
67
|
+
cacheWrite: number;
|
|
68
|
+
total: number;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** The non-content half of a model request. */
|
|
72
|
+
interface FlueModelRequestInfo {
|
|
73
|
+
providerId?: string;
|
|
74
|
+
providerName?: string;
|
|
75
|
+
requestedModel?: string;
|
|
76
|
+
api?: string;
|
|
77
|
+
serverAddress?: string;
|
|
78
|
+
serverPort?: number;
|
|
79
|
+
reasoningLevel?: string;
|
|
80
|
+
maxTokens?: number;
|
|
81
|
+
temperature?: number;
|
|
82
|
+
contextCompacted?: true;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The request's payload half. Only `tools` is read, and only for its PRESENCE:
|
|
86
|
+
* `runtype.tools.reported` is the assertion "my tool set is complete", which
|
|
87
|
+
* needs to know the array exists, not what is in it. Message content is
|
|
88
|
+
* deliberately untouched in this release — see the README's content section.
|
|
89
|
+
*/
|
|
90
|
+
interface FlueModelRequestInput {
|
|
91
|
+
tools?: unknown[];
|
|
92
|
+
}
|
|
93
|
+
interface FlueModelRequest extends FlueModelRequestInfo {
|
|
94
|
+
input?: FlueModelRequestInput;
|
|
95
|
+
}
|
|
96
|
+
interface FlueModelResponse {
|
|
97
|
+
responseId?: string;
|
|
98
|
+
responseModel?: string;
|
|
99
|
+
usage?: FluePromptUsage;
|
|
100
|
+
finishReason?: string;
|
|
101
|
+
/** 2.x only. The provider's raw finish value before normalization. */
|
|
102
|
+
providerFinishReason?: string;
|
|
103
|
+
error?: FlueErrorInfo;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Classified error details on a live observation.
|
|
107
|
+
*
|
|
108
|
+
* `stack` is declared so the type is honest about what arrives, and is NEVER
|
|
109
|
+
* read: it exposes filesystem paths and deployment layout, which is exactly the
|
|
110
|
+
* class of value a vendor package landing in a healthcare stack must not put on
|
|
111
|
+
* a wire the customer did not opt into. Flue's own durable serializers drop it
|
|
112
|
+
* for the same reason.
|
|
113
|
+
*/
|
|
114
|
+
interface FlueErrorInfo {
|
|
115
|
+
type?: string;
|
|
116
|
+
name?: string;
|
|
117
|
+
message?: string;
|
|
118
|
+
stack?: string;
|
|
119
|
+
meta?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
/** 1.x-only implementation-class axis; absent on 2.x. */
|
|
122
|
+
type FlueToolSemanticType = 'function' | 'extension' | 'datastore';
|
|
123
|
+
/** Who INITIATED a tool call. Present on both lines. NOT a tool class. */
|
|
124
|
+
type FlueToolOrigin = 'model' | 'caller' | 'framework' | 'adapter';
|
|
125
|
+
/**
|
|
126
|
+
* The per-observation detail sidecar. Every field is optional on both lines;
|
|
127
|
+
* `toolType` exists only on 1.x, which is what `compat.ts` probes for.
|
|
128
|
+
*/
|
|
129
|
+
interface FlueObservationDetail {
|
|
130
|
+
origin?: FlueToolOrigin;
|
|
131
|
+
toolType?: FlueToolSemanticType;
|
|
132
|
+
toolCallId?: string;
|
|
133
|
+
errorInfo?: FlueErrorInfo;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The event variants this package acts on. Every other variant Flue emits is
|
|
137
|
+
* ignored, which is why they are not declared: an unhandled `type` falls
|
|
138
|
+
* through the projection's switch untouched.
|
|
139
|
+
*/
|
|
140
|
+
type FlueEventVariant = {
|
|
141
|
+
type: 'operation_start';
|
|
142
|
+
operationId: string;
|
|
143
|
+
operationKind: string;
|
|
144
|
+
} | {
|
|
145
|
+
type: 'operation';
|
|
146
|
+
operationId: string;
|
|
147
|
+
operationKind: string;
|
|
148
|
+
durationMs?: number;
|
|
149
|
+
isError?: boolean;
|
|
150
|
+
error?: unknown;
|
|
151
|
+
usage?: FluePromptUsage;
|
|
152
|
+
} | {
|
|
153
|
+
type: 'task_start';
|
|
154
|
+
taskId: string;
|
|
155
|
+
prompt?: string;
|
|
156
|
+
agent?: string;
|
|
157
|
+
} | {
|
|
158
|
+
type: 'task';
|
|
159
|
+
taskId: string;
|
|
160
|
+
agent?: string;
|
|
161
|
+
isError?: boolean;
|
|
162
|
+
result?: unknown;
|
|
163
|
+
} | {
|
|
164
|
+
type: 'compaction_start';
|
|
165
|
+
reason?: string;
|
|
166
|
+
estimatedTokens?: number;
|
|
167
|
+
} | {
|
|
168
|
+
type: 'compaction';
|
|
169
|
+
isError?: boolean;
|
|
170
|
+
error?: unknown;
|
|
171
|
+
usage?: FluePromptUsage;
|
|
172
|
+
} | {
|
|
173
|
+
type: 'turn_request';
|
|
174
|
+
turnId: string;
|
|
175
|
+
purpose?: string;
|
|
176
|
+
request: FlueModelRequest;
|
|
177
|
+
} | {
|
|
178
|
+
type: 'turn';
|
|
179
|
+
turnId: string;
|
|
180
|
+
purpose?: string;
|
|
181
|
+
durationMs?: number;
|
|
182
|
+
request?: FlueModelRequestInfo;
|
|
183
|
+
response: FlueModelResponse;
|
|
184
|
+
isError?: boolean;
|
|
185
|
+
} | {
|
|
186
|
+
type: 'tool_start';
|
|
187
|
+
toolName: string;
|
|
188
|
+
toolCallId: string;
|
|
189
|
+
} | {
|
|
190
|
+
type: 'tool';
|
|
191
|
+
toolName: string;
|
|
192
|
+
toolCallId: string;
|
|
193
|
+
isError?: boolean;
|
|
194
|
+
result?: unknown;
|
|
195
|
+
durationMs?: number;
|
|
196
|
+
} | {
|
|
197
|
+
type: 'submission_settled';
|
|
198
|
+
submissionId: string;
|
|
199
|
+
outcome: string;
|
|
200
|
+
} | {
|
|
201
|
+
type: string;
|
|
202
|
+
};
|
|
203
|
+
/** One delivered observation: an event, its envelope, and its detail sidecar. */
|
|
204
|
+
type FlueObservation = FlueEventVariant & FlueEventEnvelope & FlueObservationDetail;
|
|
205
|
+
/**
|
|
206
|
+
* Context handed to `observe()` alongside each observation. Only its presence
|
|
207
|
+
* matters here — the fields this package needs all live on the envelope.
|
|
208
|
+
*/
|
|
209
|
+
interface FlueEventContext {
|
|
210
|
+
readonly id?: string;
|
|
211
|
+
readonly agentName?: string;
|
|
212
|
+
}
|
|
213
|
+
/** W3C trace context offered by the host on a detached execution. */
|
|
214
|
+
interface FlueTraceCarrier {
|
|
215
|
+
traceparent: string;
|
|
216
|
+
tracestate?: string;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* The operation the interceptor wraps. `coordinator` exists on 2.x only; the
|
|
220
|
+
* open arm makes an unknown future kind fall through rather than crash.
|
|
221
|
+
*/
|
|
222
|
+
type FlueExecutionOperation = {
|
|
223
|
+
type: 'agent';
|
|
224
|
+
operationId: string;
|
|
225
|
+
operationKind?: string;
|
|
226
|
+
} | {
|
|
227
|
+
type: 'model';
|
|
228
|
+
turnId: string;
|
|
229
|
+
} | {
|
|
230
|
+
type: 'tool';
|
|
231
|
+
toolCallId: string;
|
|
232
|
+
toolName?: string;
|
|
233
|
+
} | {
|
|
234
|
+
type: 'task';
|
|
235
|
+
taskId: string;
|
|
236
|
+
} | {
|
|
237
|
+
type: 'coordinator';
|
|
238
|
+
phase?: string;
|
|
239
|
+
} | {
|
|
240
|
+
type: string;
|
|
241
|
+
};
|
|
242
|
+
interface FlueExecutionContext {
|
|
243
|
+
eventContext?: FlueEventContext;
|
|
244
|
+
instanceId?: string;
|
|
245
|
+
submissionId?: string;
|
|
246
|
+
agentName?: string;
|
|
247
|
+
conversationId?: string;
|
|
248
|
+
harness?: string;
|
|
249
|
+
session?: string;
|
|
250
|
+
operationId?: string;
|
|
251
|
+
turnId?: string;
|
|
252
|
+
taskId?: string;
|
|
253
|
+
traceCarrier?: FlueTraceCarrier;
|
|
254
|
+
}
|
|
255
|
+
type FlueObservationSubscriber = (observation: FlueObservation, ctx: FlueEventContext) => void | Promise<void>;
|
|
256
|
+
type FlueExecutionInterceptor = <T>(operation: FlueExecutionOperation, ctx: FlueExecutionContext, next: () => Promise<T>) => Promise<T>;
|
|
257
|
+
/**
|
|
258
|
+
* The third-party instrumentation contract, identical on both supported lines.
|
|
259
|
+
* `instrument(instrumentation)` from the bare `@flue/runtime` barrel installs
|
|
260
|
+
* one of these.
|
|
261
|
+
*/
|
|
262
|
+
interface FlueInstrumentation {
|
|
263
|
+
key?: symbol;
|
|
264
|
+
observe: FlueObservationSubscriber;
|
|
265
|
+
interceptor: FlueExecutionInterceptor;
|
|
266
|
+
dispose(): void | Promise<void>;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Flue observations → span intents. PURE: no `@opentelemetry/api`, no clock, no
|
|
271
|
+
* I/O. `spans.ts` is the only module that touches a `Tracer`.
|
|
272
|
+
*
|
|
273
|
+
* The split is what makes the emit contract testable. Every attribute this
|
|
274
|
+
* package puts on the wire is decided here, from a synthesized observation, and
|
|
275
|
+
* asserted in `tests/projection.test.ts` against both the 1.x and 2.x shapes —
|
|
276
|
+
* so a mapping regression is a unit-test failure rather than something only a
|
|
277
|
+
* live export against a real agent would show.
|
|
278
|
+
*
|
|
279
|
+
* ## What is emitted, and why exactly this
|
|
280
|
+
*
|
|
281
|
+
* The read side is `packages/execution-ingest/src/otel-trace-ingest-service.ts`.
|
|
282
|
+
* What it reads is this module's specification, so each choice below is a
|
|
283
|
+
* consequence of a rule stated there:
|
|
284
|
+
*
|
|
285
|
+
* - **`invoke_agent` is the ENVELOPE and there is exactly one per trace.**
|
|
286
|
+
* `selectEnvelopeSpan` takes the first ARRAY-order `invoke_agent` span, and
|
|
287
|
+
* array order is exporter-controlled. A second one — which stock
|
|
288
|
+
* `@flue/opentelemetry` opens for every `task` delegation — is therefore a
|
|
289
|
+
* coin flip over which invocation bounds the run, whose status closes it, and
|
|
290
|
+
* (via `resolveTraceAgentId`'s span-level source) which agent it files under.
|
|
291
|
+
* So a delegated sub-agent gets a `flue.task` span with NO
|
|
292
|
+
* `gen_ai.operation.name`, and the delegation itself is reported where it
|
|
293
|
+
* actually belongs: as the `execute_tool` span for the `task` tool, typed
|
|
294
|
+
* `subagent`. Stock does the opposite — it suppresses that tool span and
|
|
295
|
+
* opens the nested `invoke_agent` — which is the one place this projection
|
|
296
|
+
* deliberately diverges from it.
|
|
297
|
+
*
|
|
298
|
+
* - **Usage is rolled up ONTO the envelope, summed from `turn` leaves.**
|
|
299
|
+
* `projectUsage` prefers an envelope roll-up precisely because a
|
|
300
|
+
* `BatchSpanProcessor` flushes children before their parent, so the batch
|
|
301
|
+
* carrying the terminal may hold no model calls at all. Flue's own docs say
|
|
302
|
+
* to sum model-turn leaves rather than the `operation` roll-up, because
|
|
303
|
+
* nested duration and usage values overlap. Both point the same way: sum
|
|
304
|
+
* `turn.response.usage`, put the total on `invoke_agent`.
|
|
305
|
+
*
|
|
306
|
+
* - **The envelope carries the model.** Stock puts none there, which left the
|
|
307
|
+
* envelope's log record showing no model at all; the server-side normalizer
|
|
308
|
+
* now lifts one, but only for traces it recognizes as Flue's. Emitting it
|
|
309
|
+
* directly is F1 and costs one attribute. Both ids come off the SAME turn, so
|
|
310
|
+
* the request-model fallback can never price one call's served model against
|
|
311
|
+
* another's requested one.
|
|
312
|
+
*
|
|
313
|
+
* - **`runtype.turn.index` / `runtype.iteration` ARE derived here, and are not
|
|
314
|
+
* derivable at ingest.** The server-side normalizer deliberately refuses
|
|
315
|
+
* them: Flue puts no absolute ordinal on the wire, and ranking opaque turn
|
|
316
|
+
* ids ranks whatever the export batch happened to carry — a five-turn run
|
|
317
|
+
* whose closing batch holds two turns would be recorded as two iterations.
|
|
318
|
+
* In-process the count is exact, because we see every `turn_request` in
|
|
319
|
+
* order. This is the clearest thing this package buys that a normalizer
|
|
320
|
+
* structurally cannot.
|
|
321
|
+
*/
|
|
322
|
+
|
|
323
|
+
/** Attribute values OTLP can carry. Deliberately narrower than OTel's type. */
|
|
324
|
+
type SpanAttributes = Record<string, string | number | boolean | string[]>;
|
|
325
|
+
/** Where a span sits relative to the process boundary. */
|
|
326
|
+
type ProjectedSpanKind = 'internal' | 'client';
|
|
327
|
+
interface OpenSpanIntent {
|
|
328
|
+
kind: 'open';
|
|
329
|
+
/** Stable identity for the span; the driver keys its live-span map on it. */
|
|
330
|
+
ref: string;
|
|
331
|
+
name: string;
|
|
332
|
+
spanKind: ProjectedSpanKind;
|
|
333
|
+
/**
|
|
334
|
+
* The span this one nests under, when the projection knows it. Absent means
|
|
335
|
+
* "use whatever is active" — which, inside Flue's interceptor, is the
|
|
336
|
+
* enclosing span, so the driver's fallback repairs chains the correlation
|
|
337
|
+
* fields cannot express (a task nested inside another task, most notably).
|
|
338
|
+
*/
|
|
339
|
+
parentRef?: string;
|
|
340
|
+
/** Flue's own event timestamp, ISO-8601. Spans stay on the runtime's clock. */
|
|
341
|
+
startTime?: string;
|
|
342
|
+
attributes: SpanAttributes;
|
|
343
|
+
/**
|
|
344
|
+
* Drop this span entirely rather than start it as the root of a new trace.
|
|
345
|
+
*
|
|
346
|
+
* Set on framework bookkeeping that only means anything INSIDE a run —
|
|
347
|
+
* `flue.operation shell` and `flue.compaction`. Both can occur outside one:
|
|
348
|
+
* `session.shell()` and `session.compact()` are public host APIs, and Flue
|
|
349
|
+
* intercepts only `prompt` and `skill` operations, so neither has a parent
|
|
350
|
+
* span or an active context to inherit.
|
|
351
|
+
*
|
|
352
|
+
* Started as a root, such a span becomes the ONLY span in its trace — and
|
|
353
|
+
* ingest's `selectEnvelopeSpan` falls back to the first parentless span when
|
|
354
|
+
* a trace carries no agent invocation, so it would be read as the envelope
|
|
355
|
+
* of a run and write a phantom execution row for every host-initiated shell
|
|
356
|
+
* command. This is the same reasoning `index.ts` applies to `coordinator`
|
|
357
|
+
* operations, applied to the other two paths that reach the same state.
|
|
358
|
+
*/
|
|
359
|
+
requiresParent?: boolean;
|
|
360
|
+
}
|
|
361
|
+
interface UpdateSpanIntent {
|
|
362
|
+
kind: 'update';
|
|
363
|
+
ref: string;
|
|
364
|
+
attributes: SpanAttributes;
|
|
365
|
+
}
|
|
366
|
+
interface CloseSpanIntent {
|
|
367
|
+
kind: 'close';
|
|
368
|
+
ref: string;
|
|
369
|
+
endTime?: string;
|
|
370
|
+
/**
|
|
371
|
+
* Present when the span failed. Carries the error TYPE and the exception
|
|
372
|
+
* class NAME only — never a message and never a stack. Both are content: a
|
|
373
|
+
* provider error message routinely quotes the prompt back, and a stack
|
|
374
|
+
* exposes filesystem paths and deployment layout. This release emits no
|
|
375
|
+
* content at all, so neither is read.
|
|
376
|
+
*/
|
|
377
|
+
error?: {
|
|
378
|
+
type: string;
|
|
379
|
+
exceptionType?: string;
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
type SpanIntent = OpenSpanIntent | UpdateSpanIntent | CloseSpanIntent;
|
|
383
|
+
interface FlueProjectionOptions {
|
|
384
|
+
/**
|
|
385
|
+
* Runtype agent id per Flue agent name, e.g. `{ triage: 'agent_01j...' }`.
|
|
386
|
+
*
|
|
387
|
+
* Stamped on the ENVELOPE span as `runtype.agent.id` — the ratified second
|
|
388
|
+
* attribution placement, for the one shape a `Resource` cannot express: a
|
|
389
|
+
* single process running several Runtype agents. A single-agent deployment
|
|
390
|
+
* should set the resource attribute instead (see
|
|
391
|
+
* {@link runtypeFlueResourceAttributes} and the README recipe), which the
|
|
392
|
+
* ingest reader prefers.
|
|
393
|
+
*
|
|
394
|
+
* A delegated sub-agent is deliberately NOT attributed separately: its work
|
|
395
|
+
* runs inside the delegating agent's trace, and one trace is one execution.
|
|
396
|
+
*/
|
|
397
|
+
agents?: Record<string, string>;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Resource attributes for the customer's `Resource`. Exported rather than set
|
|
401
|
+
* internally because this package owns none of the SDK: the provider, the
|
|
402
|
+
* processor, the exporter and the resource are all the application's, and a
|
|
403
|
+
* library that reached into them would fight whatever the customer already runs.
|
|
404
|
+
*/
|
|
405
|
+
declare function runtypeFlueResourceAttributes(resource?: {
|
|
406
|
+
agentId?: string | null;
|
|
407
|
+
}): SpanAttributes;
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* The attribute vocabulary this instrumentation emits: the GenAI semantic
|
|
411
|
+
* conventions plus Runtype's ratified `runtype.*` extension names.
|
|
412
|
+
*
|
|
413
|
+
* ## Why the literals are inlined rather than imported
|
|
414
|
+
*
|
|
415
|
+
* The canonical home for the `runtype.*` names is
|
|
416
|
+
* `packages/shared/src/otlp-runtype-semconv.ts`, and this module is a
|
|
417
|
+
* CONSUMER of that contract, never a fork of it. But `@runtypelabs/shared` is
|
|
418
|
+
* `private: true` and is not published to npm, while this package is — so a
|
|
419
|
+
* runtime import would resolve in the monorepo and fail for every customer who
|
|
420
|
+
* installs `@runtypelabs/flue-otel` from the registry.
|
|
421
|
+
*
|
|
422
|
+
* The discipline that keeps a copy from becoming a fork is
|
|
423
|
+
* `tests/contract.test.ts`: a DEV-ONLY cross-import that asserts every literal
|
|
424
|
+
* here is identical to the shared module's. A rename on either side is a red
|
|
425
|
+
* build, in the same PR, which is the only mechanism that actually holds. It is
|
|
426
|
+
* the same posture `examples/flue-persona` uses for the unified SSE vocabulary.
|
|
427
|
+
*
|
|
428
|
+
* Exact strings are load-bearing: a misspelled attribute fails no build — it
|
|
429
|
+
* silently empties a column of every ingested run.
|
|
430
|
+
*/
|
|
431
|
+
/**
|
|
432
|
+
* GenAI semantic-convention attribute names. Mirrors the subset of
|
|
433
|
+
* `packages/shared/src/gen-ai-semconv.ts` this instrumentation can populate
|
|
434
|
+
* from Flue's STABLE observation plane.
|
|
435
|
+
*/
|
|
436
|
+
declare const GEN_AI: {
|
|
437
|
+
readonly operationName: "gen_ai.operation.name";
|
|
438
|
+
readonly providerName: "gen_ai.provider.name";
|
|
439
|
+
readonly agentName: "gen_ai.agent.name";
|
|
440
|
+
readonly conversationId: "gen_ai.conversation.id";
|
|
441
|
+
readonly requestModel: "gen_ai.request.model";
|
|
442
|
+
readonly responseModel: "gen_ai.response.model";
|
|
443
|
+
readonly responseId: "gen_ai.response.id";
|
|
444
|
+
readonly requestStream: "gen_ai.request.stream";
|
|
445
|
+
readonly reasoningLevel: "gen_ai.request.reasoning.level";
|
|
446
|
+
readonly maxTokens: "gen_ai.request.max_tokens";
|
|
447
|
+
readonly temperature: "gen_ai.request.temperature";
|
|
448
|
+
readonly finishReasons: "gen_ai.response.finish_reasons";
|
|
449
|
+
readonly usageInputTokens: "gen_ai.usage.input_tokens";
|
|
450
|
+
readonly usageOutputTokens: "gen_ai.usage.output_tokens";
|
|
451
|
+
readonly usageCacheReadTokens: "gen_ai.usage.cache_read.input_tokens";
|
|
452
|
+
readonly usageCacheCreationTokens: "gen_ai.usage.cache_creation.input_tokens";
|
|
453
|
+
readonly toolName: "gen_ai.tool.name";
|
|
454
|
+
readonly toolCallId: "gen_ai.tool.call.id";
|
|
455
|
+
readonly toolType: "gen_ai.tool.type";
|
|
456
|
+
readonly conversationCompacted: "gen_ai.conversation.compacted";
|
|
457
|
+
readonly errorType: "error.type";
|
|
458
|
+
readonly serverAddress: "server.address";
|
|
459
|
+
readonly serverPort: "server.port";
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* Runtype's extension vocabulary. Placement is part of the contract and the
|
|
463
|
+
* reader enforces it — see the `RUNTYPE_ATTRIBUTES` doc block in the shared
|
|
464
|
+
* module for which level each name belongs on.
|
|
465
|
+
*/
|
|
466
|
+
declare const RUNTYPE: {
|
|
467
|
+
readonly agentId: "runtype.agent.id";
|
|
468
|
+
readonly schemaVersion: "runtype.schema.version";
|
|
469
|
+
readonly adapterName: "runtype.adapter.name";
|
|
470
|
+
readonly adapterVersion: "runtype.adapter.version";
|
|
471
|
+
readonly executionId: "runtype.execution.id";
|
|
472
|
+
readonly iteration: "runtype.iteration";
|
|
473
|
+
readonly stopReason: "runtype.stop_reason";
|
|
474
|
+
readonly toolsReported: "runtype.tools.reported";
|
|
475
|
+
readonly toolType: "runtype.tool.type";
|
|
476
|
+
readonly turnId: "runtype.turn.id";
|
|
477
|
+
readonly turnIndex: "runtype.turn.index";
|
|
478
|
+
};
|
|
479
|
+
/**
|
|
480
|
+
* `runtype.tool.type` values — the closed domain that drives display and
|
|
481
|
+
* grader routing. Mirrors `RUNTYPE_TOOL_TYPES` in the shared module; only the
|
|
482
|
+
* members this instrumentation can honestly assert are ever emitted (see
|
|
483
|
+
* `projection.ts`).
|
|
484
|
+
*/
|
|
485
|
+
declare const RUNTYPE_TOOL_TYPES: readonly ["flow", "mcp", "builtin", "custom", "external", "advisor", "subagent", "local", "data_connection", "search"];
|
|
486
|
+
type RuntypeToolType = (typeof RUNTYPE_TOOL_TYPES)[number];
|
|
487
|
+
/**
|
|
488
|
+
* Terminal stop reasons, in Runtype's own wire vocabulary
|
|
489
|
+
* (`wireStopReasonSchema` in `packages/shared/src/utils/sse-event-schemas.ts`).
|
|
490
|
+
* `runtype.stop_reason` is written to the run row verbatim, so an invented
|
|
491
|
+
* value would render in the dashboard as a real fact.
|
|
492
|
+
*/
|
|
493
|
+
declare const RUNTYPE_STOP_REASONS: readonly ["end_turn", "max_tool_calls", "length", "content_filter", "error", "unknown"];
|
|
494
|
+
type RuntypeStopReason = (typeof RUNTYPE_STOP_REASONS)[number];
|
|
495
|
+
/** The version of the `runtype.*` vocabulary this package was built against. */
|
|
496
|
+
declare const RUNTYPE_SCHEMA_VERSION = "1";
|
|
497
|
+
/** What this package reports itself as in `runtype.adapter.name`. */
|
|
498
|
+
declare const ADAPTER_NAME = "@runtypelabs/flue-otel";
|
|
499
|
+
/** What this package reports itself as in `runtype.adapter.version`. */
|
|
500
|
+
declare const ADAPTER_VERSION: string;
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* `@runtypelabs/flue-otel` — Runtype's OpenTelemetry instrumentation for Flue
|
|
504
|
+
* agents.
|
|
505
|
+
*
|
|
506
|
+
* Install it with Flue's `instrument()` and every agent run becomes a trace of
|
|
507
|
+
* GenAI-semconv spans carrying Runtype's `runtype.*` extension vocabulary, so
|
|
508
|
+
* the run lands in Runtype at `t2-runtype` fidelity — full parity with a native
|
|
509
|
+
* run — instead of the generic tier a stock export reaches.
|
|
510
|
+
*
|
|
511
|
+
* ```ts
|
|
512
|
+
* import { instrument } from '@flue/runtime'
|
|
513
|
+
* import { createRuntypeFlueInstrumentation } from '@runtypelabs/flue-otel'
|
|
514
|
+
*
|
|
515
|
+
* const stop = instrument(createRuntypeFlueInstrumentation())
|
|
516
|
+
* ```
|
|
517
|
+
*
|
|
518
|
+
* ## What it is not
|
|
519
|
+
*
|
|
520
|
+
* It owns **no** OpenTelemetry SDK. There is no `@opentelemetry/sdk-*`
|
|
521
|
+
* dependency, no provider, no exporter, no sampler, no flush. The application
|
|
522
|
+
* configures those, this package writes spans through whatever is registered,
|
|
523
|
+
* and the README carries the twelve-line recipe for a process that has none
|
|
524
|
+
* yet. That is deliberate: a vendor package that installs its own tracing
|
|
525
|
+
* pipeline fights the one the customer already runs, and in a stack exporting
|
|
526
|
+
* to two backends it silently wins one of those fights.
|
|
527
|
+
*
|
|
528
|
+
* It also emits **no content** in this release — no prompts, no completions, no
|
|
529
|
+
* tool arguments or results, no error messages, no stack traces. Only
|
|
530
|
+
* identifiers, structure and metrics reach the wire. Content is a later,
|
|
531
|
+
* explicitly opted-in increment; until then the safety story needs no
|
|
532
|
+
* qualifiers, which is the right default for a package landing inside a
|
|
533
|
+
* PHI-bearing process.
|
|
534
|
+
*
|
|
535
|
+
* ## Composing with other instrumentations
|
|
536
|
+
*
|
|
537
|
+
* Flue's `instrument()` composes — an error reporter and a tracer can subscribe
|
|
538
|
+
* side by side — and this instrumentation carries its own `key`, so installing
|
|
539
|
+
* it never replaces `@flue/opentelemetry`.
|
|
540
|
+
*
|
|
541
|
+
* **Point exactly one instrumentation at Runtype.** If both this package and a
|
|
542
|
+
* stock `@flue/opentelemetry` export to the same Runtype endpoint, ingest sees
|
|
543
|
+
* two `invoke_agent` spans for one run and the usage DOUBLES. Running both is
|
|
544
|
+
* fine when they export to different backends.
|
|
545
|
+
*/
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The instrumentation key. Distinct from stock's
|
|
549
|
+
* `Symbol.for('@flue/opentelemetry')` so `instrument()` treats the two as
|
|
550
|
+
* different subscribers and composes them rather than replacing one.
|
|
551
|
+
*/
|
|
552
|
+
declare const RUNTYPE_FLUE_INSTRUMENTATION_KEY: unique symbol;
|
|
553
|
+
interface RuntypeFlueInstrumentationOptions extends FlueProjectionOptions {
|
|
554
|
+
/**
|
|
555
|
+
* Where spans are written. Defaults to the globally registered provider's
|
|
556
|
+
* tracer, which is what an application that called `setGlobalTracerProvider`
|
|
557
|
+
* (or `NodeSDK.start()`) already has. Pass one explicitly to route Runtype's
|
|
558
|
+
* spans through a provider separate from the rest of the process.
|
|
559
|
+
*/
|
|
560
|
+
tracer?: Tracer;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Build the instrumentation. Install it with Flue's `instrument()`, which
|
|
564
|
+
* returns a disposer.
|
|
565
|
+
*
|
|
566
|
+
* The returned object implements the FULL `FlueInstrumentation` contract, not
|
|
567
|
+
* just `observe`. The `interceptor` half is not optional in practice: it is
|
|
568
|
+
* what makes each span the OTel ACTIVE context around the real agent, model and
|
|
569
|
+
* tool work, so the platform's own HTTP and database spans nest inside the run
|
|
570
|
+
* instead of landing in a separate trace — and it is the only place Flue offers
|
|
571
|
+
* `executionContext.traceCarrier`, the W3C context that joins a dispatched run
|
|
572
|
+
* to the trace that started it.
|
|
573
|
+
*/
|
|
574
|
+
declare function createRuntypeFlueInstrumentation(options?: RuntypeFlueInstrumentationOptions): FlueInstrumentation;
|
|
575
|
+
|
|
576
|
+
export { ADAPTER_NAME, ADAPTER_VERSION, type FlueInstrumentation, type FlueProjectionOptions, GEN_AI, RUNTYPE, RUNTYPE_FLUE_INSTRUMENTATION_KEY, RUNTYPE_SCHEMA_VERSION, RUNTYPE_STOP_REASONS, RUNTYPE_TOOL_TYPES, type RuntypeFlueInstrumentationOptions, type RuntypeStopReason, type RuntypeToolType, type SpanAttributes, type SpanIntent, createRuntypeFlueInstrumentation, runtypeFlueResourceAttributes };
|