@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.
@@ -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 };