@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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Runtype Labs
|
|
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
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# @runtypelabs/flue-otel
|
|
2
|
+
|
|
3
|
+
OpenTelemetry instrumentation for [Flue](https://flueframework.com) agents that
|
|
4
|
+
reports runs to [Runtype](https://runtype.com) at full fidelity.
|
|
5
|
+
|
|
6
|
+
Install it with Flue's `instrument()`, point an OTLP exporter at Runtype, and
|
|
7
|
+
every agent run appears in your Runtype dashboard as a first-class execution —
|
|
8
|
+
with the model, token counts, cost, loop iterations, tool calls and stop reason
|
|
9
|
+
that a native Runtype run has.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @runtypelabs/flue-otel @opentelemetry/api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { instrument } from '@flue/runtime'
|
|
17
|
+
import { createRuntypeFlueInstrumentation } from '@runtypelabs/flue-otel'
|
|
18
|
+
|
|
19
|
+
const stopInstrumenting = instrument(createRuntypeFlueInstrumentation())
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Works on **both** Flue lines — `>=1.0.0-beta.9` and 2.x — from one entry point.
|
|
23
|
+
|
|
24
|
+
## Already exporting Flue traces to Runtype? Read this first
|
|
25
|
+
|
|
26
|
+
Two things change, and one of them is a regression, so decide before you install:
|
|
27
|
+
|
|
28
|
+
- **Replace your stock instrumentation, do not add to it.** Flue's
|
|
29
|
+
`instrument()` composes, and this package carries its own key, so calling both
|
|
30
|
+
is possible and silently wrong: Runtype receives two `invoke_agent` spans for
|
|
31
|
+
one run and the **token counts and cost double**. Point exactly one Flue
|
|
32
|
+
instrumentation at a given Runtype endpoint.
|
|
33
|
+
- **You will lose the transcript.** Stock `@flue/opentelemetry` exports content
|
|
34
|
+
by default, so your runs currently carry prompts and completions. This release
|
|
35
|
+
exports none (below), which also means **eval capture from these runs stops
|
|
36
|
+
working**. In exchange the runs gain loop structure, an accurate iteration
|
|
37
|
+
count, a stop reason, and priced usage. If the transcript is what you rely on,
|
|
38
|
+
stay on stock until content ships.
|
|
39
|
+
|
|
40
|
+
## It emits no content
|
|
41
|
+
|
|
42
|
+
This release puts **no prompts, completions, tool arguments, tool results, error
|
|
43
|
+
messages or stack traces on the wire.** Only identifiers, structure and metrics.
|
|
44
|
+
|
|
45
|
+
That is deliberate and it is the default we intend to keep earning: a package
|
|
46
|
+
that lands inside your process should not start shipping your users' text
|
|
47
|
+
somewhere because you installed it. Content export is a later, explicitly
|
|
48
|
+
opt-in increment. Until it exists, there is nothing to configure and nothing to
|
|
49
|
+
audit — the spans carry model ids, token counts, durations, tool _names_, and
|
|
50
|
+
correlation ids.
|
|
51
|
+
|
|
52
|
+
Two consequences worth knowing:
|
|
53
|
+
|
|
54
|
+
- Your Runtype executions will show timing, cost, iteration counts and the tool
|
|
55
|
+
call sequence, but **no transcript**. Eval capture from an external run needs
|
|
56
|
+
content and is not available yet.
|
|
57
|
+
- If you also run stock `@flue/opentelemetry`, note that it treats calling
|
|
58
|
+
`instrument()` as consent to export content by default. That is a different
|
|
59
|
+
choice, not a bug — but it is worth checking before you point it at a
|
|
60
|
+
third-party backend.
|
|
61
|
+
|
|
62
|
+
## It brings no OpenTelemetry SDK
|
|
63
|
+
|
|
64
|
+
This package depends on `@opentelemetry/api` and nothing else. It creates no
|
|
65
|
+
provider, no exporter, no sampler, no resource, and it never flushes. Your
|
|
66
|
+
application owns all of that, and this instrumentation writes through whatever
|
|
67
|
+
you have registered.
|
|
68
|
+
|
|
69
|
+
If you already run OpenTelemetry, you are done after the `instrument()` call
|
|
70
|
+
above — add the resource attributes below so Runtype knows which agent the runs
|
|
71
|
+
belong to.
|
|
72
|
+
|
|
73
|
+
### If you have no OpenTelemetry setup yet
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { NodeTracerProvider, BatchSpanProcessor } from '@opentelemetry/sdk-trace-node'
|
|
77
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
|
|
78
|
+
import { resourceFromAttributes } from '@opentelemetry/resources'
|
|
79
|
+
import { instrument } from '@flue/runtime'
|
|
80
|
+
import {
|
|
81
|
+
createRuntypeFlueInstrumentation,
|
|
82
|
+
runtypeFlueResourceAttributes,
|
|
83
|
+
} from '@runtypelabs/flue-otel'
|
|
84
|
+
|
|
85
|
+
const provider = new NodeTracerProvider({
|
|
86
|
+
resource: resourceFromAttributes({
|
|
87
|
+
'service.name': 'my-agent',
|
|
88
|
+
// Which Runtype agent these runs file under, plus adapter provenance.
|
|
89
|
+
...runtypeFlueResourceAttributes({ agentId: process.env.RUNTYPE_AGENT_ID }),
|
|
90
|
+
}),
|
|
91
|
+
spanProcessors: [
|
|
92
|
+
new BatchSpanProcessor(
|
|
93
|
+
new OTLPTraceExporter({
|
|
94
|
+
url: 'https://api.runtype.com/v1/otel/v1/traces',
|
|
95
|
+
headers: { Authorization: `Bearer ${process.env.RUNTYPE_API_KEY}` },
|
|
96
|
+
})
|
|
97
|
+
),
|
|
98
|
+
],
|
|
99
|
+
})
|
|
100
|
+
// `register()` also installs the context manager, WITHOUT WHICH nothing nests:
|
|
101
|
+
// the OTel API's default is a no-op that makes every span a trace root.
|
|
102
|
+
provider.register()
|
|
103
|
+
|
|
104
|
+
instrument(createRuntypeFlueInstrumentation())
|
|
105
|
+
|
|
106
|
+
// Flush before the process exits — see "Shutdown" below. This is not optional.
|
|
107
|
+
process.on('beforeExit', () => void provider.shutdown())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
You will also need `@opentelemetry/sdk-trace-node`,
|
|
111
|
+
`@opentelemetry/exporter-trace-otlp-http` and `@opentelemetry/resources`.
|
|
112
|
+
|
|
113
|
+
## Shutdown is yours, and it matters
|
|
114
|
+
|
|
115
|
+
A `BatchSpanProcessor` flushes children before their parents. A process that
|
|
116
|
+
exits without `forceFlush()` or `shutdown()` therefore tends to lose the
|
|
117
|
+
**closing `invoke_agent` span specifically** — and a run whose envelope never
|
|
118
|
+
arrives is recorded as still in flight, permanently.
|
|
119
|
+
|
|
120
|
+
Wire the shutdown. In a serverless handler, `await provider.forceFlush()` before
|
|
121
|
+
returning.
|
|
122
|
+
|
|
123
|
+
Flue's `instrument()` returns a disposer. Calling it ends any span still open as
|
|
124
|
+
`interrupted`, which is what you want on an unclean exit — but it does not
|
|
125
|
+
flush, because the exporter is not ours to flush.
|
|
126
|
+
|
|
127
|
+
## Attribution: which Runtype agent owns the run
|
|
128
|
+
|
|
129
|
+
Runtype resolves the owning agent in this order:
|
|
130
|
+
|
|
131
|
+
1. the `runtype.agent.id` **resource** attribute (what
|
|
132
|
+
`runtypeFlueResourceAttributes({ agentId })` sets) — preferred;
|
|
133
|
+
2. the `x-runtype-agent-id` request header on the exporter;
|
|
134
|
+
3. the `runtype.agent.id` attribute on the run's `invoke_agent` span.
|
|
135
|
+
|
|
136
|
+
The first two describe a whole process, so they are the right answer when it
|
|
137
|
+
runs one Runtype agent. **One process running several Runtype agents** cannot
|
|
138
|
+
express that in a shared resource, which is what the third placement is for:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
createRuntypeFlueInstrumentation({
|
|
142
|
+
agents: {
|
|
143
|
+
triage: 'agent_01jabc...', // keyed by the Flue agent's name
|
|
144
|
+
billing: 'agent_01jxyz...',
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
A delegated sub-agent is deliberately **not** attributed separately. Its work
|
|
150
|
+
runs inside the delegating agent's trace, and one trace is one execution.
|
|
151
|
+
|
|
152
|
+
> **One agent invocation per trace.** Runtype's model is that one trace is one
|
|
153
|
+
> execution, and spans inherit whatever OTel context is active. So if an
|
|
154
|
+
> enclosing span is active while two agent invocations run — an HTTP server span
|
|
155
|
+
> from auto-instrumentation is the usual way this happens — both land in the
|
|
156
|
+
> same trace, and Runtype either merges them into one execution (dropping the
|
|
157
|
+
> second run's usage and stop reason) or, when they claim different
|
|
158
|
+
> `runtype.agent.id` values through the `agents` map above with no resource
|
|
159
|
+
> attribute and no header to fall back on, rejects the trace as
|
|
160
|
+
> `ambiguous_agent_attribution` and writes nothing.
|
|
161
|
+
>
|
|
162
|
+
> If you dispatch several agent invocations inside one request or job, start
|
|
163
|
+
> each one in its own trace, or attribute at the resource level with one process
|
|
164
|
+
> per agent. Flue's own `dispatch(...)` does not propagate trace context, so a
|
|
165
|
+
> plain dispatch is already its own trace.
|
|
166
|
+
|
|
167
|
+
## Composing with other instrumentations
|
|
168
|
+
|
|
169
|
+
Flue's `instrument()` composes — an error reporter and a tracer subscribe side
|
|
170
|
+
by side — and this package carries its own key, so installing it never replaces
|
|
171
|
+
`@flue/opentelemetry`.
|
|
172
|
+
|
|
173
|
+
> **Point exactly one instrumentation at Runtype.** If this package and a stock
|
|
174
|
+
> `@flue/opentelemetry` both export to the same Runtype endpoint, Runtype sees
|
|
175
|
+
> two `invoke_agent` spans for one run and the **token counts double**. Running
|
|
176
|
+
> both is fine when they export to different backends.
|
|
177
|
+
|
|
178
|
+
## What it emits
|
|
179
|
+
|
|
180
|
+
| Span | When | Carries |
|
|
181
|
+
| -------------------------------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
182
|
+
| `invoke_agent <agent>` | one per agent invocation | the run's model, summed token usage, `runtype.stop_reason`, `runtype.tools.reported`, the highest loop iteration, `runtype.execution.id`, and `runtype.agent.id` when `agents` names the agent |
|
|
183
|
+
| `chat <model>` | one per model turn | provider, request/response model, finish reason, per-turn usage, `runtype.turn.id` / `.turn.index` / `.iteration` |
|
|
184
|
+
| `execute_tool <tool>` | one per tool call | `gen_ai.tool.name`, `gen_ai.tool.call.id`, the loop position it belongs to, and `runtype.tool.type` when the tool's class is actually known |
|
|
185
|
+
| `flue.task <agent>`, `flue.compaction`, `flue.operation shell` | delegation, compaction, host shell | correlation ids only — these are framework structure, not agent invocations |
|
|
186
|
+
|
|
187
|
+
Every span also carries the `flue.*` correlation attributes stock
|
|
188
|
+
`@flue/opentelemetry` emits, so dashboards you have already built keep working.
|
|
189
|
+
|
|
190
|
+
### Two places it deliberately says nothing
|
|
191
|
+
|
|
192
|
+
- **`runtype.tool.type` for an ordinary tool.** Flue's `origin` field classifies
|
|
193
|
+
who _initiated_ a call (`model` for every model-requested tool, whoever wrote
|
|
194
|
+
it), not what the tool _is_. Mapping it would fill the column with a value
|
|
195
|
+
that means nothing on that axis. Only a genuine class fact is emitted: a
|
|
196
|
+
sub-agent delegation, and a 1.x `datastore` tool.
|
|
197
|
+
- **A stop reason it cannot determine.** A run that ended mid-tool-call reports
|
|
198
|
+
`unknown` rather than guessing between a turn cap, a tool cap and a host
|
|
199
|
+
abort.
|
|
200
|
+
|
|
201
|
+
An absent attribute costs one column. A wrong one renders as a measurement.
|
|
202
|
+
|
|
203
|
+
## Deriving from Flue's stable surface only
|
|
204
|
+
|
|
205
|
+
Everything here comes from the observations Flue publishes as stable — event
|
|
206
|
+
type names, envelope and correlation fields, and the normalized `turn_request` /
|
|
207
|
+
`turn` / `tool_*` / `task` / `operation` / `compaction` / `submission_settled`
|
|
208
|
+
payloads.
|
|
209
|
+
|
|
210
|
+
Nothing reads `AgentMessage`, which Flue documents as explicitly unstable and
|
|
211
|
+
which rides `message_start`, `message_end`, `turn_messages` and `agent_end`.
|
|
212
|
+
That exclusion is why one entry point serves both the 1.x and 2.x lines: the
|
|
213
|
+
observation plane was essentially frozen across Flue's major rewrite, and the
|
|
214
|
+
handful of fields that did change are probed rather than version-compared.
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|