@runtypelabs/flue-otel 0.2.2 → 0.3.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/README.md +82 -9
- package/dist/index.cjs +141 -9
- package/dist/index.d.cts +141 -8
- package/dist/index.d.ts +141 -8
- package/dist/index.mjs +138 -9
- package/package.json +3 -2
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
|
|
@@ -105,11 +107,15 @@ subscriber. Its key is exported as `RUNTYPE_FLUE_INSTRUMENTATION_KEY`.
|
|
|
105
107
|
- `runtypeFlueResourceAttributes({ agentId? })` builds the `runtype.*` resource
|
|
106
108
|
attributes (`schema.version`, `adapter.name`, `adapter.version`, and
|
|
107
109
|
`agent.id` when given). Spread it into your SDK `Resource`.
|
|
108
|
-
- `GEN_AI` and `RUNTYPE`: the attribute-name constants this package emits
|
|
110
|
+
- `GEN_AI` and `RUNTYPE`: the attribute-name constants this package emits by
|
|
111
|
+
default; `GEN_AI_CONTENT`: the two it emits under the `content` opt-in.
|
|
112
|
+
- `DEFAULT_CONTENT_MAX_CHARS` and `INGEST_CONTENT_ATTRIBUTE_CEILING`: the
|
|
113
|
+
content size defaults, see [Tool content](#tool-content).
|
|
109
114
|
- `RUNTYPE_STOP_REASONS`, `RUNTYPE_TOOL_TYPES`, `RUNTYPE_SCHEMA_VERSION`,
|
|
110
115
|
`ADAPTER_NAME`, `ADAPTER_VERSION`, plus the `RuntypeStopReason`,
|
|
111
116
|
`RuntypeToolType`, `FlueInstrumentation`, `FlueProjectionOptions`,
|
|
112
|
-
`
|
|
117
|
+
`FlueContentOptions`, `FlueContentRedactContext`, `SpanAttributes` and
|
|
118
|
+
`SpanIntent` types.
|
|
113
119
|
|
|
114
120
|
## Attribution
|
|
115
121
|
|
|
@@ -179,7 +185,7 @@ backends.
|
|
|
179
185
|
| -------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
180
186
|
| `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
187
|
| `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
|
|
188
|
+
| `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
189
|
| `flue.task <agent>`, `flue.compaction`, `flue.operation shell` | delegation, compaction, host shell call | correlation ids only; framework structure, not agent invocations |
|
|
184
190
|
|
|
185
191
|
Every span also carries Flue's own `flue.*` correlation attributes
|
|
@@ -220,6 +226,73 @@ Rules the projection follows:
|
|
|
220
226
|
|
|
221
227
|
An absent attribute costs one column. A wrong one renders as a measurement.
|
|
222
228
|
|
|
229
|
+
## Tool content
|
|
230
|
+
|
|
231
|
+
By default an `execute_tool` span says which tool ran and how long it took, and
|
|
232
|
+
Runtype's trace view shows its card as "No tool content in this trace". To see
|
|
233
|
+
arguments and results on that card, opt in:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
createRuntypeFlueInstrumentation({
|
|
237
|
+
content: {
|
|
238
|
+
toolArguments: true, // gen_ai.tool.call.arguments, set when the span opens
|
|
239
|
+
toolResults: true, // gen_ai.tool.call.result, set just before the span ends
|
|
240
|
+
maxChars: 65_536, // per value, marker included; this is the default
|
|
241
|
+
redact: (value, { kind, toolName }) => value, // optional; return undefined to drop
|
|
242
|
+
},
|
|
243
|
+
})
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Each switch is independent and defaults to off, so `{}` changes nothing. The
|
|
247
|
+
values come from Flue's stable `tool_start.args` and `tool.result` payloads
|
|
248
|
+
(2.x's `effectiveResult`, what the model was actually shown, when present).
|
|
249
|
+
|
|
250
|
+
The attribute names are the GenAI semconv `gen_ai.tool.call.arguments` and
|
|
251
|
+
`gen_ai.tool.call.result` (exported as `GEN_AI_CONTENT`), which Runtype reads
|
|
252
|
+
onto the tool card's input and output. The two are encoded differently because
|
|
253
|
+
Runtype projects them differently:
|
|
254
|
+
|
|
255
|
+
- **Arguments are always a JSON object**, since that is the only shape the
|
|
256
|
+
reader projects. A non-object value (a bare string, an array) is carried as
|
|
257
|
+
`{ "value": ... }` rather than dropped. Over `maxChars`, the object is not
|
|
258
|
+
cut in place (a mid-document cut is not JSON and would be discarded); it is
|
|
259
|
+
replaced by `{ "truncated": "<head of the JSON>…[truncated N chars]" }`.
|
|
260
|
+
- **A result is text**: a string as-is, anything else as JSON. Over `maxChars`
|
|
261
|
+
it is cut and ends in `…[truncated N chars]`, so a cut value never looks
|
|
262
|
+
complete.
|
|
263
|
+
|
|
264
|
+
Ceilings worth knowing, because they decide what Runtype keeps:
|
|
265
|
+
|
|
266
|
+
- `maxChars` defaults to `DEFAULT_CONTENT_MAX_CHARS` (64 KiB) and is clamped to
|
|
267
|
+
`INGEST_CONTENT_ATTRIBUTE_CEILING` (256 KiB). Runtype drops a single content
|
|
268
|
+
attribute above that ceiling **whole** rather than cutting it, which is why
|
|
269
|
+
the package cuts first.
|
|
270
|
+
- One span may carry at most 384 KiB of content across every attribute, and one
|
|
271
|
+
export request at most 2 MiB of content. Arguments plus result at the default
|
|
272
|
+
fit inside the per-span ceiling; raise `maxChars` with that in mind.
|
|
273
|
+
- Content makes spans large. A `BatchSpanProcessor` at its default 512 spans per
|
|
274
|
+
request can push an export past Runtype's 8 MiB request bound, which rejects
|
|
275
|
+
the whole batch (envelope and usage included, not only the content). With
|
|
276
|
+
both switches on, set `maxExportBatchSize` to something like 64.
|
|
277
|
+
|
|
278
|
+
What is still never sent, whatever you set:
|
|
279
|
+
|
|
280
|
+
- **A failed tool's result.** On both Flue lines it is commonly the error
|
|
281
|
+
payload, and error messages stay off the wire. The span still closes with its
|
|
282
|
+
error type and exception class name.
|
|
283
|
+
- **The host's own shell call** (`session.shell()`), which is not a tool row.
|
|
284
|
+
- **Prompts and completions.** They live on `AgentMessage`, which Flue marks
|
|
285
|
+
unstable. This package will read them once Flue exposes a stable shape. One
|
|
286
|
+
edge to know: a delegation to a sub-agent is a tool call, and its
|
|
287
|
+
**arguments carry the prompt handed to that sub-agent**. If that matters for
|
|
288
|
+
your deployment, drop it with `redact` (return `undefined` when `toolName`
|
|
289
|
+
is the delegation tool).
|
|
290
|
+
|
|
291
|
+
`redact` runs on the raw value before encoding, so it can address fields by
|
|
292
|
+
name; it receives `{ kind: 'arguments' | 'result', toolName }`. Returning
|
|
293
|
+
`undefined` drops the attribute for that call. If it throws, the attribute is
|
|
294
|
+
dropped and the span is otherwise unaffected.
|
|
295
|
+
|
|
223
296
|
## Flue compatibility
|
|
224
297
|
|
|
225
298
|
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.3.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();
|
|
@@ -343,7 +426,12 @@ function createFlueProjection(options = {}) {
|
|
|
343
426
|
const ref = compactionKey(event);
|
|
344
427
|
if (open.has(ref)) return [];
|
|
345
428
|
const parentRef = resolveParentRef(event);
|
|
346
|
-
open.set(ref, {
|
|
429
|
+
open.set(ref, {
|
|
430
|
+
ref,
|
|
431
|
+
...parentRef ? { parentRef } : {},
|
|
432
|
+
chainKey: chainKey(event),
|
|
433
|
+
isTask: false
|
|
434
|
+
});
|
|
347
435
|
const reason = readString(event, "reason");
|
|
348
436
|
return [
|
|
349
437
|
{
|
|
@@ -381,7 +469,12 @@ function createFlueProjection(options = {}) {
|
|
|
381
469
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
382
470
|
const compactionRef = compactionKey(event);
|
|
383
471
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
384
|
-
open.set(ref, {
|
|
472
|
+
open.set(ref, {
|
|
473
|
+
ref,
|
|
474
|
+
...parentRef ? { parentRef } : {},
|
|
475
|
+
chainKey: chainKey(event),
|
|
476
|
+
isTask: false
|
|
477
|
+
});
|
|
385
478
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
386
479
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
387
480
|
let position;
|
|
@@ -409,7 +502,8 @@ function createFlueProjection(options = {}) {
|
|
|
409
502
|
if (position) turnPositions.set(ref, position);
|
|
410
503
|
}
|
|
411
504
|
if (envelope) {
|
|
412
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
505
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
506
|
+
envelope.requestModel = request.requestedModel;
|
|
413
507
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
414
508
|
}
|
|
415
509
|
return [
|
|
@@ -486,7 +580,12 @@ function createFlueProjection(options = {}) {
|
|
|
486
580
|
const ref = toolKey(event);
|
|
487
581
|
if (open.has(ref)) return [];
|
|
488
582
|
const parentRef = resolveParentRef(event);
|
|
489
|
-
open.set(ref, {
|
|
583
|
+
open.set(ref, {
|
|
584
|
+
ref,
|
|
585
|
+
...parentRef ? { parentRef } : {},
|
|
586
|
+
chainKey: chainKey(event),
|
|
587
|
+
isTask: false
|
|
588
|
+
});
|
|
490
589
|
const position = turnPositions.get(turnKey(event));
|
|
491
590
|
if (isCallerShellCall(toolName, event)) {
|
|
492
591
|
return [
|
|
@@ -510,6 +609,10 @@ function createFlueProjection(options = {}) {
|
|
|
510
609
|
}
|
|
511
610
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
512
611
|
const toolType = resolveToolType(toolName, event);
|
|
612
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
613
|
+
kind: "arguments",
|
|
614
|
+
toolName
|
|
615
|
+
}) : void 0;
|
|
513
616
|
return [
|
|
514
617
|
{
|
|
515
618
|
kind: "open",
|
|
@@ -523,6 +626,7 @@ function createFlueProjection(options = {}) {
|
|
|
523
626
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
524
627
|
[GEN_AI.toolName]: toolName,
|
|
525
628
|
[GEN_AI.toolCallId]: toolCallId,
|
|
629
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
526
630
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
527
631
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
528
632
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -541,9 +645,22 @@ function createFlueProjection(options = {}) {
|
|
|
541
645
|
const ref = toolKey(event);
|
|
542
646
|
if (!open.has(ref)) return [];
|
|
543
647
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
)
|
|
648
|
+
const isError = readBoolean(event, "isError") === true;
|
|
649
|
+
const toolName = readString(event, "toolName");
|
|
650
|
+
if (content.toolResults && !isError && toolName && !isCallerShellCall(toolName, event)) {
|
|
651
|
+
const result = encodeContentValue(readToolResult(event), content, {
|
|
652
|
+
kind: "result",
|
|
653
|
+
toolName
|
|
654
|
+
});
|
|
655
|
+
if (result !== void 0) {
|
|
656
|
+
intents.push({
|
|
657
|
+
kind: "update",
|
|
658
|
+
ref,
|
|
659
|
+
attributes: { [GEN_AI_CONTENT.toolCallResult]: result }
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
intents.push(closeIntent(ref, event, isError, readErrorInfo(event)));
|
|
547
664
|
open.delete(ref);
|
|
548
665
|
return intents;
|
|
549
666
|
}
|
|
@@ -735,6 +852,13 @@ function readErrorInfo(event) {
|
|
|
735
852
|
const detail = event.errorInfo;
|
|
736
853
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
737
854
|
}
|
|
855
|
+
function readUnknown(event, key) {
|
|
856
|
+
return event[key];
|
|
857
|
+
}
|
|
858
|
+
function readToolResult(event) {
|
|
859
|
+
const record = event;
|
|
860
|
+
return Object.prototype.hasOwnProperty.call(record, "effectiveResult") ? record.effectiveResult : record.result;
|
|
861
|
+
}
|
|
738
862
|
function readString(event, key) {
|
|
739
863
|
const value = event[key];
|
|
740
864
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -751,7 +875,12 @@ function positiveNumber(value) {
|
|
|
751
875
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
752
876
|
}
|
|
753
877
|
function chainKey(value) {
|
|
754
|
-
return identityKey("chain", [
|
|
878
|
+
return identityKey("chain", [
|
|
879
|
+
value.instanceId,
|
|
880
|
+
value.harness,
|
|
881
|
+
value.conversationId,
|
|
882
|
+
value.session
|
|
883
|
+
]);
|
|
755
884
|
}
|
|
756
885
|
function operationKey(value) {
|
|
757
886
|
return identityKey("operation", [
|
|
@@ -940,7 +1069,10 @@ function readIdField(operation, key) {
|
|
|
940
1069
|
0 && (module.exports = {
|
|
941
1070
|
ADAPTER_NAME,
|
|
942
1071
|
ADAPTER_VERSION,
|
|
1072
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
943
1073
|
GEN_AI,
|
|
1074
|
+
GEN_AI_CONTENT,
|
|
1075
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
944
1076
|
RUNTYPE,
|
|
945
1077
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
946
1078
|
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.3.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();
|
|
@@ -310,7 +390,12 @@ function createFlueProjection(options = {}) {
|
|
|
310
390
|
const ref = compactionKey(event);
|
|
311
391
|
if (open.has(ref)) return [];
|
|
312
392
|
const parentRef = resolveParentRef(event);
|
|
313
|
-
open.set(ref, {
|
|
393
|
+
open.set(ref, {
|
|
394
|
+
ref,
|
|
395
|
+
...parentRef ? { parentRef } : {},
|
|
396
|
+
chainKey: chainKey(event),
|
|
397
|
+
isTask: false
|
|
398
|
+
});
|
|
314
399
|
const reason = readString(event, "reason");
|
|
315
400
|
return [
|
|
316
401
|
{
|
|
@@ -348,7 +433,12 @@ function createFlueProjection(options = {}) {
|
|
|
348
433
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
349
434
|
const compactionRef = compactionKey(event);
|
|
350
435
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
351
|
-
open.set(ref, {
|
|
436
|
+
open.set(ref, {
|
|
437
|
+
ref,
|
|
438
|
+
...parentRef ? { parentRef } : {},
|
|
439
|
+
chainKey: chainKey(event),
|
|
440
|
+
isTask: false
|
|
441
|
+
});
|
|
352
442
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
353
443
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
354
444
|
let position;
|
|
@@ -376,7 +466,8 @@ function createFlueProjection(options = {}) {
|
|
|
376
466
|
if (position) turnPositions.set(ref, position);
|
|
377
467
|
}
|
|
378
468
|
if (envelope) {
|
|
379
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
469
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
470
|
+
envelope.requestModel = request.requestedModel;
|
|
380
471
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
381
472
|
}
|
|
382
473
|
return [
|
|
@@ -453,7 +544,12 @@ function createFlueProjection(options = {}) {
|
|
|
453
544
|
const ref = toolKey(event);
|
|
454
545
|
if (open.has(ref)) return [];
|
|
455
546
|
const parentRef = resolveParentRef(event);
|
|
456
|
-
open.set(ref, {
|
|
547
|
+
open.set(ref, {
|
|
548
|
+
ref,
|
|
549
|
+
...parentRef ? { parentRef } : {},
|
|
550
|
+
chainKey: chainKey(event),
|
|
551
|
+
isTask: false
|
|
552
|
+
});
|
|
457
553
|
const position = turnPositions.get(turnKey(event));
|
|
458
554
|
if (isCallerShellCall(toolName, event)) {
|
|
459
555
|
return [
|
|
@@ -477,6 +573,10 @@ function createFlueProjection(options = {}) {
|
|
|
477
573
|
}
|
|
478
574
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
479
575
|
const toolType = resolveToolType(toolName, event);
|
|
576
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
577
|
+
kind: "arguments",
|
|
578
|
+
toolName
|
|
579
|
+
}) : void 0;
|
|
480
580
|
return [
|
|
481
581
|
{
|
|
482
582
|
kind: "open",
|
|
@@ -490,6 +590,7 @@ function createFlueProjection(options = {}) {
|
|
|
490
590
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
491
591
|
[GEN_AI.toolName]: toolName,
|
|
492
592
|
[GEN_AI.toolCallId]: toolCallId,
|
|
593
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
493
594
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
494
595
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
495
596
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -508,9 +609,22 @@ function createFlueProjection(options = {}) {
|
|
|
508
609
|
const ref = toolKey(event);
|
|
509
610
|
if (!open.has(ref)) return [];
|
|
510
611
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
)
|
|
612
|
+
const isError = readBoolean(event, "isError") === true;
|
|
613
|
+
const toolName = readString(event, "toolName");
|
|
614
|
+
if (content.toolResults && !isError && toolName && !isCallerShellCall(toolName, event)) {
|
|
615
|
+
const result = encodeContentValue(readToolResult(event), content, {
|
|
616
|
+
kind: "result",
|
|
617
|
+
toolName
|
|
618
|
+
});
|
|
619
|
+
if (result !== void 0) {
|
|
620
|
+
intents.push({
|
|
621
|
+
kind: "update",
|
|
622
|
+
ref,
|
|
623
|
+
attributes: { [GEN_AI_CONTENT.toolCallResult]: result }
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
intents.push(closeIntent(ref, event, isError, readErrorInfo(event)));
|
|
514
628
|
open.delete(ref);
|
|
515
629
|
return intents;
|
|
516
630
|
}
|
|
@@ -702,6 +816,13 @@ function readErrorInfo(event) {
|
|
|
702
816
|
const detail = event.errorInfo;
|
|
703
817
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
704
818
|
}
|
|
819
|
+
function readUnknown(event, key) {
|
|
820
|
+
return event[key];
|
|
821
|
+
}
|
|
822
|
+
function readToolResult(event) {
|
|
823
|
+
const record = event;
|
|
824
|
+
return Object.prototype.hasOwnProperty.call(record, "effectiveResult") ? record.effectiveResult : record.result;
|
|
825
|
+
}
|
|
705
826
|
function readString(event, key) {
|
|
706
827
|
const value = event[key];
|
|
707
828
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -718,7 +839,12 @@ function positiveNumber(value) {
|
|
|
718
839
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
719
840
|
}
|
|
720
841
|
function chainKey(value) {
|
|
721
|
-
return identityKey("chain", [
|
|
842
|
+
return identityKey("chain", [
|
|
843
|
+
value.instanceId,
|
|
844
|
+
value.harness,
|
|
845
|
+
value.conversationId,
|
|
846
|
+
value.session
|
|
847
|
+
]);
|
|
722
848
|
}
|
|
723
849
|
function operationKey(value) {
|
|
724
850
|
return identityKey("operation", [
|
|
@@ -912,7 +1038,10 @@ function readIdField(operation, key) {
|
|
|
912
1038
|
export {
|
|
913
1039
|
ADAPTER_NAME,
|
|
914
1040
|
ADAPTER_VERSION,
|
|
1041
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
915
1042
|
GEN_AI,
|
|
1043
|
+
GEN_AI_CONTENT,
|
|
1044
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
916
1045
|
RUNTYPE,
|
|
917
1046
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
918
1047
|
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.3.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",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"tsup": "^8.0.2",
|
|
35
35
|
"typescript": "^6.0.3",
|
|
36
36
|
"vitest": "^4.1.0",
|
|
37
|
-
"@runtypelabs/
|
|
37
|
+
"@runtypelabs/execution-ingest": "0.6.12",
|
|
38
|
+
"@runtypelabs/shared": "3.32.0"
|
|
38
39
|
},
|
|
39
40
|
"publishConfig": {
|
|
40
41
|
"access": "public"
|