@runtypelabs/flue-otel 0.2.2 → 0.4.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/CHANGELOG.md +105 -0
- package/README.md +86 -10
- package/dist/index.cjs +147 -10
- package/dist/index.d.cts +141 -8
- package/dist/index.d.ts +141 -8
- package/dist/index.mjs +144 -10
- package/package.json +4 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# @runtypelabs/flue-otel
|
|
2
|
+
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 21f5bbb: Make the producing instrumentation's version detectable server-side. `@runtypelabs/flue-otel` now stamps `runtype.adapter.name` / `runtype.adapter.version` on each run's `invoke_agent` span, so a trace identifies its producer without the customer wiring `runtypeFlueResourceAttributes` into their SDK `Resource`. OTLP trace ingest resolves the pair (resource level first, envelope span second) and records it on the run's log row as `otlpAdapterName` / `otlpAdapterVersion`, regardless of logging policy.
|
|
8
|
+
|
|
9
|
+
## 0.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 01df868: Add an opt-in `content` option that emits tool arguments (`gen_ai.tool.call.arguments`, at span open) and tool results (`gen_ai.tool.call.result`, before span end) on `execute_tool` spans, so a Flue run's tool cards in Runtype show their input and output. Off by default; per-kind switches, a per-value `maxChars` ceiling with an explicit truncation marker (clamped to the ingest per-attribute ceiling), and a `redact` hook. A failed tool's result, the host's shell call, and prompts/completions are still never sent.
|
|
14
|
+
|
|
15
|
+
## 0.2.2
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 5f00348: Carry Workers AI `gatewayLogId` and `providerFinishReason` onto the chat span
|
|
20
|
+
|
|
21
|
+
Flue's `turn` event carries two allowlisted raw-provider fields that
|
|
22
|
+
`@runtypelabs/flue-otel` previously dropped. Both are now emitted on the `chat`
|
|
23
|
+
span as `runtype.*` extension attributes, gated on presence so an absent value
|
|
24
|
+
costs nothing:
|
|
25
|
+
- `runtype.gateway.log_id` — the response's own gateway log id (e.g.
|
|
26
|
+
Cloudflare AI Gateway's `cf-aig-log-id`), a pointer to the request in the
|
|
27
|
+
customer's own AI Gateway dashboard without shipping the content.
|
|
28
|
+
- `runtype.provider.finish_reason` — the provider's exact finish value before
|
|
29
|
+
normalization (e.g. Workers AI's `tool_calls` behind the normalized
|
|
30
|
+
`toolUse`), for diagnosis when a run stops unexpectedly.
|
|
31
|
+
|
|
32
|
+
Both attribute names are added to the shared `RUNTYPE_ATTRIBUTES` vocabulary
|
|
33
|
+
(`packages/shared/src/otlp-runtype-semconv.ts`) so the emit side and the ingest
|
|
34
|
+
side stay in step. They are additive diagnostics and do not count toward the
|
|
35
|
+
achieved fidelity tier.
|
|
36
|
+
|
|
37
|
+
## 0.2.1
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- 625c455: Rewrite the README from first principles. It now leads with install, quick
|
|
42
|
+
start and the no-SDK recipe, then configuration, attribution, shutdown, and
|
|
43
|
+
what the package emits, instead of narrating how the package differs from
|
|
44
|
+
earlier instrumentations.
|
|
45
|
+
|
|
46
|
+
## 0.2.0
|
|
47
|
+
|
|
48
|
+
### Minor Changes
|
|
49
|
+
|
|
50
|
+
- 2439b84: New package: `@runtypelabs/flue-otel`, an OpenTelemetry instrumentation for Flue
|
|
51
|
+
agents that reports runs to Runtype at `t2-runtype` fidelity.
|
|
52
|
+
|
|
53
|
+
Stock `@flue/opentelemetry` already exports GenAI semconv spans, and the ingest
|
|
54
|
+
normalizer shipped in #7336 lifts those to t2 by reading Flue's own `flue.*`
|
|
55
|
+
identifiers. This package is the client-side complement, for the facts a
|
|
56
|
+
server-side normalizer structurally cannot reach:
|
|
57
|
+
- **Absolute loop position.** `runtype.turn.index` / `runtype.iteration` are
|
|
58
|
+
deliberately NOT derived at ingest, because Flue puts no absolute ordinal on
|
|
59
|
+
the wire and ranking opaque turn ids ranks whatever the export batch happened
|
|
60
|
+
to carry — a five-turn run whose closing batch holds two turns would be
|
|
61
|
+
recorded as two iterations. In process the count is exact.
|
|
62
|
+
- **A run-level usage roll-up on the envelope.** Summed from `turn` leaves, so a
|
|
63
|
+
split export cannot lose the total: the batch carrying the terminal carries
|
|
64
|
+
the complete sum by construction.
|
|
65
|
+
- **Per-agent attribution for N agents in one process**, via the span-level
|
|
66
|
+
`runtype.agent.id` placement, keyed by Flue agent name.
|
|
67
|
+
- **One `invoke_agent` span per trace.** Stock opens a nested one for every
|
|
68
|
+
`task` delegation, which is a coin flip over which invocation bounds the run
|
|
69
|
+
(ingest's envelope selection is first-match over exporter-controlled array
|
|
70
|
+
order). Here a delegation is reported as an `execute_tool` span typed
|
|
71
|
+
`subagent`, and the sub-agent's work gets a non-agent `flue.task` span.
|
|
72
|
+
|
|
73
|
+
Works on both Flue lines (`>=1.0.0-beta.9` and 2.x) from one entry point, via a
|
|
74
|
+
capability probe rather than version comparison. Depends on `@opentelemetry/api`
|
|
75
|
+
only — no SDK, no provider, no exporter, no flush; the application owns those.
|
|
76
|
+
|
|
77
|
+
Emits no content in this release: no prompts, completions, tool arguments or
|
|
78
|
+
results, error messages, or stack traces.
|
|
79
|
+
|
|
80
|
+
`@runtypelabs/shared` exports the wire stop-reason tuple as `WIRE_STOP_REASONS`
|
|
81
|
+
(the zod enum is now derived from it, so the two cannot disagree). The ingest
|
|
82
|
+
reader writes `runtype.stop_reason` to `agent_executions.stop_reason` verbatim
|
|
83
|
+
with no enum validation, so an external producer has to spell those six strings
|
|
84
|
+
itself; exporting the tuple is what lets the producer's contract test pin its
|
|
85
|
+
copy and turn a rename into a red build instead of a silently wrong column.
|
|
86
|
+
|
|
87
|
+
### Patch Changes
|
|
88
|
+
|
|
89
|
+
- 3b4f69c: License `@runtypelabs/flue-otel` under MIT rather than Apache-2.0, and ship the
|
|
90
|
+
license text in the published tarball.
|
|
91
|
+
|
|
92
|
+
The package declared `Apache-2.0` in `package.json` and in its README while
|
|
93
|
+
carrying no license file at all, so the published artifact asserted a grant
|
|
94
|
+
whose terms it never included. `LICENSE` is now added to `files`, matching how
|
|
95
|
+
every other published package here (`cli`, `python-sdk`, `ruby-sdk`,
|
|
96
|
+
`java-sdk`, `php-sdk`) ships its license.
|
|
97
|
+
|
|
98
|
+
- 3b4f69c: Normalize package authorship and copyright metadata.
|
|
99
|
+
|
|
100
|
+
The `author` field reads `Runtype` rather than `Runtype Labs` across every
|
|
101
|
+
workspace package that set it, and the copyright holder in each license file is
|
|
102
|
+
now `Runtype, Inc` — replacing `Travrse Labs, Inc` in the CLI and the four Fern
|
|
103
|
+
SDKs, and the `Runtype Labs` that the new `flue-otel` license was written with.
|
|
104
|
+
|
|
105
|
+
Metadata only: no source, dependency, or behavior change.
|
package/README.md
CHANGED
|
@@ -10,8 +10,9 @@ token usage, cost, loop iterations, tool calls and stop reason.
|
|
|
10
10
|
- Works with Flue `>=1.0.0-beta.9` and 2.x from one entry point.
|
|
11
11
|
- Depends on `@opentelemetry/api` only. It brings no SDK, provider, exporter or
|
|
12
12
|
sampler; it writes through whatever your application has registered.
|
|
13
|
-
- Emits identifiers, structure and metrics
|
|
14
|
-
arguments, tool results, error messages or stack traces reach the wire
|
|
13
|
+
- Emits identifiers, structure and metrics by default. No prompts, completions,
|
|
14
|
+
tool arguments, tool results, error messages or stack traces reach the wire
|
|
15
|
+
unless you opt in; the one opt-in is [tool content](#tool-content).
|
|
15
16
|
|
|
16
17
|
Full guide: [Instrumenting a Flue agent](https://docs.runtype.com/developer-guides/guides/flue-instrumentation).
|
|
17
18
|
|
|
@@ -84,10 +85,11 @@ process.on('beforeExit', () => void provider.shutdown())
|
|
|
84
85
|
|
|
85
86
|
`createRuntypeFlueInstrumentation(options?)` accepts:
|
|
86
87
|
|
|
87
|
-
| Option
|
|
88
|
-
|
|
|
89
|
-
| `agents`
|
|
90
|
-
| `tracer`
|
|
88
|
+
| Option | Type | Purpose |
|
|
89
|
+
| --------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
90
|
+
| `agents` | `Record<string, string>` | Runtype agent id per Flue agent name, e.g. `{ triage: 'agent_01j...' }`. Stamped as `runtype.agent.id` on that agent's `invoke_agent` span. See [Attribution](#attribution). |
|
|
91
|
+
| `tracer` | `Tracer` | Where spans are written. Defaults to `trace.getTracer('@runtypelabs/flue-otel')` on the global provider. Pass one to route Runtype spans through a separate provider. |
|
|
92
|
+
| `content` | `FlueContentOptions` | Opt in to tool arguments and results on `execute_tool` spans. Off by default. See [Tool content](#tool-content). |
|
|
91
93
|
|
|
92
94
|
The returned object implements Flue's full `FlueInstrumentation` contract:
|
|
93
95
|
`observe` (builds spans from Flue's observation stream), `interceptor` (makes
|
|
@@ -104,12 +106,19 @@ subscriber. Its key is exported as `RUNTYPE_FLUE_INSTRUMENTATION_KEY`.
|
|
|
104
106
|
|
|
105
107
|
- `runtypeFlueResourceAttributes({ agentId? })` builds the `runtype.*` resource
|
|
106
108
|
attributes (`schema.version`, `adapter.name`, `adapter.version`, and
|
|
107
|
-
`agent.id` when given). Spread it into your SDK `Resource`.
|
|
108
|
-
|
|
109
|
+
`agent.id` when given). Spread it into your SDK `Resource`. The adapter pair
|
|
110
|
+
is also stamped on each run's `invoke_agent` span, so Runtype can tell which
|
|
111
|
+
version produced a trace even when the resource is not wired; the resource
|
|
112
|
+
placement still wins where both are present.
|
|
113
|
+
- `GEN_AI` and `RUNTYPE`: the attribute-name constants this package emits by
|
|
114
|
+
default; `GEN_AI_CONTENT`: the two it emits under the `content` opt-in.
|
|
115
|
+
- `DEFAULT_CONTENT_MAX_CHARS` and `INGEST_CONTENT_ATTRIBUTE_CEILING`: the
|
|
116
|
+
content size defaults, see [Tool content](#tool-content).
|
|
109
117
|
- `RUNTYPE_STOP_REASONS`, `RUNTYPE_TOOL_TYPES`, `RUNTYPE_SCHEMA_VERSION`,
|
|
110
118
|
`ADAPTER_NAME`, `ADAPTER_VERSION`, plus the `RuntypeStopReason`,
|
|
111
119
|
`RuntypeToolType`, `FlueInstrumentation`, `FlueProjectionOptions`,
|
|
112
|
-
`
|
|
120
|
+
`FlueContentOptions`, `FlueContentRedactContext`, `SpanAttributes` and
|
|
121
|
+
`SpanIntent` types.
|
|
113
122
|
|
|
114
123
|
## Attribution
|
|
115
124
|
|
|
@@ -179,7 +188,7 @@ backends.
|
|
|
179
188
|
| -------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
180
189
|
| `invoke_agent <agent>` | once per agent invocation | `gen_ai.agent.name`, `gen_ai.conversation.id`, the run's request/response model, summed token usage across every turn (`gen_ai.usage.*`), `runtype.stop_reason`, `runtype.tools.reported`, the highest loop iteration (`runtype.iteration`), `runtype.execution.id`, and `runtype.agent.id` when `agents` names the agent |
|
|
181
190
|
| `chat <model>` | once per model turn | `gen_ai.provider.name`, request/response model, response id, finish reason, per-turn usage, request parameters (`max_tokens`, `temperature`, reasoning level, server address), `runtype.turn.id` / `runtype.turn.index` / `runtype.iteration`, and `runtype.provider.finish_reason` / `runtype.gateway.log_id` when the provider records them (Workers AI attaches both — the gateway log id is a pointer to that exact request in your AI Gateway dashboard) |
|
|
182
|
-
| `execute_tool <tool>` | once per model-requested tool call | `gen_ai.tool.name`, `gen_ai.tool.call.id`, the loop position it belongs to, `gen_ai.tool.type` (`function`) for every tool except a sub-agent delegation, and `runtype.tool.type` when the tool's class is known
|
|
191
|
+
| `execute_tool <tool>` | once per model-requested tool call | `gen_ai.tool.name`, `gen_ai.tool.call.id`, the loop position it belongs to, `gen_ai.tool.type` (`function`) for every tool except a sub-agent delegation, and `runtype.tool.type` when the tool's class is known, and `gen_ai.tool.call.arguments` / `gen_ai.tool.call.result` only under the [`content`](#tool-content) opt-in |
|
|
183
192
|
| `flue.task <agent>`, `flue.compaction`, `flue.operation shell` | delegation, compaction, host shell call | correlation ids only; framework structure, not agent invocations |
|
|
184
193
|
|
|
185
194
|
Every span also carries Flue's own `flue.*` correlation attributes
|
|
@@ -220,6 +229,73 @@ Rules the projection follows:
|
|
|
220
229
|
|
|
221
230
|
An absent attribute costs one column. A wrong one renders as a measurement.
|
|
222
231
|
|
|
232
|
+
## Tool content
|
|
233
|
+
|
|
234
|
+
By default an `execute_tool` span says which tool ran and how long it took, and
|
|
235
|
+
Runtype's trace view shows its card as "No tool content in this trace". To see
|
|
236
|
+
arguments and results on that card, opt in:
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
createRuntypeFlueInstrumentation({
|
|
240
|
+
content: {
|
|
241
|
+
toolArguments: true, // gen_ai.tool.call.arguments, set when the span opens
|
|
242
|
+
toolResults: true, // gen_ai.tool.call.result, set just before the span ends
|
|
243
|
+
maxChars: 65_536, // per value, marker included; this is the default
|
|
244
|
+
redact: (value, { kind, toolName }) => value, // optional; return undefined to drop
|
|
245
|
+
},
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Each switch is independent and defaults to off, so `{}` changes nothing. The
|
|
250
|
+
values come from Flue's stable `tool_start.args` and `tool.result` payloads
|
|
251
|
+
(2.x's `effectiveResult`, what the model was actually shown, when present).
|
|
252
|
+
|
|
253
|
+
The attribute names are the GenAI semconv `gen_ai.tool.call.arguments` and
|
|
254
|
+
`gen_ai.tool.call.result` (exported as `GEN_AI_CONTENT`), which Runtype reads
|
|
255
|
+
onto the tool card's input and output. The two are encoded differently because
|
|
256
|
+
Runtype projects them differently:
|
|
257
|
+
|
|
258
|
+
- **Arguments are always a JSON object**, since that is the only shape the
|
|
259
|
+
reader projects. A non-object value (a bare string, an array) is carried as
|
|
260
|
+
`{ "value": ... }` rather than dropped. Over `maxChars`, the object is not
|
|
261
|
+
cut in place (a mid-document cut is not JSON and would be discarded); it is
|
|
262
|
+
replaced by `{ "truncated": "<head of the JSON>…[truncated N chars]" }`.
|
|
263
|
+
- **A result is text**: a string as-is, anything else as JSON. Over `maxChars`
|
|
264
|
+
it is cut and ends in `…[truncated N chars]`, so a cut value never looks
|
|
265
|
+
complete.
|
|
266
|
+
|
|
267
|
+
Ceilings worth knowing, because they decide what Runtype keeps:
|
|
268
|
+
|
|
269
|
+
- `maxChars` defaults to `DEFAULT_CONTENT_MAX_CHARS` (64 KiB) and is clamped to
|
|
270
|
+
`INGEST_CONTENT_ATTRIBUTE_CEILING` (256 KiB). Runtype drops a single content
|
|
271
|
+
attribute above that ceiling **whole** rather than cutting it, which is why
|
|
272
|
+
the package cuts first.
|
|
273
|
+
- One span may carry at most 384 KiB of content across every attribute, and one
|
|
274
|
+
export request at most 2 MiB of content. Arguments plus result at the default
|
|
275
|
+
fit inside the per-span ceiling; raise `maxChars` with that in mind.
|
|
276
|
+
- Content makes spans large. A `BatchSpanProcessor` at its default 512 spans per
|
|
277
|
+
request can push an export past Runtype's 8 MiB request bound, which rejects
|
|
278
|
+
the whole batch (envelope and usage included, not only the content). With
|
|
279
|
+
both switches on, set `maxExportBatchSize` to something like 64.
|
|
280
|
+
|
|
281
|
+
What is still never sent, whatever you set:
|
|
282
|
+
|
|
283
|
+
- **A failed tool's result.** On both Flue lines it is commonly the error
|
|
284
|
+
payload, and error messages stay off the wire. The span still closes with its
|
|
285
|
+
error type and exception class name.
|
|
286
|
+
- **The host's own shell call** (`session.shell()`), which is not a tool row.
|
|
287
|
+
- **Prompts and completions.** They live on `AgentMessage`, which Flue marks
|
|
288
|
+
unstable. This package will read them once Flue exposes a stable shape. One
|
|
289
|
+
edge to know: a delegation to a sub-agent is a tool call, and its
|
|
290
|
+
**arguments carry the prompt handed to that sub-agent**. If that matters for
|
|
291
|
+
your deployment, drop it with `redact` (return `undefined` when `toolName`
|
|
292
|
+
is the delegation tool).
|
|
293
|
+
|
|
294
|
+
`redact` runs on the raw value before encoding, so it can address fields by
|
|
295
|
+
name; it receives `{ kind: 'arguments' | 'result', toolName }`. Returning
|
|
296
|
+
`undefined` drops the attribute for that call. If it throws, the attribute is
|
|
297
|
+
dropped and the span is otherwise unaffected.
|
|
298
|
+
|
|
223
299
|
## Flue compatibility
|
|
224
300
|
|
|
225
301
|
The instrumentation reads only Flue's stable observation surface: event type
|
package/dist/index.cjs
CHANGED
|
@@ -22,7 +22,10 @@ var index_exports = {};
|
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ADAPTER_NAME: () => ADAPTER_NAME,
|
|
24
24
|
ADAPTER_VERSION: () => ADAPTER_VERSION,
|
|
25
|
+
DEFAULT_CONTENT_MAX_CHARS: () => DEFAULT_CONTENT_MAX_CHARS,
|
|
25
26
|
GEN_AI: () => GEN_AI,
|
|
27
|
+
GEN_AI_CONTENT: () => GEN_AI_CONTENT,
|
|
28
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING: () => INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
26
29
|
RUNTYPE: () => RUNTYPE,
|
|
27
30
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY: () => RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
28
31
|
RUNTYPE_SCHEMA_VERSION: () => RUNTYPE_SCHEMA_VERSION,
|
|
@@ -75,8 +78,83 @@ function mapSettlementOutcome(outcome) {
|
|
|
75
78
|
return void 0;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
// src/content.ts
|
|
82
|
+
var DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
83
|
+
var INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
84
|
+
function resolveContentPolicy(options) {
|
|
85
|
+
const requested = options?.maxChars;
|
|
86
|
+
const maxChars = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), INGEST_CONTENT_ATTRIBUTE_CEILING) : DEFAULT_CONTENT_MAX_CHARS;
|
|
87
|
+
return {
|
|
88
|
+
toolArguments: options?.toolArguments === true,
|
|
89
|
+
toolResults: options?.toolResults === true,
|
|
90
|
+
maxChars,
|
|
91
|
+
redact: options?.redact
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function encodeContentValue(value, policy, context3) {
|
|
95
|
+
if (value === void 0) return void 0;
|
|
96
|
+
let redacted = value;
|
|
97
|
+
if (policy.redact) {
|
|
98
|
+
try {
|
|
99
|
+
redacted = policy.redact(value, context3);
|
|
100
|
+
} catch {
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
if (redacted === void 0) return void 0;
|
|
104
|
+
}
|
|
105
|
+
if (context3.kind === "arguments") return encodeArguments(redacted, policy.maxChars);
|
|
106
|
+
const encoded = stringify(redacted);
|
|
107
|
+
if (encoded === void 0) return void 0;
|
|
108
|
+
return truncate(encoded, policy.maxChars);
|
|
109
|
+
}
|
|
110
|
+
function encodeArguments(value, maxChars) {
|
|
111
|
+
const shaped = isPlainObject(value) ? value : { value };
|
|
112
|
+
const encoded = stringify(shaped);
|
|
113
|
+
if (encoded === void 0) return void 0;
|
|
114
|
+
if (encoded.length <= maxChars) return encoded;
|
|
115
|
+
let budget = maxChars - TRUNCATED_WRAPPER_OVERHEAD;
|
|
116
|
+
for (let round = 0; round < 8 && budget > 0; round += 1) {
|
|
117
|
+
const wrapped = JSON.stringify({ truncated: truncate(encoded, budget) });
|
|
118
|
+
if (wrapped.length <= maxChars) return wrapped;
|
|
119
|
+
budget -= wrapped.length - maxChars;
|
|
120
|
+
}
|
|
121
|
+
return JSON.stringify({ truncated: truncate(encoded, Math.max(0, budget)) });
|
|
122
|
+
}
|
|
123
|
+
var TRUNCATED_WRAPPER_OVERHEAD = 16;
|
|
124
|
+
function isPlainObject(value) {
|
|
125
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
126
|
+
}
|
|
127
|
+
function stringify(value) {
|
|
128
|
+
if (typeof value === "string") return value;
|
|
129
|
+
try {
|
|
130
|
+
const encoded = JSON.stringify(value);
|
|
131
|
+
return typeof encoded === "string" ? encoded : void 0;
|
|
132
|
+
} catch {
|
|
133
|
+
return void 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function truncate(value, maxChars) {
|
|
137
|
+
if (value.length <= maxChars) return value;
|
|
138
|
+
const upperBound = truncationMarker(value.length);
|
|
139
|
+
let keep = Math.max(0, maxChars - upperBound.length);
|
|
140
|
+
let marker = truncationMarker(value.length - keep);
|
|
141
|
+
keep = Math.max(0, maxChars - marker.length);
|
|
142
|
+
marker = truncationMarker(value.length - keep);
|
|
143
|
+
if (keep > 0 && isHighSurrogate(value.charCodeAt(keep - 1))) {
|
|
144
|
+
keep -= 1;
|
|
145
|
+
marker = truncationMarker(value.length - keep);
|
|
146
|
+
}
|
|
147
|
+
return value.slice(0, keep) + marker;
|
|
148
|
+
}
|
|
149
|
+
function truncationMarker(droppedChars) {
|
|
150
|
+
return `\u2026[truncated ${droppedChars} chars]`;
|
|
151
|
+
}
|
|
152
|
+
function isHighSurrogate(code) {
|
|
153
|
+
return code >= 55296 && code <= 56319;
|
|
154
|
+
}
|
|
155
|
+
|
|
78
156
|
// package.json
|
|
79
|
-
var version = "0.
|
|
157
|
+
var version = "0.4.0";
|
|
80
158
|
|
|
81
159
|
// src/semconv.ts
|
|
82
160
|
var GEN_AI = {
|
|
@@ -104,6 +182,10 @@ var GEN_AI = {
|
|
|
104
182
|
serverAddress: "server.address",
|
|
105
183
|
serverPort: "server.port"
|
|
106
184
|
};
|
|
185
|
+
var GEN_AI_CONTENT = {
|
|
186
|
+
toolCallArguments: "gen_ai.tool.call.arguments",
|
|
187
|
+
toolCallResult: "gen_ai.tool.call.result"
|
|
188
|
+
};
|
|
107
189
|
var GEN_AI_OPERATION_INVOKE_AGENT = "invoke_agent";
|
|
108
190
|
var GEN_AI_OPERATION_CHAT = "chat";
|
|
109
191
|
var GEN_AI_OPERATION_EXECUTE_TOOL = "execute_tool";
|
|
@@ -173,6 +255,7 @@ function runtypeFlueResourceAttributes(resource) {
|
|
|
173
255
|
};
|
|
174
256
|
}
|
|
175
257
|
function createFlueProjection(options = {}) {
|
|
258
|
+
const content = resolveContentPolicy(options.content);
|
|
176
259
|
const open = /* @__PURE__ */ new Map();
|
|
177
260
|
const envelopes = /* @__PURE__ */ new Map();
|
|
178
261
|
const envelopeByChain = /* @__PURE__ */ new Map();
|
|
@@ -257,7 +340,12 @@ function createFlueProjection(options = {}) {
|
|
|
257
340
|
// any inbound id; it does not key the row, which stays derived from
|
|
258
341
|
// the trace id.
|
|
259
342
|
...isEnvelope && event.submissionId ? { [RUNTYPE.executionId]: event.submissionId } : {},
|
|
260
|
-
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {}
|
|
343
|
+
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {},
|
|
344
|
+
// WHY(README.md): the resource placement is the customer's to wire and is routinely skipped.
|
|
345
|
+
...isEnvelope ? {
|
|
346
|
+
[RUNTYPE.adapterName]: ADAPTER_NAME,
|
|
347
|
+
[RUNTYPE.adapterVersion]: ADAPTER_VERSION
|
|
348
|
+
} : {}
|
|
261
349
|
}
|
|
262
350
|
}
|
|
263
351
|
];
|
|
@@ -343,7 +431,12 @@ function createFlueProjection(options = {}) {
|
|
|
343
431
|
const ref = compactionKey(event);
|
|
344
432
|
if (open.has(ref)) return [];
|
|
345
433
|
const parentRef = resolveParentRef(event);
|
|
346
|
-
open.set(ref, {
|
|
434
|
+
open.set(ref, {
|
|
435
|
+
ref,
|
|
436
|
+
...parentRef ? { parentRef } : {},
|
|
437
|
+
chainKey: chainKey(event),
|
|
438
|
+
isTask: false
|
|
439
|
+
});
|
|
347
440
|
const reason = readString(event, "reason");
|
|
348
441
|
return [
|
|
349
442
|
{
|
|
@@ -381,7 +474,12 @@ function createFlueProjection(options = {}) {
|
|
|
381
474
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
382
475
|
const compactionRef = compactionKey(event);
|
|
383
476
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
384
|
-
open.set(ref, {
|
|
477
|
+
open.set(ref, {
|
|
478
|
+
ref,
|
|
479
|
+
...parentRef ? { parentRef } : {},
|
|
480
|
+
chainKey: chainKey(event),
|
|
481
|
+
isTask: false
|
|
482
|
+
});
|
|
385
483
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
386
484
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
387
485
|
let position;
|
|
@@ -409,7 +507,8 @@ function createFlueProjection(options = {}) {
|
|
|
409
507
|
if (position) turnPositions.set(ref, position);
|
|
410
508
|
}
|
|
411
509
|
if (envelope) {
|
|
412
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
510
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
511
|
+
envelope.requestModel = request.requestedModel;
|
|
413
512
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
414
513
|
}
|
|
415
514
|
return [
|
|
@@ -486,7 +585,12 @@ function createFlueProjection(options = {}) {
|
|
|
486
585
|
const ref = toolKey(event);
|
|
487
586
|
if (open.has(ref)) return [];
|
|
488
587
|
const parentRef = resolveParentRef(event);
|
|
489
|
-
open.set(ref, {
|
|
588
|
+
open.set(ref, {
|
|
589
|
+
ref,
|
|
590
|
+
...parentRef ? { parentRef } : {},
|
|
591
|
+
chainKey: chainKey(event),
|
|
592
|
+
isTask: false
|
|
593
|
+
});
|
|
490
594
|
const position = turnPositions.get(turnKey(event));
|
|
491
595
|
if (isCallerShellCall(toolName, event)) {
|
|
492
596
|
return [
|
|
@@ -510,6 +614,10 @@ function createFlueProjection(options = {}) {
|
|
|
510
614
|
}
|
|
511
615
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
512
616
|
const toolType = resolveToolType(toolName, event);
|
|
617
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
618
|
+
kind: "arguments",
|
|
619
|
+
toolName
|
|
620
|
+
}) : void 0;
|
|
513
621
|
return [
|
|
514
622
|
{
|
|
515
623
|
kind: "open",
|
|
@@ -523,6 +631,7 @@ function createFlueProjection(options = {}) {
|
|
|
523
631
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
524
632
|
[GEN_AI.toolName]: toolName,
|
|
525
633
|
[GEN_AI.toolCallId]: toolCallId,
|
|
634
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
526
635
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
527
636
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
528
637
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -541,9 +650,22 @@ function createFlueProjection(options = {}) {
|
|
|
541
650
|
const ref = toolKey(event);
|
|
542
651
|
if (!open.has(ref)) return [];
|
|
543
652
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)
|
|
653
|
+
const isError = readBoolean(event, "isError") === true;
|
|
654
|
+
const toolName = readString(event, "toolName");
|
|
655
|
+
if (content.toolResults && !isError && toolName && !isCallerShellCall(toolName, event)) {
|
|
656
|
+
const result = encodeContentValue(readToolResult(event), content, {
|
|
657
|
+
kind: "result",
|
|
658
|
+
toolName
|
|
659
|
+
});
|
|
660
|
+
if (result !== void 0) {
|
|
661
|
+
intents.push({
|
|
662
|
+
kind: "update",
|
|
663
|
+
ref,
|
|
664
|
+
attributes: { [GEN_AI_CONTENT.toolCallResult]: result }
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
intents.push(closeIntent(ref, event, isError, readErrorInfo(event)));
|
|
547
669
|
open.delete(ref);
|
|
548
670
|
return intents;
|
|
549
671
|
}
|
|
@@ -735,6 +857,13 @@ function readErrorInfo(event) {
|
|
|
735
857
|
const detail = event.errorInfo;
|
|
736
858
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
737
859
|
}
|
|
860
|
+
function readUnknown(event, key) {
|
|
861
|
+
return event[key];
|
|
862
|
+
}
|
|
863
|
+
function readToolResult(event) {
|
|
864
|
+
const record = event;
|
|
865
|
+
return Object.prototype.hasOwnProperty.call(record, "effectiveResult") ? record.effectiveResult : record.result;
|
|
866
|
+
}
|
|
738
867
|
function readString(event, key) {
|
|
739
868
|
const value = event[key];
|
|
740
869
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -751,7 +880,12 @@ function positiveNumber(value) {
|
|
|
751
880
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
752
881
|
}
|
|
753
882
|
function chainKey(value) {
|
|
754
|
-
return identityKey("chain", [
|
|
883
|
+
return identityKey("chain", [
|
|
884
|
+
value.instanceId,
|
|
885
|
+
value.harness,
|
|
886
|
+
value.conversationId,
|
|
887
|
+
value.session
|
|
888
|
+
]);
|
|
755
889
|
}
|
|
756
890
|
function operationKey(value) {
|
|
757
891
|
return identityKey("operation", [
|
|
@@ -940,7 +1074,10 @@ function readIdField(operation, key) {
|
|
|
940
1074
|
0 && (module.exports = {
|
|
941
1075
|
ADAPTER_NAME,
|
|
942
1076
|
ADAPTER_VERSION,
|
|
1077
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
943
1078
|
GEN_AI,
|
|
1079
|
+
GEN_AI_CONTENT,
|
|
1080
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
944
1081
|
RUNTYPE,
|
|
945
1082
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
946
1083
|
RUNTYPE_SCHEMA_VERSION,
|
package/dist/index.d.cts
CHANGED
|
@@ -193,12 +193,14 @@ type FlueEventVariant = {
|
|
|
193
193
|
type: 'tool_start';
|
|
194
194
|
toolName: string;
|
|
195
195
|
toolCallId: string;
|
|
196
|
+
args?: unknown;
|
|
196
197
|
} | {
|
|
197
198
|
type: 'tool';
|
|
198
199
|
toolName: string;
|
|
199
200
|
toolCallId: string;
|
|
200
201
|
isError?: boolean;
|
|
201
202
|
result?: unknown;
|
|
203
|
+
effectiveResult?: unknown;
|
|
202
204
|
durationMs?: number;
|
|
203
205
|
} | {
|
|
204
206
|
type: 'submission_settled';
|
|
@@ -273,6 +275,117 @@ interface FlueInstrumentation {
|
|
|
273
275
|
dispose(): void | Promise<void>;
|
|
274
276
|
}
|
|
275
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Opt-in tool content: `gen_ai.tool.call.arguments` and
|
|
280
|
+
* `gen_ai.tool.call.result` on `execute_tool` spans.
|
|
281
|
+
*
|
|
282
|
+
* PURE, like `projection.ts`: value in, attribute string out. The projection
|
|
283
|
+
* decides WHEN to call this (arguments when the tool span opens, the result
|
|
284
|
+
* just before it closes); this module decides WHAT reaches the wire and how
|
|
285
|
+
* much of it.
|
|
286
|
+
*
|
|
287
|
+
* ## Why opt-in, and why only tools
|
|
288
|
+
*
|
|
289
|
+
* The package's default contract is that no content leaves the process, and
|
|
290
|
+
* that stays the default: an instrumentation that lands inside a PHI-bearing
|
|
291
|
+
* process must not start shipping tool payloads because of a version bump.
|
|
292
|
+
* The `content` option is the explicit decision, and it is per kind, so a
|
|
293
|
+
* deployment can export the arguments it controls without the results a
|
|
294
|
+
* third-party API returns.
|
|
295
|
+
*
|
|
296
|
+
* Tool content is the one kind this release can emit honestly. Flue declares
|
|
297
|
+
* `tool_start.args` and `tool.result` stable, so both are on the same payloads
|
|
298
|
+
* the span lifecycle already reads. Prompts and completions are NOT here:
|
|
299
|
+
* they live on `AgentMessage`, which Flue marks unstable and this package
|
|
300
|
+
* refuses to read (see `flue-types.ts`).
|
|
301
|
+
*
|
|
302
|
+
* ## The read-side contract this honours
|
|
303
|
+
*
|
|
304
|
+
* Runtype's ingest reads exactly these two attributes off an `execute_tool`
|
|
305
|
+
* span (`packages/shared/src/otlp-runtype-semconv.ts`,
|
|
306
|
+
* `GEN_AI_CONTENT_ATTRIBUTES`) and projects them to the tool card's input and
|
|
307
|
+
* output. The value is a STRING: a JSON document is parsed back, anything
|
|
308
|
+
* that does not parse is kept as plain text. It is admitted under a budget
|
|
309
|
+
* (`packages/execution-ingest/src/ingest-content.ts`): a single attribute over
|
|
310
|
+
* `MAX_CONTENT_ATTRIBUTE_CHARS` (256 KiB) is dropped WHOLE, not cut, and one
|
|
311
|
+
* span may carry at most `MAX_CONTENT_SPAN_CHARS` (384 KiB) across every
|
|
312
|
+
* content attribute. Both ceilings are why `maxChars` exists and why its
|
|
313
|
+
* default sits where it does: arguments plus result at the default each fit
|
|
314
|
+
* inside the per-span ceiling with room to spare, and a value this module has
|
|
315
|
+
* already cut can never trip the per-attribute drop.
|
|
316
|
+
*
|
|
317
|
+
* The canonical `gen_ai.tool.call.*` names are emitted for every value,
|
|
318
|
+
* string or not. Stock `@flue/opentelemetry` routes a stringified non-object
|
|
319
|
+
* to `flue.tool.call.*` instead; ingest reads that alias only as a fallback,
|
|
320
|
+
* and the semconv attribute type is a string either way, so there is nothing
|
|
321
|
+
* to gain from the split.
|
|
322
|
+
*
|
|
323
|
+
* The two kinds are encoded differently because ingest projects them
|
|
324
|
+
* differently: a RESULT is any text (a string as-is, anything else as JSON,
|
|
325
|
+
* cut in place when over budget), while ARGUMENTS are always a JSON object,
|
|
326
|
+
* see {@link encodeArguments}.
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* What to emit, per tool span. Every field is optional and every default is
|
|
330
|
+
* OFF; passing `{}` changes nothing.
|
|
331
|
+
*/
|
|
332
|
+
interface FlueContentOptions {
|
|
333
|
+
/**
|
|
334
|
+
* Emit the call's arguments as `gen_ai.tool.call.arguments` when the
|
|
335
|
+
* `execute_tool` span opens. Read from Flue's stable `tool_start.args`.
|
|
336
|
+
*/
|
|
337
|
+
toolArguments?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Emit the tool's return value as `gen_ai.tool.call.result` just before the
|
|
340
|
+
* `execute_tool` span closes. Read from Flue's stable `tool.result` (2.x's
|
|
341
|
+
* `effectiveResult` — what the model was actually shown — when present).
|
|
342
|
+
*
|
|
343
|
+
* A FAILED tool's result is never emitted: on both Flue lines it commonly
|
|
344
|
+
* IS the error payload, and error messages stay off the wire under every
|
|
345
|
+
* setting of this option. The span still closes with its error type and
|
|
346
|
+
* exception class name, as before.
|
|
347
|
+
*/
|
|
348
|
+
toolResults?: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* Ceiling on ONE emitted value, in UTF-16 code units, marker included.
|
|
351
|
+
* Defaults to {@link DEFAULT_CONTENT_MAX_CHARS}. A longer value is cut and
|
|
352
|
+
* ends in an explicit `…[truncated N chars]` marker, so a cut value never
|
|
353
|
+
* masquerades as complete.
|
|
354
|
+
*
|
|
355
|
+
* Clamped to {@link INGEST_CONTENT_ATTRIBUTE_CEILING}: above that, Runtype's
|
|
356
|
+
* ingest drops the attribute whole, which is strictly worse than a cut.
|
|
357
|
+
*/
|
|
358
|
+
maxChars?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Rewrite a value before it is encoded. Runs on the RAW value, before
|
|
361
|
+
* stringification and truncation, so a redaction can address fields by name.
|
|
362
|
+
* Return `undefined` to suppress the attribute for this call entirely.
|
|
363
|
+
*
|
|
364
|
+
* An exception thrown here suppresses the attribute and nothing else: the
|
|
365
|
+
* span still opens and closes normally.
|
|
366
|
+
*/
|
|
367
|
+
redact?: (value: unknown, context: FlueContentRedactContext) => unknown;
|
|
368
|
+
}
|
|
369
|
+
interface FlueContentRedactContext {
|
|
370
|
+
kind: 'arguments' | 'result';
|
|
371
|
+
toolName: string;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* The default per-value ceiling: 64 KiB of ASCII. Large enough for any
|
|
375
|
+
* realistic tool argument object and most results, and small enough that
|
|
376
|
+
* arguments plus result on one span sit well inside ingest's 384 KiB per-span
|
|
377
|
+
* ceiling — a customer who raises it should know that ceiling exists.
|
|
378
|
+
*/
|
|
379
|
+
declare const DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
380
|
+
/**
|
|
381
|
+
* Runtype ingest's per-attribute ceiling (`MAX_CONTENT_ATTRIBUTE_CHARS` in
|
|
382
|
+
* `packages/execution-ingest/src/ingest-content.ts`), inlined for the same
|
|
383
|
+
* reason `semconv.ts` inlines the attribute names — that package is private
|
|
384
|
+
* and cannot resolve for a customer installing from npm. `tests/contract.test.ts`
|
|
385
|
+
* pins the two equal.
|
|
386
|
+
*/
|
|
387
|
+
declare const INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
388
|
+
|
|
276
389
|
/**
|
|
277
390
|
* Flue observations → span intents. PURE: no `@opentelemetry/api`, no clock, no
|
|
278
391
|
* I/O. `spans.ts` is the only module that touches a `Tracer`.
|
|
@@ -378,8 +491,8 @@ interface CloseSpanIntent {
|
|
|
378
491
|
* Present when the span failed. Carries the error TYPE and the exception
|
|
379
492
|
* class NAME only — never a message and never a stack. Both are content: a
|
|
380
493
|
* provider error message routinely quotes the prompt back, and a stack
|
|
381
|
-
* exposes filesystem paths and deployment layout.
|
|
382
|
-
* content
|
|
494
|
+
* exposes filesystem paths and deployment layout. Neither is read under any
|
|
495
|
+
* setting; the `content` opt-in covers tool payloads only.
|
|
383
496
|
*/
|
|
384
497
|
error?: {
|
|
385
498
|
type: string;
|
|
@@ -402,6 +515,13 @@ interface FlueProjectionOptions {
|
|
|
402
515
|
* runs inside the delegating agent's trace, and one trace is one execution.
|
|
403
516
|
*/
|
|
404
517
|
agents?: Record<string, string>;
|
|
518
|
+
/**
|
|
519
|
+
* Opt in to tool content on `execute_tool` spans. Default: nothing. See
|
|
520
|
+
* {@link FlueContentOptions} for the per-kind switches, the size ceiling
|
|
521
|
+
* and the redaction hook, and `content.ts` for the read-side contract they
|
|
522
|
+
* honour.
|
|
523
|
+
*/
|
|
524
|
+
content?: FlueContentOptions;
|
|
405
525
|
}
|
|
406
526
|
/**
|
|
407
527
|
* Resource attributes for the customer's `Resource`. Exported rather than set
|
|
@@ -465,6 +585,18 @@ declare const GEN_AI: {
|
|
|
465
585
|
readonly serverAddress: "server.address";
|
|
466
586
|
readonly serverPort: "server.port";
|
|
467
587
|
};
|
|
588
|
+
/**
|
|
589
|
+
* GenAI semconv CONTENT attribute names — the semconv Opt-In set. Kept apart
|
|
590
|
+
* from {@link GEN_AI} on purpose: that table is what this package emits by
|
|
591
|
+
* DEFAULT, and the contract test pins that no content name is in it. These
|
|
592
|
+
* two reach the wire only under the `content` option (`content.ts`), and only
|
|
593
|
+
* on `execute_tool` spans. Mirrors `GEN_AI_CONTENT_ATTRIBUTES` in
|
|
594
|
+
* `packages/shared/src/otlp-runtype-semconv.ts`, which is what ingest reads.
|
|
595
|
+
*/
|
|
596
|
+
declare const GEN_AI_CONTENT: {
|
|
597
|
+
readonly toolCallArguments: "gen_ai.tool.call.arguments";
|
|
598
|
+
readonly toolCallResult: "gen_ai.tool.call.result";
|
|
599
|
+
};
|
|
468
600
|
/**
|
|
469
601
|
* Runtype's extension vocabulary. Placement is part of the contract and the
|
|
470
602
|
* reader enforces it — see the `RUNTYPE_ATTRIBUTES` doc block in the shared
|
|
@@ -534,12 +666,13 @@ declare const ADAPTER_VERSION: string;
|
|
|
534
666
|
* pipeline fights the one the customer already runs, and in a stack exporting
|
|
535
667
|
* to two backends it silently wins one of those fights.
|
|
536
668
|
*
|
|
537
|
-
* It also emits **no content
|
|
669
|
+
* It also emits **no content by default** — no prompts, no completions, no
|
|
538
670
|
* tool arguments or results, no error messages, no stack traces. Only
|
|
539
|
-
* identifiers, structure and metrics reach the wire
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
671
|
+
* identifiers, structure and metrics reach the wire, which is the right
|
|
672
|
+
* default for a package landing inside a PHI-bearing process. The one
|
|
673
|
+
* explicit opt-in is `content`: tool arguments and results on `execute_tool`
|
|
674
|
+
* spans, per kind, size-capped and redactable (`content.ts`). Prompts and
|
|
675
|
+
* completions remain off the table until Flue exposes a stable message shape.
|
|
543
676
|
*
|
|
544
677
|
* ## Composing with other instrumentations
|
|
545
678
|
*
|
|
@@ -582,4 +715,4 @@ interface RuntypeFlueInstrumentationOptions extends FlueProjectionOptions {
|
|
|
582
715
|
*/
|
|
583
716
|
declare function createRuntypeFlueInstrumentation(options?: RuntypeFlueInstrumentationOptions): FlueInstrumentation;
|
|
584
717
|
|
|
585
|
-
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 };
|
|
718
|
+
export { ADAPTER_NAME, ADAPTER_VERSION, DEFAULT_CONTENT_MAX_CHARS, type FlueContentOptions, type FlueContentRedactContext, type FlueInstrumentation, type FlueProjectionOptions, GEN_AI, GEN_AI_CONTENT, INGEST_CONTENT_ATTRIBUTE_CEILING, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -193,12 +193,14 @@ type FlueEventVariant = {
|
|
|
193
193
|
type: 'tool_start';
|
|
194
194
|
toolName: string;
|
|
195
195
|
toolCallId: string;
|
|
196
|
+
args?: unknown;
|
|
196
197
|
} | {
|
|
197
198
|
type: 'tool';
|
|
198
199
|
toolName: string;
|
|
199
200
|
toolCallId: string;
|
|
200
201
|
isError?: boolean;
|
|
201
202
|
result?: unknown;
|
|
203
|
+
effectiveResult?: unknown;
|
|
202
204
|
durationMs?: number;
|
|
203
205
|
} | {
|
|
204
206
|
type: 'submission_settled';
|
|
@@ -273,6 +275,117 @@ interface FlueInstrumentation {
|
|
|
273
275
|
dispose(): void | Promise<void>;
|
|
274
276
|
}
|
|
275
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Opt-in tool content: `gen_ai.tool.call.arguments` and
|
|
280
|
+
* `gen_ai.tool.call.result` on `execute_tool` spans.
|
|
281
|
+
*
|
|
282
|
+
* PURE, like `projection.ts`: value in, attribute string out. The projection
|
|
283
|
+
* decides WHEN to call this (arguments when the tool span opens, the result
|
|
284
|
+
* just before it closes); this module decides WHAT reaches the wire and how
|
|
285
|
+
* much of it.
|
|
286
|
+
*
|
|
287
|
+
* ## Why opt-in, and why only tools
|
|
288
|
+
*
|
|
289
|
+
* The package's default contract is that no content leaves the process, and
|
|
290
|
+
* that stays the default: an instrumentation that lands inside a PHI-bearing
|
|
291
|
+
* process must not start shipping tool payloads because of a version bump.
|
|
292
|
+
* The `content` option is the explicit decision, and it is per kind, so a
|
|
293
|
+
* deployment can export the arguments it controls without the results a
|
|
294
|
+
* third-party API returns.
|
|
295
|
+
*
|
|
296
|
+
* Tool content is the one kind this release can emit honestly. Flue declares
|
|
297
|
+
* `tool_start.args` and `tool.result` stable, so both are on the same payloads
|
|
298
|
+
* the span lifecycle already reads. Prompts and completions are NOT here:
|
|
299
|
+
* they live on `AgentMessage`, which Flue marks unstable and this package
|
|
300
|
+
* refuses to read (see `flue-types.ts`).
|
|
301
|
+
*
|
|
302
|
+
* ## The read-side contract this honours
|
|
303
|
+
*
|
|
304
|
+
* Runtype's ingest reads exactly these two attributes off an `execute_tool`
|
|
305
|
+
* span (`packages/shared/src/otlp-runtype-semconv.ts`,
|
|
306
|
+
* `GEN_AI_CONTENT_ATTRIBUTES`) and projects them to the tool card's input and
|
|
307
|
+
* output. The value is a STRING: a JSON document is parsed back, anything
|
|
308
|
+
* that does not parse is kept as plain text. It is admitted under a budget
|
|
309
|
+
* (`packages/execution-ingest/src/ingest-content.ts`): a single attribute over
|
|
310
|
+
* `MAX_CONTENT_ATTRIBUTE_CHARS` (256 KiB) is dropped WHOLE, not cut, and one
|
|
311
|
+
* span may carry at most `MAX_CONTENT_SPAN_CHARS` (384 KiB) across every
|
|
312
|
+
* content attribute. Both ceilings are why `maxChars` exists and why its
|
|
313
|
+
* default sits where it does: arguments plus result at the default each fit
|
|
314
|
+
* inside the per-span ceiling with room to spare, and a value this module has
|
|
315
|
+
* already cut can never trip the per-attribute drop.
|
|
316
|
+
*
|
|
317
|
+
* The canonical `gen_ai.tool.call.*` names are emitted for every value,
|
|
318
|
+
* string or not. Stock `@flue/opentelemetry` routes a stringified non-object
|
|
319
|
+
* to `flue.tool.call.*` instead; ingest reads that alias only as a fallback,
|
|
320
|
+
* and the semconv attribute type is a string either way, so there is nothing
|
|
321
|
+
* to gain from the split.
|
|
322
|
+
*
|
|
323
|
+
* The two kinds are encoded differently because ingest projects them
|
|
324
|
+
* differently: a RESULT is any text (a string as-is, anything else as JSON,
|
|
325
|
+
* cut in place when over budget), while ARGUMENTS are always a JSON object,
|
|
326
|
+
* see {@link encodeArguments}.
|
|
327
|
+
*/
|
|
328
|
+
/**
|
|
329
|
+
* What to emit, per tool span. Every field is optional and every default is
|
|
330
|
+
* OFF; passing `{}` changes nothing.
|
|
331
|
+
*/
|
|
332
|
+
interface FlueContentOptions {
|
|
333
|
+
/**
|
|
334
|
+
* Emit the call's arguments as `gen_ai.tool.call.arguments` when the
|
|
335
|
+
* `execute_tool` span opens. Read from Flue's stable `tool_start.args`.
|
|
336
|
+
*/
|
|
337
|
+
toolArguments?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Emit the tool's return value as `gen_ai.tool.call.result` just before the
|
|
340
|
+
* `execute_tool` span closes. Read from Flue's stable `tool.result` (2.x's
|
|
341
|
+
* `effectiveResult` — what the model was actually shown — when present).
|
|
342
|
+
*
|
|
343
|
+
* A FAILED tool's result is never emitted: on both Flue lines it commonly
|
|
344
|
+
* IS the error payload, and error messages stay off the wire under every
|
|
345
|
+
* setting of this option. The span still closes with its error type and
|
|
346
|
+
* exception class name, as before.
|
|
347
|
+
*/
|
|
348
|
+
toolResults?: boolean;
|
|
349
|
+
/**
|
|
350
|
+
* Ceiling on ONE emitted value, in UTF-16 code units, marker included.
|
|
351
|
+
* Defaults to {@link DEFAULT_CONTENT_MAX_CHARS}. A longer value is cut and
|
|
352
|
+
* ends in an explicit `…[truncated N chars]` marker, so a cut value never
|
|
353
|
+
* masquerades as complete.
|
|
354
|
+
*
|
|
355
|
+
* Clamped to {@link INGEST_CONTENT_ATTRIBUTE_CEILING}: above that, Runtype's
|
|
356
|
+
* ingest drops the attribute whole, which is strictly worse than a cut.
|
|
357
|
+
*/
|
|
358
|
+
maxChars?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Rewrite a value before it is encoded. Runs on the RAW value, before
|
|
361
|
+
* stringification and truncation, so a redaction can address fields by name.
|
|
362
|
+
* Return `undefined` to suppress the attribute for this call entirely.
|
|
363
|
+
*
|
|
364
|
+
* An exception thrown here suppresses the attribute and nothing else: the
|
|
365
|
+
* span still opens and closes normally.
|
|
366
|
+
*/
|
|
367
|
+
redact?: (value: unknown, context: FlueContentRedactContext) => unknown;
|
|
368
|
+
}
|
|
369
|
+
interface FlueContentRedactContext {
|
|
370
|
+
kind: 'arguments' | 'result';
|
|
371
|
+
toolName: string;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* The default per-value ceiling: 64 KiB of ASCII. Large enough for any
|
|
375
|
+
* realistic tool argument object and most results, and small enough that
|
|
376
|
+
* arguments plus result on one span sit well inside ingest's 384 KiB per-span
|
|
377
|
+
* ceiling — a customer who raises it should know that ceiling exists.
|
|
378
|
+
*/
|
|
379
|
+
declare const DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
380
|
+
/**
|
|
381
|
+
* Runtype ingest's per-attribute ceiling (`MAX_CONTENT_ATTRIBUTE_CHARS` in
|
|
382
|
+
* `packages/execution-ingest/src/ingest-content.ts`), inlined for the same
|
|
383
|
+
* reason `semconv.ts` inlines the attribute names — that package is private
|
|
384
|
+
* and cannot resolve for a customer installing from npm. `tests/contract.test.ts`
|
|
385
|
+
* pins the two equal.
|
|
386
|
+
*/
|
|
387
|
+
declare const INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
388
|
+
|
|
276
389
|
/**
|
|
277
390
|
* Flue observations → span intents. PURE: no `@opentelemetry/api`, no clock, no
|
|
278
391
|
* I/O. `spans.ts` is the only module that touches a `Tracer`.
|
|
@@ -378,8 +491,8 @@ interface CloseSpanIntent {
|
|
|
378
491
|
* Present when the span failed. Carries the error TYPE and the exception
|
|
379
492
|
* class NAME only — never a message and never a stack. Both are content: a
|
|
380
493
|
* provider error message routinely quotes the prompt back, and a stack
|
|
381
|
-
* exposes filesystem paths and deployment layout.
|
|
382
|
-
* content
|
|
494
|
+
* exposes filesystem paths and deployment layout. Neither is read under any
|
|
495
|
+
* setting; the `content` opt-in covers tool payloads only.
|
|
383
496
|
*/
|
|
384
497
|
error?: {
|
|
385
498
|
type: string;
|
|
@@ -402,6 +515,13 @@ interface FlueProjectionOptions {
|
|
|
402
515
|
* runs inside the delegating agent's trace, and one trace is one execution.
|
|
403
516
|
*/
|
|
404
517
|
agents?: Record<string, string>;
|
|
518
|
+
/**
|
|
519
|
+
* Opt in to tool content on `execute_tool` spans. Default: nothing. See
|
|
520
|
+
* {@link FlueContentOptions} for the per-kind switches, the size ceiling
|
|
521
|
+
* and the redaction hook, and `content.ts` for the read-side contract they
|
|
522
|
+
* honour.
|
|
523
|
+
*/
|
|
524
|
+
content?: FlueContentOptions;
|
|
405
525
|
}
|
|
406
526
|
/**
|
|
407
527
|
* Resource attributes for the customer's `Resource`. Exported rather than set
|
|
@@ -465,6 +585,18 @@ declare const GEN_AI: {
|
|
|
465
585
|
readonly serverAddress: "server.address";
|
|
466
586
|
readonly serverPort: "server.port";
|
|
467
587
|
};
|
|
588
|
+
/**
|
|
589
|
+
* GenAI semconv CONTENT attribute names — the semconv Opt-In set. Kept apart
|
|
590
|
+
* from {@link GEN_AI} on purpose: that table is what this package emits by
|
|
591
|
+
* DEFAULT, and the contract test pins that no content name is in it. These
|
|
592
|
+
* two reach the wire only under the `content` option (`content.ts`), and only
|
|
593
|
+
* on `execute_tool` spans. Mirrors `GEN_AI_CONTENT_ATTRIBUTES` in
|
|
594
|
+
* `packages/shared/src/otlp-runtype-semconv.ts`, which is what ingest reads.
|
|
595
|
+
*/
|
|
596
|
+
declare const GEN_AI_CONTENT: {
|
|
597
|
+
readonly toolCallArguments: "gen_ai.tool.call.arguments";
|
|
598
|
+
readonly toolCallResult: "gen_ai.tool.call.result";
|
|
599
|
+
};
|
|
468
600
|
/**
|
|
469
601
|
* Runtype's extension vocabulary. Placement is part of the contract and the
|
|
470
602
|
* reader enforces it — see the `RUNTYPE_ATTRIBUTES` doc block in the shared
|
|
@@ -534,12 +666,13 @@ declare const ADAPTER_VERSION: string;
|
|
|
534
666
|
* pipeline fights the one the customer already runs, and in a stack exporting
|
|
535
667
|
* to two backends it silently wins one of those fights.
|
|
536
668
|
*
|
|
537
|
-
* It also emits **no content
|
|
669
|
+
* It also emits **no content by default** — no prompts, no completions, no
|
|
538
670
|
* tool arguments or results, no error messages, no stack traces. Only
|
|
539
|
-
* identifiers, structure and metrics reach the wire
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
671
|
+
* identifiers, structure and metrics reach the wire, which is the right
|
|
672
|
+
* default for a package landing inside a PHI-bearing process. The one
|
|
673
|
+
* explicit opt-in is `content`: tool arguments and results on `execute_tool`
|
|
674
|
+
* spans, per kind, size-capped and redactable (`content.ts`). Prompts and
|
|
675
|
+
* completions remain off the table until Flue exposes a stable message shape.
|
|
543
676
|
*
|
|
544
677
|
* ## Composing with other instrumentations
|
|
545
678
|
*
|
|
@@ -582,4 +715,4 @@ interface RuntypeFlueInstrumentationOptions extends FlueProjectionOptions {
|
|
|
582
715
|
*/
|
|
583
716
|
declare function createRuntypeFlueInstrumentation(options?: RuntypeFlueInstrumentationOptions): FlueInstrumentation;
|
|
584
717
|
|
|
585
|
-
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 };
|
|
718
|
+
export { ADAPTER_NAME, ADAPTER_VERSION, DEFAULT_CONTENT_MAX_CHARS, type FlueContentOptions, type FlueContentRedactContext, type FlueInstrumentation, type FlueProjectionOptions, GEN_AI, GEN_AI_CONTENT, INGEST_CONTENT_ATTRIBUTE_CEILING, 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 };
|
package/dist/index.mjs
CHANGED
|
@@ -42,8 +42,83 @@ function mapSettlementOutcome(outcome) {
|
|
|
42
42
|
return void 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/content.ts
|
|
46
|
+
var DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
47
|
+
var INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
48
|
+
function resolveContentPolicy(options) {
|
|
49
|
+
const requested = options?.maxChars;
|
|
50
|
+
const maxChars = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), INGEST_CONTENT_ATTRIBUTE_CEILING) : DEFAULT_CONTENT_MAX_CHARS;
|
|
51
|
+
return {
|
|
52
|
+
toolArguments: options?.toolArguments === true,
|
|
53
|
+
toolResults: options?.toolResults === true,
|
|
54
|
+
maxChars,
|
|
55
|
+
redact: options?.redact
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function encodeContentValue(value, policy, context3) {
|
|
59
|
+
if (value === void 0) return void 0;
|
|
60
|
+
let redacted = value;
|
|
61
|
+
if (policy.redact) {
|
|
62
|
+
try {
|
|
63
|
+
redacted = policy.redact(value, context3);
|
|
64
|
+
} catch {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
if (redacted === void 0) return void 0;
|
|
68
|
+
}
|
|
69
|
+
if (context3.kind === "arguments") return encodeArguments(redacted, policy.maxChars);
|
|
70
|
+
const encoded = stringify(redacted);
|
|
71
|
+
if (encoded === void 0) return void 0;
|
|
72
|
+
return truncate(encoded, policy.maxChars);
|
|
73
|
+
}
|
|
74
|
+
function encodeArguments(value, maxChars) {
|
|
75
|
+
const shaped = isPlainObject(value) ? value : { value };
|
|
76
|
+
const encoded = stringify(shaped);
|
|
77
|
+
if (encoded === void 0) return void 0;
|
|
78
|
+
if (encoded.length <= maxChars) return encoded;
|
|
79
|
+
let budget = maxChars - TRUNCATED_WRAPPER_OVERHEAD;
|
|
80
|
+
for (let round = 0; round < 8 && budget > 0; round += 1) {
|
|
81
|
+
const wrapped = JSON.stringify({ truncated: truncate(encoded, budget) });
|
|
82
|
+
if (wrapped.length <= maxChars) return wrapped;
|
|
83
|
+
budget -= wrapped.length - maxChars;
|
|
84
|
+
}
|
|
85
|
+
return JSON.stringify({ truncated: truncate(encoded, Math.max(0, budget)) });
|
|
86
|
+
}
|
|
87
|
+
var TRUNCATED_WRAPPER_OVERHEAD = 16;
|
|
88
|
+
function isPlainObject(value) {
|
|
89
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
90
|
+
}
|
|
91
|
+
function stringify(value) {
|
|
92
|
+
if (typeof value === "string") return value;
|
|
93
|
+
try {
|
|
94
|
+
const encoded = JSON.stringify(value);
|
|
95
|
+
return typeof encoded === "string" ? encoded : void 0;
|
|
96
|
+
} catch {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function truncate(value, maxChars) {
|
|
101
|
+
if (value.length <= maxChars) return value;
|
|
102
|
+
const upperBound = truncationMarker(value.length);
|
|
103
|
+
let keep = Math.max(0, maxChars - upperBound.length);
|
|
104
|
+
let marker = truncationMarker(value.length - keep);
|
|
105
|
+
keep = Math.max(0, maxChars - marker.length);
|
|
106
|
+
marker = truncationMarker(value.length - keep);
|
|
107
|
+
if (keep > 0 && isHighSurrogate(value.charCodeAt(keep - 1))) {
|
|
108
|
+
keep -= 1;
|
|
109
|
+
marker = truncationMarker(value.length - keep);
|
|
110
|
+
}
|
|
111
|
+
return value.slice(0, keep) + marker;
|
|
112
|
+
}
|
|
113
|
+
function truncationMarker(droppedChars) {
|
|
114
|
+
return `\u2026[truncated ${droppedChars} chars]`;
|
|
115
|
+
}
|
|
116
|
+
function isHighSurrogate(code) {
|
|
117
|
+
return code >= 55296 && code <= 56319;
|
|
118
|
+
}
|
|
119
|
+
|
|
45
120
|
// package.json
|
|
46
|
-
var version = "0.
|
|
121
|
+
var version = "0.4.0";
|
|
47
122
|
|
|
48
123
|
// src/semconv.ts
|
|
49
124
|
var GEN_AI = {
|
|
@@ -71,6 +146,10 @@ var GEN_AI = {
|
|
|
71
146
|
serverAddress: "server.address",
|
|
72
147
|
serverPort: "server.port"
|
|
73
148
|
};
|
|
149
|
+
var GEN_AI_CONTENT = {
|
|
150
|
+
toolCallArguments: "gen_ai.tool.call.arguments",
|
|
151
|
+
toolCallResult: "gen_ai.tool.call.result"
|
|
152
|
+
};
|
|
74
153
|
var GEN_AI_OPERATION_INVOKE_AGENT = "invoke_agent";
|
|
75
154
|
var GEN_AI_OPERATION_CHAT = "chat";
|
|
76
155
|
var GEN_AI_OPERATION_EXECUTE_TOOL = "execute_tool";
|
|
@@ -140,6 +219,7 @@ function runtypeFlueResourceAttributes(resource) {
|
|
|
140
219
|
};
|
|
141
220
|
}
|
|
142
221
|
function createFlueProjection(options = {}) {
|
|
222
|
+
const content = resolveContentPolicy(options.content);
|
|
143
223
|
const open = /* @__PURE__ */ new Map();
|
|
144
224
|
const envelopes = /* @__PURE__ */ new Map();
|
|
145
225
|
const envelopeByChain = /* @__PURE__ */ new Map();
|
|
@@ -224,7 +304,12 @@ function createFlueProjection(options = {}) {
|
|
|
224
304
|
// any inbound id; it does not key the row, which stays derived from
|
|
225
305
|
// the trace id.
|
|
226
306
|
...isEnvelope && event.submissionId ? { [RUNTYPE.executionId]: event.submissionId } : {},
|
|
227
|
-
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {}
|
|
307
|
+
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {},
|
|
308
|
+
// WHY(README.md): the resource placement is the customer's to wire and is routinely skipped.
|
|
309
|
+
...isEnvelope ? {
|
|
310
|
+
[RUNTYPE.adapterName]: ADAPTER_NAME,
|
|
311
|
+
[RUNTYPE.adapterVersion]: ADAPTER_VERSION
|
|
312
|
+
} : {}
|
|
228
313
|
}
|
|
229
314
|
}
|
|
230
315
|
];
|
|
@@ -310,7 +395,12 @@ function createFlueProjection(options = {}) {
|
|
|
310
395
|
const ref = compactionKey(event);
|
|
311
396
|
if (open.has(ref)) return [];
|
|
312
397
|
const parentRef = resolveParentRef(event);
|
|
313
|
-
open.set(ref, {
|
|
398
|
+
open.set(ref, {
|
|
399
|
+
ref,
|
|
400
|
+
...parentRef ? { parentRef } : {},
|
|
401
|
+
chainKey: chainKey(event),
|
|
402
|
+
isTask: false
|
|
403
|
+
});
|
|
314
404
|
const reason = readString(event, "reason");
|
|
315
405
|
return [
|
|
316
406
|
{
|
|
@@ -348,7 +438,12 @@ function createFlueProjection(options = {}) {
|
|
|
348
438
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
349
439
|
const compactionRef = compactionKey(event);
|
|
350
440
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
351
|
-
open.set(ref, {
|
|
441
|
+
open.set(ref, {
|
|
442
|
+
ref,
|
|
443
|
+
...parentRef ? { parentRef } : {},
|
|
444
|
+
chainKey: chainKey(event),
|
|
445
|
+
isTask: false
|
|
446
|
+
});
|
|
352
447
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
353
448
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
354
449
|
let position;
|
|
@@ -376,7 +471,8 @@ function createFlueProjection(options = {}) {
|
|
|
376
471
|
if (position) turnPositions.set(ref, position);
|
|
377
472
|
}
|
|
378
473
|
if (envelope) {
|
|
379
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
474
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
475
|
+
envelope.requestModel = request.requestedModel;
|
|
380
476
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
381
477
|
}
|
|
382
478
|
return [
|
|
@@ -453,7 +549,12 @@ function createFlueProjection(options = {}) {
|
|
|
453
549
|
const ref = toolKey(event);
|
|
454
550
|
if (open.has(ref)) return [];
|
|
455
551
|
const parentRef = resolveParentRef(event);
|
|
456
|
-
open.set(ref, {
|
|
552
|
+
open.set(ref, {
|
|
553
|
+
ref,
|
|
554
|
+
...parentRef ? { parentRef } : {},
|
|
555
|
+
chainKey: chainKey(event),
|
|
556
|
+
isTask: false
|
|
557
|
+
});
|
|
457
558
|
const position = turnPositions.get(turnKey(event));
|
|
458
559
|
if (isCallerShellCall(toolName, event)) {
|
|
459
560
|
return [
|
|
@@ -477,6 +578,10 @@ function createFlueProjection(options = {}) {
|
|
|
477
578
|
}
|
|
478
579
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
479
580
|
const toolType = resolveToolType(toolName, event);
|
|
581
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
582
|
+
kind: "arguments",
|
|
583
|
+
toolName
|
|
584
|
+
}) : void 0;
|
|
480
585
|
return [
|
|
481
586
|
{
|
|
482
587
|
kind: "open",
|
|
@@ -490,6 +595,7 @@ function createFlueProjection(options = {}) {
|
|
|
490
595
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
491
596
|
[GEN_AI.toolName]: toolName,
|
|
492
597
|
[GEN_AI.toolCallId]: toolCallId,
|
|
598
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
493
599
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
494
600
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
495
601
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -508,9 +614,22 @@ function createFlueProjection(options = {}) {
|
|
|
508
614
|
const ref = toolKey(event);
|
|
509
615
|
if (!open.has(ref)) return [];
|
|
510
616
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
)
|
|
617
|
+
const isError = readBoolean(event, "isError") === true;
|
|
618
|
+
const toolName = readString(event, "toolName");
|
|
619
|
+
if (content.toolResults && !isError && toolName && !isCallerShellCall(toolName, event)) {
|
|
620
|
+
const result = encodeContentValue(readToolResult(event), content, {
|
|
621
|
+
kind: "result",
|
|
622
|
+
toolName
|
|
623
|
+
});
|
|
624
|
+
if (result !== void 0) {
|
|
625
|
+
intents.push({
|
|
626
|
+
kind: "update",
|
|
627
|
+
ref,
|
|
628
|
+
attributes: { [GEN_AI_CONTENT.toolCallResult]: result }
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
intents.push(closeIntent(ref, event, isError, readErrorInfo(event)));
|
|
514
633
|
open.delete(ref);
|
|
515
634
|
return intents;
|
|
516
635
|
}
|
|
@@ -702,6 +821,13 @@ function readErrorInfo(event) {
|
|
|
702
821
|
const detail = event.errorInfo;
|
|
703
822
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
704
823
|
}
|
|
824
|
+
function readUnknown(event, key) {
|
|
825
|
+
return event[key];
|
|
826
|
+
}
|
|
827
|
+
function readToolResult(event) {
|
|
828
|
+
const record = event;
|
|
829
|
+
return Object.prototype.hasOwnProperty.call(record, "effectiveResult") ? record.effectiveResult : record.result;
|
|
830
|
+
}
|
|
705
831
|
function readString(event, key) {
|
|
706
832
|
const value = event[key];
|
|
707
833
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -718,7 +844,12 @@ function positiveNumber(value) {
|
|
|
718
844
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
719
845
|
}
|
|
720
846
|
function chainKey(value) {
|
|
721
|
-
return identityKey("chain", [
|
|
847
|
+
return identityKey("chain", [
|
|
848
|
+
value.instanceId,
|
|
849
|
+
value.harness,
|
|
850
|
+
value.conversationId,
|
|
851
|
+
value.session
|
|
852
|
+
]);
|
|
722
853
|
}
|
|
723
854
|
function operationKey(value) {
|
|
724
855
|
return identityKey("operation", [
|
|
@@ -912,7 +1043,10 @@ function readIdField(operation, key) {
|
|
|
912
1043
|
export {
|
|
913
1044
|
ADAPTER_NAME,
|
|
914
1045
|
ADAPTER_VERSION,
|
|
1046
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
915
1047
|
GEN_AI,
|
|
1048
|
+
GEN_AI_CONTENT,
|
|
1049
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
916
1050
|
RUNTYPE,
|
|
917
1051
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
918
1052
|
RUNTYPE_SCHEMA_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/flue-otel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "OpenTelemetry instrumentation for Flue agents that emits GenAI semconv spans plus Runtype's runtype.* extension vocabulary, so a Flue run lands in Runtype at full fidelity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -31,10 +31,11 @@
|
|
|
31
31
|
"@opentelemetry/api": "^1.9.0",
|
|
32
32
|
"@opentelemetry/context-async-hooks": "^2.1.0",
|
|
33
33
|
"@opentelemetry/sdk-trace-base": "^2.1.0",
|
|
34
|
+
"@runtypelabs/execution-ingest": "0.6.18",
|
|
35
|
+
"@runtypelabs/shared": "3.35.1",
|
|
34
36
|
"tsup": "^8.0.2",
|
|
35
37
|
"typescript": "^6.0.3",
|
|
36
|
-
"vitest": "^4.1.0"
|
|
37
|
-
"@runtypelabs/shared": "3.28.0"
|
|
38
|
+
"vitest": "^4.1.0"
|
|
38
39
|
},
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"access": "public"
|