@runtypelabs/flue-otel 0.2.1 → 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 +87 -14
- package/dist/index.cjs +155 -11
- package/dist/index.d.cts +150 -8
- package/dist/index.d.ts +150 -8
- package/dist/index.mjs +152 -11
- 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
|
|
|
@@ -175,12 +181,12 @@ backends.
|
|
|
175
181
|
|
|
176
182
|
## What it emits
|
|
177
183
|
|
|
178
|
-
| Span | When | Carries
|
|
179
|
-
| -------------------------------------------------------------- | --------------------------------------- |
|
|
180
|
-
| `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
|
-
| `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),
|
|
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
|
|
183
|
-
| `flue.task <agent>`, `flue.compaction`, `flue.operation shell` | delegation, compaction, host shell call | correlation ids only; framework structure, not agent invocations
|
|
184
|
+
| Span | When | Carries |
|
|
185
|
+
| -------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
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 |
|
|
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) |
|
|
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 |
|
|
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
|
|
186
192
|
(`flue.instance.id`, `flue.submission.id`, `flue.agent.name`,
|
|
@@ -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";
|
|
@@ -118,7 +200,9 @@ var RUNTYPE = {
|
|
|
118
200
|
toolsReported: "runtype.tools.reported",
|
|
119
201
|
toolType: "runtype.tool.type",
|
|
120
202
|
turnId: "runtype.turn.id",
|
|
121
|
-
turnIndex: "runtype.turn.index"
|
|
203
|
+
turnIndex: "runtype.turn.index",
|
|
204
|
+
providerFinishReason: "runtype.provider.finish_reason",
|
|
205
|
+
gatewayLogId: "runtype.gateway.log_id"
|
|
122
206
|
};
|
|
123
207
|
var FLUE = {
|
|
124
208
|
instanceId: "flue.instance.id",
|
|
@@ -171,6 +255,7 @@ function runtypeFlueResourceAttributes(resource) {
|
|
|
171
255
|
};
|
|
172
256
|
}
|
|
173
257
|
function createFlueProjection(options = {}) {
|
|
258
|
+
const content = resolveContentPolicy(options.content);
|
|
174
259
|
const open = /* @__PURE__ */ new Map();
|
|
175
260
|
const envelopes = /* @__PURE__ */ new Map();
|
|
176
261
|
const envelopeByChain = /* @__PURE__ */ new Map();
|
|
@@ -341,7 +426,12 @@ function createFlueProjection(options = {}) {
|
|
|
341
426
|
const ref = compactionKey(event);
|
|
342
427
|
if (open.has(ref)) return [];
|
|
343
428
|
const parentRef = resolveParentRef(event);
|
|
344
|
-
open.set(ref, {
|
|
429
|
+
open.set(ref, {
|
|
430
|
+
ref,
|
|
431
|
+
...parentRef ? { parentRef } : {},
|
|
432
|
+
chainKey: chainKey(event),
|
|
433
|
+
isTask: false
|
|
434
|
+
});
|
|
345
435
|
const reason = readString(event, "reason");
|
|
346
436
|
return [
|
|
347
437
|
{
|
|
@@ -379,7 +469,12 @@ function createFlueProjection(options = {}) {
|
|
|
379
469
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
380
470
|
const compactionRef = compactionKey(event);
|
|
381
471
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
382
|
-
open.set(ref, {
|
|
472
|
+
open.set(ref, {
|
|
473
|
+
ref,
|
|
474
|
+
...parentRef ? { parentRef } : {},
|
|
475
|
+
chainKey: chainKey(event),
|
|
476
|
+
isTask: false
|
|
477
|
+
});
|
|
383
478
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
384
479
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
385
480
|
let position;
|
|
@@ -407,7 +502,8 @@ function createFlueProjection(options = {}) {
|
|
|
407
502
|
if (position) turnPositions.set(ref, position);
|
|
408
503
|
}
|
|
409
504
|
if (envelope) {
|
|
410
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
505
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
506
|
+
envelope.requestModel = request.requestedModel;
|
|
411
507
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
412
508
|
}
|
|
413
509
|
return [
|
|
@@ -459,7 +555,17 @@ function createFlueProjection(options = {}) {
|
|
|
459
555
|
...response.responseModel ? { [GEN_AI.responseModel]: response.responseModel } : {},
|
|
460
556
|
...response.responseId ? { [GEN_AI.responseId]: response.responseId } : {},
|
|
461
557
|
...response.finishReason ? { [GEN_AI.finishReasons]: [response.finishReason] } : {},
|
|
462
|
-
...usageAttributes(response.usage)
|
|
558
|
+
...usageAttributes(response.usage),
|
|
559
|
+
// Provider-diagnostic pointers, emitted only when Flue actually provides
|
|
560
|
+
// them. Both are optional and provider-dependent (Workers AI attaches
|
|
561
|
+
// both today); an absent value costs one column, a synthesized one would
|
|
562
|
+
// render as a measurement. `gatewayLogId` is a pointer to the content
|
|
563
|
+
// without shipping the content — a customer can click through to that
|
|
564
|
+
// exact request in their own AI Gateway dashboard. `providerFinishReason`
|
|
565
|
+
// is the provider's exact finish value before normalization, which our
|
|
566
|
+
// `GEN_AI.finishReasons` above deliberately hides.
|
|
567
|
+
...response.providerFinishReason ? { [RUNTYPE.providerFinishReason]: response.providerFinishReason } : {},
|
|
568
|
+
...response.gatewayLogId ? { [RUNTYPE.gatewayLogId]: response.gatewayLogId } : {}
|
|
463
569
|
};
|
|
464
570
|
const intents = [];
|
|
465
571
|
if (Object.keys(attributes).length > 0) intents.push({ kind: "update", ref, attributes });
|
|
@@ -474,7 +580,12 @@ function createFlueProjection(options = {}) {
|
|
|
474
580
|
const ref = toolKey(event);
|
|
475
581
|
if (open.has(ref)) return [];
|
|
476
582
|
const parentRef = resolveParentRef(event);
|
|
477
|
-
open.set(ref, {
|
|
583
|
+
open.set(ref, {
|
|
584
|
+
ref,
|
|
585
|
+
...parentRef ? { parentRef } : {},
|
|
586
|
+
chainKey: chainKey(event),
|
|
587
|
+
isTask: false
|
|
588
|
+
});
|
|
478
589
|
const position = turnPositions.get(turnKey(event));
|
|
479
590
|
if (isCallerShellCall(toolName, event)) {
|
|
480
591
|
return [
|
|
@@ -498,6 +609,10 @@ function createFlueProjection(options = {}) {
|
|
|
498
609
|
}
|
|
499
610
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
500
611
|
const toolType = resolveToolType(toolName, event);
|
|
612
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
613
|
+
kind: "arguments",
|
|
614
|
+
toolName
|
|
615
|
+
}) : void 0;
|
|
501
616
|
return [
|
|
502
617
|
{
|
|
503
618
|
kind: "open",
|
|
@@ -511,6 +626,7 @@ function createFlueProjection(options = {}) {
|
|
|
511
626
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
512
627
|
[GEN_AI.toolName]: toolName,
|
|
513
628
|
[GEN_AI.toolCallId]: toolCallId,
|
|
629
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
514
630
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
515
631
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
516
632
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -529,9 +645,22 @@ function createFlueProjection(options = {}) {
|
|
|
529
645
|
const ref = toolKey(event);
|
|
530
646
|
if (!open.has(ref)) return [];
|
|
531
647
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
)
|
|
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)));
|
|
535
664
|
open.delete(ref);
|
|
536
665
|
return intents;
|
|
537
666
|
}
|
|
@@ -723,6 +852,13 @@ function readErrorInfo(event) {
|
|
|
723
852
|
const detail = event.errorInfo;
|
|
724
853
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
725
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
|
+
}
|
|
726
862
|
function readString(event, key) {
|
|
727
863
|
const value = event[key];
|
|
728
864
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -739,7 +875,12 @@ function positiveNumber(value) {
|
|
|
739
875
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
740
876
|
}
|
|
741
877
|
function chainKey(value) {
|
|
742
|
-
return identityKey("chain", [
|
|
878
|
+
return identityKey("chain", [
|
|
879
|
+
value.instanceId,
|
|
880
|
+
value.harness,
|
|
881
|
+
value.conversationId,
|
|
882
|
+
value.session
|
|
883
|
+
]);
|
|
743
884
|
}
|
|
744
885
|
function operationKey(value) {
|
|
745
886
|
return identityKey("operation", [
|
|
@@ -928,7 +1069,10 @@ function readIdField(operation, key) {
|
|
|
928
1069
|
0 && (module.exports = {
|
|
929
1070
|
ADAPTER_NAME,
|
|
930
1071
|
ADAPTER_VERSION,
|
|
1072
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
931
1073
|
GEN_AI,
|
|
1074
|
+
GEN_AI_CONTENT,
|
|
1075
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
932
1076
|
RUNTYPE,
|
|
933
1077
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
934
1078
|
RUNTYPE_SCHEMA_VERSION,
|
package/dist/index.d.cts
CHANGED
|
@@ -100,6 +100,13 @@ interface FlueModelResponse {
|
|
|
100
100
|
finishReason?: string;
|
|
101
101
|
/** 2.x only. The provider's raw finish value before normalization. */
|
|
102
102
|
providerFinishReason?: string;
|
|
103
|
+
/**
|
|
104
|
+
* The response's own gateway log id (e.g. Cloudflare AI Gateway's
|
|
105
|
+
* `cf-aig-log-id`), for correlating a specific turn with its entry in the
|
|
106
|
+
* gateway dashboard. Telemetry only — present only when the provider records
|
|
107
|
+
* one. The Workers AI provider attaches it today.
|
|
108
|
+
*/
|
|
109
|
+
gatewayLogId?: string;
|
|
103
110
|
error?: FlueErrorInfo;
|
|
104
111
|
}
|
|
105
112
|
/**
|
|
@@ -186,12 +193,14 @@ type FlueEventVariant = {
|
|
|
186
193
|
type: 'tool_start';
|
|
187
194
|
toolName: string;
|
|
188
195
|
toolCallId: string;
|
|
196
|
+
args?: unknown;
|
|
189
197
|
} | {
|
|
190
198
|
type: 'tool';
|
|
191
199
|
toolName: string;
|
|
192
200
|
toolCallId: string;
|
|
193
201
|
isError?: boolean;
|
|
194
202
|
result?: unknown;
|
|
203
|
+
effectiveResult?: unknown;
|
|
195
204
|
durationMs?: number;
|
|
196
205
|
} | {
|
|
197
206
|
type: 'submission_settled';
|
|
@@ -266,6 +275,117 @@ interface FlueInstrumentation {
|
|
|
266
275
|
dispose(): void | Promise<void>;
|
|
267
276
|
}
|
|
268
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
|
+
|
|
269
389
|
/**
|
|
270
390
|
* Flue observations → span intents. PURE: no `@opentelemetry/api`, no clock, no
|
|
271
391
|
* I/O. `spans.ts` is the only module that touches a `Tracer`.
|
|
@@ -371,8 +491,8 @@ interface CloseSpanIntent {
|
|
|
371
491
|
* Present when the span failed. Carries the error TYPE and the exception
|
|
372
492
|
* class NAME only — never a message and never a stack. Both are content: a
|
|
373
493
|
* provider error message routinely quotes the prompt back, and a stack
|
|
374
|
-
* exposes filesystem paths and deployment layout.
|
|
375
|
-
* content
|
|
494
|
+
* exposes filesystem paths and deployment layout. Neither is read under any
|
|
495
|
+
* setting; the `content` opt-in covers tool payloads only.
|
|
376
496
|
*/
|
|
377
497
|
error?: {
|
|
378
498
|
type: string;
|
|
@@ -395,6 +515,13 @@ interface FlueProjectionOptions {
|
|
|
395
515
|
* runs inside the delegating agent's trace, and one trace is one execution.
|
|
396
516
|
*/
|
|
397
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;
|
|
398
525
|
}
|
|
399
526
|
/**
|
|
400
527
|
* Resource attributes for the customer's `Resource`. Exported rather than set
|
|
@@ -458,6 +585,18 @@ declare const GEN_AI: {
|
|
|
458
585
|
readonly serverAddress: "server.address";
|
|
459
586
|
readonly serverPort: "server.port";
|
|
460
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
|
+
};
|
|
461
600
|
/**
|
|
462
601
|
* Runtype's extension vocabulary. Placement is part of the contract and the
|
|
463
602
|
* reader enforces it — see the `RUNTYPE_ATTRIBUTES` doc block in the shared
|
|
@@ -475,6 +614,8 @@ declare const RUNTYPE: {
|
|
|
475
614
|
readonly toolType: "runtype.tool.type";
|
|
476
615
|
readonly turnId: "runtype.turn.id";
|
|
477
616
|
readonly turnIndex: "runtype.turn.index";
|
|
617
|
+
readonly providerFinishReason: "runtype.provider.finish_reason";
|
|
618
|
+
readonly gatewayLogId: "runtype.gateway.log_id";
|
|
478
619
|
};
|
|
479
620
|
/**
|
|
480
621
|
* `runtype.tool.type` values — the closed domain that drives display and
|
|
@@ -525,12 +666,13 @@ declare const ADAPTER_VERSION: string;
|
|
|
525
666
|
* pipeline fights the one the customer already runs, and in a stack exporting
|
|
526
667
|
* to two backends it silently wins one of those fights.
|
|
527
668
|
*
|
|
528
|
-
* It also emits **no content
|
|
669
|
+
* It also emits **no content by default** — no prompts, no completions, no
|
|
529
670
|
* tool arguments or results, no error messages, no stack traces. Only
|
|
530
|
-
* identifiers, structure and metrics reach the wire
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
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.
|
|
534
676
|
*
|
|
535
677
|
* ## Composing with other instrumentations
|
|
536
678
|
*
|
|
@@ -573,4 +715,4 @@ interface RuntypeFlueInstrumentationOptions extends FlueProjectionOptions {
|
|
|
573
715
|
*/
|
|
574
716
|
declare function createRuntypeFlueInstrumentation(options?: RuntypeFlueInstrumentationOptions): FlueInstrumentation;
|
|
575
717
|
|
|
576
|
-
export { ADAPTER_NAME, ADAPTER_VERSION, type FlueInstrumentation, type FlueProjectionOptions, GEN_AI, RUNTYPE, RUNTYPE_FLUE_INSTRUMENTATION_KEY, RUNTYPE_SCHEMA_VERSION, RUNTYPE_STOP_REASONS, RUNTYPE_TOOL_TYPES, type RuntypeFlueInstrumentationOptions, type RuntypeStopReason, type RuntypeToolType, type SpanAttributes, type SpanIntent, createRuntypeFlueInstrumentation, runtypeFlueResourceAttributes };
|
|
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
|
@@ -100,6 +100,13 @@ interface FlueModelResponse {
|
|
|
100
100
|
finishReason?: string;
|
|
101
101
|
/** 2.x only. The provider's raw finish value before normalization. */
|
|
102
102
|
providerFinishReason?: string;
|
|
103
|
+
/**
|
|
104
|
+
* The response's own gateway log id (e.g. Cloudflare AI Gateway's
|
|
105
|
+
* `cf-aig-log-id`), for correlating a specific turn with its entry in the
|
|
106
|
+
* gateway dashboard. Telemetry only — present only when the provider records
|
|
107
|
+
* one. The Workers AI provider attaches it today.
|
|
108
|
+
*/
|
|
109
|
+
gatewayLogId?: string;
|
|
103
110
|
error?: FlueErrorInfo;
|
|
104
111
|
}
|
|
105
112
|
/**
|
|
@@ -186,12 +193,14 @@ type FlueEventVariant = {
|
|
|
186
193
|
type: 'tool_start';
|
|
187
194
|
toolName: string;
|
|
188
195
|
toolCallId: string;
|
|
196
|
+
args?: unknown;
|
|
189
197
|
} | {
|
|
190
198
|
type: 'tool';
|
|
191
199
|
toolName: string;
|
|
192
200
|
toolCallId: string;
|
|
193
201
|
isError?: boolean;
|
|
194
202
|
result?: unknown;
|
|
203
|
+
effectiveResult?: unknown;
|
|
195
204
|
durationMs?: number;
|
|
196
205
|
} | {
|
|
197
206
|
type: 'submission_settled';
|
|
@@ -266,6 +275,117 @@ interface FlueInstrumentation {
|
|
|
266
275
|
dispose(): void | Promise<void>;
|
|
267
276
|
}
|
|
268
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
|
+
|
|
269
389
|
/**
|
|
270
390
|
* Flue observations → span intents. PURE: no `@opentelemetry/api`, no clock, no
|
|
271
391
|
* I/O. `spans.ts` is the only module that touches a `Tracer`.
|
|
@@ -371,8 +491,8 @@ interface CloseSpanIntent {
|
|
|
371
491
|
* Present when the span failed. Carries the error TYPE and the exception
|
|
372
492
|
* class NAME only — never a message and never a stack. Both are content: a
|
|
373
493
|
* provider error message routinely quotes the prompt back, and a stack
|
|
374
|
-
* exposes filesystem paths and deployment layout.
|
|
375
|
-
* content
|
|
494
|
+
* exposes filesystem paths and deployment layout. Neither is read under any
|
|
495
|
+
* setting; the `content` opt-in covers tool payloads only.
|
|
376
496
|
*/
|
|
377
497
|
error?: {
|
|
378
498
|
type: string;
|
|
@@ -395,6 +515,13 @@ interface FlueProjectionOptions {
|
|
|
395
515
|
* runs inside the delegating agent's trace, and one trace is one execution.
|
|
396
516
|
*/
|
|
397
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;
|
|
398
525
|
}
|
|
399
526
|
/**
|
|
400
527
|
* Resource attributes for the customer's `Resource`. Exported rather than set
|
|
@@ -458,6 +585,18 @@ declare const GEN_AI: {
|
|
|
458
585
|
readonly serverAddress: "server.address";
|
|
459
586
|
readonly serverPort: "server.port";
|
|
460
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
|
+
};
|
|
461
600
|
/**
|
|
462
601
|
* Runtype's extension vocabulary. Placement is part of the contract and the
|
|
463
602
|
* reader enforces it — see the `RUNTYPE_ATTRIBUTES` doc block in the shared
|
|
@@ -475,6 +614,8 @@ declare const RUNTYPE: {
|
|
|
475
614
|
readonly toolType: "runtype.tool.type";
|
|
476
615
|
readonly turnId: "runtype.turn.id";
|
|
477
616
|
readonly turnIndex: "runtype.turn.index";
|
|
617
|
+
readonly providerFinishReason: "runtype.provider.finish_reason";
|
|
618
|
+
readonly gatewayLogId: "runtype.gateway.log_id";
|
|
478
619
|
};
|
|
479
620
|
/**
|
|
480
621
|
* `runtype.tool.type` values — the closed domain that drives display and
|
|
@@ -525,12 +666,13 @@ declare const ADAPTER_VERSION: string;
|
|
|
525
666
|
* pipeline fights the one the customer already runs, and in a stack exporting
|
|
526
667
|
* to two backends it silently wins one of those fights.
|
|
527
668
|
*
|
|
528
|
-
* It also emits **no content
|
|
669
|
+
* It also emits **no content by default** — no prompts, no completions, no
|
|
529
670
|
* tool arguments or results, no error messages, no stack traces. Only
|
|
530
|
-
* identifiers, structure and metrics reach the wire
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
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.
|
|
534
676
|
*
|
|
535
677
|
* ## Composing with other instrumentations
|
|
536
678
|
*
|
|
@@ -573,4 +715,4 @@ interface RuntypeFlueInstrumentationOptions extends FlueProjectionOptions {
|
|
|
573
715
|
*/
|
|
574
716
|
declare function createRuntypeFlueInstrumentation(options?: RuntypeFlueInstrumentationOptions): FlueInstrumentation;
|
|
575
717
|
|
|
576
|
-
export { ADAPTER_NAME, ADAPTER_VERSION, type FlueInstrumentation, type FlueProjectionOptions, GEN_AI, RUNTYPE, RUNTYPE_FLUE_INSTRUMENTATION_KEY, RUNTYPE_SCHEMA_VERSION, RUNTYPE_STOP_REASONS, RUNTYPE_TOOL_TYPES, type RuntypeFlueInstrumentationOptions, type RuntypeStopReason, type RuntypeToolType, type SpanAttributes, type SpanIntent, createRuntypeFlueInstrumentation, runtypeFlueResourceAttributes };
|
|
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";
|
|
@@ -85,7 +164,9 @@ var RUNTYPE = {
|
|
|
85
164
|
toolsReported: "runtype.tools.reported",
|
|
86
165
|
toolType: "runtype.tool.type",
|
|
87
166
|
turnId: "runtype.turn.id",
|
|
88
|
-
turnIndex: "runtype.turn.index"
|
|
167
|
+
turnIndex: "runtype.turn.index",
|
|
168
|
+
providerFinishReason: "runtype.provider.finish_reason",
|
|
169
|
+
gatewayLogId: "runtype.gateway.log_id"
|
|
89
170
|
};
|
|
90
171
|
var FLUE = {
|
|
91
172
|
instanceId: "flue.instance.id",
|
|
@@ -138,6 +219,7 @@ function runtypeFlueResourceAttributes(resource) {
|
|
|
138
219
|
};
|
|
139
220
|
}
|
|
140
221
|
function createFlueProjection(options = {}) {
|
|
222
|
+
const content = resolveContentPolicy(options.content);
|
|
141
223
|
const open = /* @__PURE__ */ new Map();
|
|
142
224
|
const envelopes = /* @__PURE__ */ new Map();
|
|
143
225
|
const envelopeByChain = /* @__PURE__ */ new Map();
|
|
@@ -308,7 +390,12 @@ function createFlueProjection(options = {}) {
|
|
|
308
390
|
const ref = compactionKey(event);
|
|
309
391
|
if (open.has(ref)) return [];
|
|
310
392
|
const parentRef = resolveParentRef(event);
|
|
311
|
-
open.set(ref, {
|
|
393
|
+
open.set(ref, {
|
|
394
|
+
ref,
|
|
395
|
+
...parentRef ? { parentRef } : {},
|
|
396
|
+
chainKey: chainKey(event),
|
|
397
|
+
isTask: false
|
|
398
|
+
});
|
|
312
399
|
const reason = readString(event, "reason");
|
|
313
400
|
return [
|
|
314
401
|
{
|
|
@@ -346,7 +433,12 @@ function createFlueProjection(options = {}) {
|
|
|
346
433
|
const purpose = readString(event, "purpose") ?? "agent";
|
|
347
434
|
const compactionRef = compactionKey(event);
|
|
348
435
|
const parentRef = purpose !== "agent" && open.has(compactionRef) ? compactionRef : resolveParentRef(event);
|
|
349
|
-
open.set(ref, {
|
|
436
|
+
open.set(ref, {
|
|
437
|
+
ref,
|
|
438
|
+
...parentRef ? { parentRef } : {},
|
|
439
|
+
chainKey: chainKey(event),
|
|
440
|
+
isTask: false
|
|
441
|
+
});
|
|
350
442
|
const envelopeRef = resolveEnvelopeRef(event);
|
|
351
443
|
const envelope = envelopeRef ? envelopes.get(envelopeRef) : void 0;
|
|
352
444
|
let position;
|
|
@@ -374,7 +466,8 @@ function createFlueProjection(options = {}) {
|
|
|
374
466
|
if (position) turnPositions.set(ref, position);
|
|
375
467
|
}
|
|
376
468
|
if (envelope) {
|
|
377
|
-
if (!envelope.modelPinned && purpose === "agent")
|
|
469
|
+
if (!envelope.modelPinned && purpose === "agent")
|
|
470
|
+
envelope.requestModel = request.requestedModel;
|
|
378
471
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
379
472
|
}
|
|
380
473
|
return [
|
|
@@ -426,7 +519,17 @@ function createFlueProjection(options = {}) {
|
|
|
426
519
|
...response.responseModel ? { [GEN_AI.responseModel]: response.responseModel } : {},
|
|
427
520
|
...response.responseId ? { [GEN_AI.responseId]: response.responseId } : {},
|
|
428
521
|
...response.finishReason ? { [GEN_AI.finishReasons]: [response.finishReason] } : {},
|
|
429
|
-
...usageAttributes(response.usage)
|
|
522
|
+
...usageAttributes(response.usage),
|
|
523
|
+
// Provider-diagnostic pointers, emitted only when Flue actually provides
|
|
524
|
+
// them. Both are optional and provider-dependent (Workers AI attaches
|
|
525
|
+
// both today); an absent value costs one column, a synthesized one would
|
|
526
|
+
// render as a measurement. `gatewayLogId` is a pointer to the content
|
|
527
|
+
// without shipping the content — a customer can click through to that
|
|
528
|
+
// exact request in their own AI Gateway dashboard. `providerFinishReason`
|
|
529
|
+
// is the provider's exact finish value before normalization, which our
|
|
530
|
+
// `GEN_AI.finishReasons` above deliberately hides.
|
|
531
|
+
...response.providerFinishReason ? { [RUNTYPE.providerFinishReason]: response.providerFinishReason } : {},
|
|
532
|
+
...response.gatewayLogId ? { [RUNTYPE.gatewayLogId]: response.gatewayLogId } : {}
|
|
430
533
|
};
|
|
431
534
|
const intents = [];
|
|
432
535
|
if (Object.keys(attributes).length > 0) intents.push({ kind: "update", ref, attributes });
|
|
@@ -441,7 +544,12 @@ function createFlueProjection(options = {}) {
|
|
|
441
544
|
const ref = toolKey(event);
|
|
442
545
|
if (open.has(ref)) return [];
|
|
443
546
|
const parentRef = resolveParentRef(event);
|
|
444
|
-
open.set(ref, {
|
|
547
|
+
open.set(ref, {
|
|
548
|
+
ref,
|
|
549
|
+
...parentRef ? { parentRef } : {},
|
|
550
|
+
chainKey: chainKey(event),
|
|
551
|
+
isTask: false
|
|
552
|
+
});
|
|
445
553
|
const position = turnPositions.get(turnKey(event));
|
|
446
554
|
if (isCallerShellCall(toolName, event)) {
|
|
447
555
|
return [
|
|
@@ -465,6 +573,10 @@ function createFlueProjection(options = {}) {
|
|
|
465
573
|
}
|
|
466
574
|
const isDelegation = isTaskDelegationTool(toolName, event);
|
|
467
575
|
const toolType = resolveToolType(toolName, event);
|
|
576
|
+
const args = content.toolArguments ? encodeContentValue(readUnknown(event, "args"), content, {
|
|
577
|
+
kind: "arguments",
|
|
578
|
+
toolName
|
|
579
|
+
}) : void 0;
|
|
468
580
|
return [
|
|
469
581
|
{
|
|
470
582
|
kind: "open",
|
|
@@ -478,6 +590,7 @@ function createFlueProjection(options = {}) {
|
|
|
478
590
|
[GEN_AI.operationName]: GEN_AI_OPERATION_EXECUTE_TOOL,
|
|
479
591
|
[GEN_AI.toolName]: toolName,
|
|
480
592
|
[GEN_AI.toolCallId]: toolCallId,
|
|
593
|
+
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
481
594
|
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
482
595
|
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
483
596
|
// not a plain function call, so it is left unclaimed there while
|
|
@@ -496,9 +609,22 @@ function createFlueProjection(options = {}) {
|
|
|
496
609
|
const ref = toolKey(event);
|
|
497
610
|
if (!open.has(ref)) return [];
|
|
498
611
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
)
|
|
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)));
|
|
502
628
|
open.delete(ref);
|
|
503
629
|
return intents;
|
|
504
630
|
}
|
|
@@ -690,6 +816,13 @@ function readErrorInfo(event) {
|
|
|
690
816
|
const detail = event.errorInfo;
|
|
691
817
|
return detail !== null && typeof detail === "object" ? detail : void 0;
|
|
692
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
|
+
}
|
|
693
826
|
function readString(event, key) {
|
|
694
827
|
const value = event[key];
|
|
695
828
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -706,7 +839,12 @@ function positiveNumber(value) {
|
|
|
706
839
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
707
840
|
}
|
|
708
841
|
function chainKey(value) {
|
|
709
|
-
return identityKey("chain", [
|
|
842
|
+
return identityKey("chain", [
|
|
843
|
+
value.instanceId,
|
|
844
|
+
value.harness,
|
|
845
|
+
value.conversationId,
|
|
846
|
+
value.session
|
|
847
|
+
]);
|
|
710
848
|
}
|
|
711
849
|
function operationKey(value) {
|
|
712
850
|
return identityKey("operation", [
|
|
@@ -900,7 +1038,10 @@ function readIdField(operation, key) {
|
|
|
900
1038
|
export {
|
|
901
1039
|
ADAPTER_NAME,
|
|
902
1040
|
ADAPTER_VERSION,
|
|
1041
|
+
DEFAULT_CONTENT_MAX_CHARS,
|
|
903
1042
|
GEN_AI,
|
|
1043
|
+
GEN_AI_CONTENT,
|
|
1044
|
+
INGEST_CONTENT_ATTRIBUTE_CEILING,
|
|
904
1045
|
RUNTYPE,
|
|
905
1046
|
RUNTYPE_FLUE_INSTRUMENTATION_KEY,
|
|
906
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"
|