@runtypelabs/flue-otel 0.4.0 → 0.5.1
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 +146 -31
- package/dist/index.cjs +219 -55
- package/dist/index.d.cts +81 -299
- package/dist/index.d.ts +81 -299
- package/dist/index.mjs +219 -55
- package/package.json +5 -4
- package/CHANGELOG.md +0 -105
package/dist/index.mjs
CHANGED
|
@@ -42,49 +42,193 @@ function mapSettlementOutcome(outcome) {
|
|
|
42
42
|
return void 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// src/messages.ts
|
|
46
|
+
var ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant"]);
|
|
47
|
+
var FLUE_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
48
|
+
var SIGNAL_MESSAGE = /^<([A-Za-z_][\w.-]*) type="[^"]*"[^>]*>\n[\s\S]*\n<\/\1>$/;
|
|
49
|
+
var COMPACTION_SUMMARY_PREFIX = '<compaction type="context_summary"';
|
|
50
|
+
var HTML_TYPED_TAGS = /* @__PURE__ */ new Set([
|
|
51
|
+
"a",
|
|
52
|
+
"button",
|
|
53
|
+
"embed",
|
|
54
|
+
"input",
|
|
55
|
+
"link",
|
|
56
|
+
"menu",
|
|
57
|
+
"object",
|
|
58
|
+
"ol",
|
|
59
|
+
"script",
|
|
60
|
+
"source",
|
|
61
|
+
"style",
|
|
62
|
+
"ul"
|
|
63
|
+
]);
|
|
64
|
+
function projectFlueMessages(value) {
|
|
65
|
+
if (!Array.isArray(value)) return [];
|
|
66
|
+
const out = [];
|
|
67
|
+
for (const entry of value) {
|
|
68
|
+
const message = projectFlueMessage(entry);
|
|
69
|
+
if (message) out.push(message);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function projectFlueMessage(value) {
|
|
74
|
+
if (!isRecord(value)) return void 0;
|
|
75
|
+
const role = value.role;
|
|
76
|
+
if (typeof role !== "string" || !FLUE_ROLES.has(role)) return void 0;
|
|
77
|
+
const text = flueMessageText(value.content);
|
|
78
|
+
if (text === "") return void 0;
|
|
79
|
+
if (role === "user" && isFlueSignal(value.content, text)) {
|
|
80
|
+
return { role: "system", parts: [{ type: "text", content: text }] };
|
|
81
|
+
}
|
|
82
|
+
return { role, parts: [{ type: "text", content: text }] };
|
|
83
|
+
}
|
|
84
|
+
function isFlueSignal(content, text) {
|
|
85
|
+
if (!isSingleText(content)) return false;
|
|
86
|
+
const signal = SIGNAL_MESSAGE.exec(text);
|
|
87
|
+
return signal !== null && !HTML_TYPED_TAGS.has(signal[1].toLowerCase());
|
|
88
|
+
}
|
|
89
|
+
function isSingleText(content) {
|
|
90
|
+
if (typeof content === "string") return true;
|
|
91
|
+
return Array.isArray(content) && content.length === 1;
|
|
92
|
+
}
|
|
93
|
+
function flueMessageText(content) {
|
|
94
|
+
return contentMessageText({ content });
|
|
95
|
+
}
|
|
96
|
+
function normalizeContentMessages(value) {
|
|
97
|
+
if (!Array.isArray(value)) return [];
|
|
98
|
+
const out = [];
|
|
99
|
+
for (const entry of value) {
|
|
100
|
+
if (!isRecord(entry)) continue;
|
|
101
|
+
const role = entry.role;
|
|
102
|
+
if (typeof role !== "string" || !ROLES.has(role)) continue;
|
|
103
|
+
const text = contentMessageText(entry);
|
|
104
|
+
if (text === "") continue;
|
|
105
|
+
out.push({ role, parts: [{ type: "text", content: text }] });
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
function contentTextOf(value) {
|
|
110
|
+
if (typeof value === "string") return value;
|
|
111
|
+
return Array.isArray(value) ? contentMessageText({ parts: value }) : "";
|
|
112
|
+
}
|
|
113
|
+
function contentMessageText(message) {
|
|
114
|
+
if (typeof message.content === "string") return message.content;
|
|
115
|
+
const parts = Array.isArray(message.parts) ? message.parts : Array.isArray(message.content) ? message.content : [];
|
|
116
|
+
const texts = [];
|
|
117
|
+
for (const part of parts) {
|
|
118
|
+
if (!isRecord(part) || part.type !== "text") continue;
|
|
119
|
+
const text = typeof part.content === "string" && part.content.length > 0 ? part.content : typeof part.text === "string" && part.text.length > 0 ? part.text : "";
|
|
120
|
+
if (text !== "") texts.push(text);
|
|
121
|
+
}
|
|
122
|
+
return texts.join("\n");
|
|
123
|
+
}
|
|
124
|
+
function isRecord(value) {
|
|
125
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
126
|
+
}
|
|
127
|
+
|
|
45
128
|
// src/content.ts
|
|
46
129
|
var DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
47
130
|
var INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
48
|
-
|
|
131
|
+
var INGEST_CONTENT_SPAN_CEILING = 393216;
|
|
132
|
+
function resolveContentPolicy(setting) {
|
|
133
|
+
const options = setting === false ? void 0 : setting;
|
|
134
|
+
const on = (value) => setting !== false && value !== false;
|
|
49
135
|
const requested = options?.maxChars;
|
|
50
136
|
const maxChars = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), INGEST_CONTENT_ATTRIBUTE_CEILING) : DEFAULT_CONTENT_MAX_CHARS;
|
|
51
137
|
return {
|
|
52
|
-
toolArguments: options?.toolArguments
|
|
53
|
-
toolResults: options?.toolResults
|
|
138
|
+
toolArguments: on(options?.toolArguments),
|
|
139
|
+
toolResults: on(options?.toolResults),
|
|
140
|
+
inputMessages: on(options?.inputMessages),
|
|
141
|
+
outputMessages: on(options?.outputMessages),
|
|
142
|
+
systemInstructions: on(options?.systemInstructions),
|
|
54
143
|
maxChars,
|
|
144
|
+
envelopeMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 3)),
|
|
145
|
+
toolMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 2)),
|
|
55
146
|
redact: options?.redact
|
|
56
147
|
};
|
|
57
148
|
}
|
|
58
149
|
function encodeContentValue(value, policy, context3) {
|
|
59
150
|
if (value === void 0) return void 0;
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
|
|
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);
|
|
151
|
+
const redacted = applyRedact(value, policy, context3);
|
|
152
|
+
if (redacted === void 0) return void 0;
|
|
153
|
+
if (context3.kind === "arguments") return encodeArguments(redacted, policy.toolMaxChars);
|
|
70
154
|
const encoded = stringify(redacted);
|
|
71
155
|
if (encoded === void 0) return void 0;
|
|
72
|
-
return truncate(encoded, policy.
|
|
156
|
+
return truncate(encoded, policy.toolMaxChars);
|
|
157
|
+
}
|
|
158
|
+
function encodeMessagesValue(messages, policy, kind) {
|
|
159
|
+
if (messages.length === 0) return void 0;
|
|
160
|
+
const redacted = applyRedact(messages, policy, { kind });
|
|
161
|
+
if (redacted === void 0) return void 0;
|
|
162
|
+
const all = normalizeContentMessages(redacted);
|
|
163
|
+
if (all.length === 0) return void 0;
|
|
164
|
+
const maxChars = policy.envelopeMaxChars;
|
|
165
|
+
const perMessage = all.length > 1 ? Math.floor((maxChars - 3) / 2) : maxChars - 2;
|
|
166
|
+
const encoded = [];
|
|
167
|
+
for (const message of all) {
|
|
168
|
+
const one = JSON.stringify(message);
|
|
169
|
+
encoded.push(one.length <= perMessage ? one : encodeTruncatedMessage(message, perMessage) ?? "");
|
|
170
|
+
}
|
|
171
|
+
let start = all.length;
|
|
172
|
+
let total = 2;
|
|
173
|
+
while (start > 0) {
|
|
174
|
+
const length = encoded[start - 1].length + (start < all.length ? 1 : 0);
|
|
175
|
+
if (total + length > maxChars) break;
|
|
176
|
+
total += length;
|
|
177
|
+
start -= 1;
|
|
178
|
+
}
|
|
179
|
+
const kept = encoded.slice(start).filter((one) => one !== "");
|
|
180
|
+
if (kept.length === 0) return void 0;
|
|
181
|
+
return "[" + kept.join(",") + "]";
|
|
182
|
+
}
|
|
183
|
+
function encodeSystemInstructionsValue(value, policy) {
|
|
184
|
+
if (value.length === 0) return void 0;
|
|
185
|
+
const redacted = applyRedact(value, policy, { kind: "system_instructions" });
|
|
186
|
+
if (redacted === void 0) return void 0;
|
|
187
|
+
const text = contentTextOf(redacted);
|
|
188
|
+
if (text.length === 0) return void 0;
|
|
189
|
+
return encodeTextParts(text, policy.envelopeMaxChars);
|
|
190
|
+
}
|
|
191
|
+
function encodeTruncatedMessage(message, maxChars) {
|
|
192
|
+
const text = message.parts.map((part) => part.content).join("\n");
|
|
193
|
+
return fitJson(
|
|
194
|
+
(cut) => JSON.stringify({ role: message.role, parts: [{ type: "text", content: cut }] }),
|
|
195
|
+
text,
|
|
196
|
+
maxChars
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function encodeTextParts(text, maxChars) {
|
|
200
|
+
return fitJson((cut) => JSON.stringify([{ type: "text", content: cut }]), text, maxChars);
|
|
201
|
+
}
|
|
202
|
+
function fitJson(wrap, text, maxChars) {
|
|
203
|
+
let encoded = wrap(text);
|
|
204
|
+
if (encoded.length <= maxChars) return encoded;
|
|
205
|
+
const shell = wrap("").length;
|
|
206
|
+
let budget = maxChars - shell;
|
|
207
|
+
for (let round = 0; round < 8; round += 1) {
|
|
208
|
+
if (budget <= truncationMarker(text.length).length) return void 0;
|
|
209
|
+
const cut = truncate(text, budget);
|
|
210
|
+
encoded = wrap(cut);
|
|
211
|
+
if (encoded.length <= maxChars) return encoded;
|
|
212
|
+
const rescaled = Math.floor(budget * (maxChars - shell) / (encoded.length - shell));
|
|
213
|
+
budget = Math.min(budget - 1, rescaled);
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
function applyRedact(value, policy, context3) {
|
|
218
|
+
if (!policy.redact) return value;
|
|
219
|
+
try {
|
|
220
|
+
return policy.redact(value, context3);
|
|
221
|
+
} catch {
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
73
224
|
}
|
|
74
225
|
function encodeArguments(value, maxChars) {
|
|
75
226
|
const shaped = isPlainObject(value) ? value : { value };
|
|
76
227
|
const encoded = stringify(shaped);
|
|
77
228
|
if (encoded === void 0) return void 0;
|
|
78
229
|
if (encoded.length <= maxChars) return encoded;
|
|
79
|
-
|
|
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)) });
|
|
230
|
+
return fitJson((cut) => JSON.stringify({ truncated: cut }), encoded, maxChars);
|
|
86
231
|
}
|
|
87
|
-
var TRUNCATED_WRAPPER_OVERHEAD = 16;
|
|
88
232
|
function isPlainObject(value) {
|
|
89
233
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
90
234
|
}
|
|
@@ -118,7 +262,7 @@ function isHighSurrogate(code) {
|
|
|
118
262
|
}
|
|
119
263
|
|
|
120
264
|
// package.json
|
|
121
|
-
var version = "0.
|
|
265
|
+
var version = "0.5.1";
|
|
122
266
|
|
|
123
267
|
// src/semconv.ts
|
|
124
268
|
var GEN_AI = {
|
|
@@ -147,6 +291,9 @@ var GEN_AI = {
|
|
|
147
291
|
serverPort: "server.port"
|
|
148
292
|
};
|
|
149
293
|
var GEN_AI_CONTENT = {
|
|
294
|
+
inputMessages: "gen_ai.input.messages",
|
|
295
|
+
outputMessages: "gen_ai.output.messages",
|
|
296
|
+
systemInstructions: "gen_ai.system_instructions",
|
|
150
297
|
toolCallArguments: "gen_ai.tool.call.arguments",
|
|
151
298
|
toolCallResult: "gen_ai.tool.call.result"
|
|
152
299
|
};
|
|
@@ -300,9 +447,6 @@ function createFlueProjection(options = {}) {
|
|
|
300
447
|
...isEnvelope ? { [GEN_AI.operationName]: GEN_AI_OPERATION_INVOKE_AGENT } : {},
|
|
301
448
|
...isEnvelope && event.agentName ? { [GEN_AI.agentName]: event.agentName } : {},
|
|
302
449
|
...isEnvelope && event.conversationId ? { [GEN_AI.conversationId]: event.conversationId } : {},
|
|
303
|
-
// The execution id LIFTS the tier and is claim-checked at ingest like
|
|
304
|
-
// any inbound id; it does not key the row, which stays derived from
|
|
305
|
-
// the trace id.
|
|
306
450
|
...isEnvelope && event.submissionId ? { [RUNTYPE.executionId]: event.submissionId } : {},
|
|
307
451
|
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {},
|
|
308
452
|
// WHY(README.md): the resource placement is the customer's to wire and is routinely skipped.
|
|
@@ -366,9 +510,7 @@ function createFlueProjection(options = {}) {
|
|
|
366
510
|
{
|
|
367
511
|
kind: "open",
|
|
368
512
|
ref,
|
|
369
|
-
//
|
|
370
|
-
// agent-invocation span in the trace is a coin flip over which one
|
|
371
|
-
// becomes the run's envelope.
|
|
513
|
+
// WHY(packages/flue-otel/src/projection.ts): Use a task span, not invoke_agent; one trace is persisted as one execution.
|
|
372
514
|
name: agent ? `flue.task ${agent}` : "flue.task",
|
|
373
515
|
spanKind: "internal",
|
|
374
516
|
...parentRef ? { parentRef } : {},
|
|
@@ -408,8 +550,6 @@ function createFlueProjection(options = {}) {
|
|
|
408
550
|
ref,
|
|
409
551
|
name: "flue.compaction",
|
|
410
552
|
spanKind: "internal",
|
|
411
|
-
// `session.compact()` is callable by the host outside any turn, and a
|
|
412
|
-
// `compact` operation is not intercepted, so this can be parentless.
|
|
413
553
|
requiresParent: true,
|
|
414
554
|
...parentRef ? { parentRef } : {},
|
|
415
555
|
...event.timestamp ? { startTime: event.timestamp } : {},
|
|
@@ -423,6 +563,11 @@ function createFlueProjection(options = {}) {
|
|
|
423
563
|
function onCompactionEnd(event) {
|
|
424
564
|
const ref = compactionKey(event);
|
|
425
565
|
if (!open.has(ref)) return [];
|
|
566
|
+
if (!event.taskId) {
|
|
567
|
+
const envelope = resolveEnvelopeRef(event);
|
|
568
|
+
const state = envelope ? envelopes.get(envelope) : void 0;
|
|
569
|
+
if (state) state.compacted = true;
|
|
570
|
+
}
|
|
426
571
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
427
572
|
intents.push(
|
|
428
573
|
closeIntent(ref, event, readBoolean(event, "isError") === true, readErrorInfo(event))
|
|
@@ -474,6 +619,7 @@ function createFlueProjection(options = {}) {
|
|
|
474
619
|
if (!envelope.modelPinned && purpose === "agent")
|
|
475
620
|
envelope.requestModel = request.requestedModel;
|
|
476
621
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
622
|
+
if (position?.atEnvelopeScope) captureInput(envelope, request);
|
|
477
623
|
}
|
|
478
624
|
return [
|
|
479
625
|
{
|
|
@@ -519,20 +665,16 @@ function createFlueProjection(options = {}) {
|
|
|
519
665
|
if (purpose === "agent" && response.finishReason) {
|
|
520
666
|
envelope.lastFinishReason = response.finishReason;
|
|
521
667
|
}
|
|
668
|
+
if (content.outputMessages && purpose === "agent" && !event.taskId && !isError) {
|
|
669
|
+
const output = projectFlueMessage(response.output);
|
|
670
|
+
if (output?.role === "assistant") envelope.outputMessage = output;
|
|
671
|
+
}
|
|
522
672
|
}
|
|
523
673
|
const attributes = {
|
|
524
674
|
...response.responseModel ? { [GEN_AI.responseModel]: response.responseModel } : {},
|
|
525
675
|
...response.responseId ? { [GEN_AI.responseId]: response.responseId } : {},
|
|
526
676
|
...response.finishReason ? { [GEN_AI.finishReasons]: [response.finishReason] } : {},
|
|
527
677
|
...usageAttributes(response.usage),
|
|
528
|
-
// Provider-diagnostic pointers, emitted only when Flue actually provides
|
|
529
|
-
// them. Both are optional and provider-dependent (Workers AI attaches
|
|
530
|
-
// both today); an absent value costs one column, a synthesized one would
|
|
531
|
-
// render as a measurement. `gatewayLogId` is a pointer to the content
|
|
532
|
-
// without shipping the content — a customer can click through to that
|
|
533
|
-
// exact request in their own AI Gateway dashboard. `providerFinishReason`
|
|
534
|
-
// is the provider's exact finish value before normalization, which our
|
|
535
|
-
// `GEN_AI.finishReasons` above deliberately hides.
|
|
536
678
|
...response.providerFinishReason ? { [RUNTYPE.providerFinishReason]: response.providerFinishReason } : {},
|
|
537
679
|
...response.gatewayLogId ? { [RUNTYPE.gatewayLogId]: response.gatewayLogId } : {}
|
|
538
680
|
};
|
|
@@ -563,8 +705,6 @@ function createFlueProjection(options = {}) {
|
|
|
563
705
|
ref,
|
|
564
706
|
name: "flue.operation shell",
|
|
565
707
|
spanKind: "internal",
|
|
566
|
-
// `session.shell()` is callable by the host outside any turn, and a
|
|
567
|
-
// `shell` operation is not intercepted, so this can be parentless.
|
|
568
708
|
requiresParent: true,
|
|
569
709
|
...parentRef ? { parentRef } : {},
|
|
570
710
|
...event.timestamp ? { startTime: event.timestamp } : {},
|
|
@@ -596,10 +736,6 @@ function createFlueProjection(options = {}) {
|
|
|
596
736
|
[GEN_AI.toolName]: toolName,
|
|
597
737
|
[GEN_AI.toolCallId]: toolCallId,
|
|
598
738
|
...args !== void 0 ? { [GEN_AI_CONTENT.toolCallArguments]: args } : {},
|
|
599
|
-
// The semconv `gen_ai.tool.type` domain is the transport-level one
|
|
600
|
-
// (`function` / `extension` / `datastore`); a sub-agent delegation is
|
|
601
|
-
// not a plain function call, so it is left unclaimed there while
|
|
602
|
-
// `runtype.tool.type` says what it actually is.
|
|
603
739
|
...isDelegation ? {} : { [GEN_AI.toolType]: "function" },
|
|
604
740
|
...toolType ? { [RUNTYPE.toolType]: toolType } : {},
|
|
605
741
|
...event.origin ? { [FLUE.toolOrigin]: event.origin } : {},
|
|
@@ -658,10 +794,43 @@ function createFlueProjection(options = {}) {
|
|
|
658
794
|
} : {},
|
|
659
795
|
...stopReason ? { [RUNTYPE.stopReason]: stopReason } : {},
|
|
660
796
|
...envelope.toolsReported ? { [RUNTYPE.toolsReported]: true } : {},
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
797
|
+
...envelope.maxIteration !== null ? { [RUNTYPE.iteration]: envelope.maxIteration } : {},
|
|
798
|
+
...envelopeContentAttributes(envelope)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function captureInput(envelope, request) {
|
|
802
|
+
const input = request.input;
|
|
803
|
+
if (envelope.inputRaw && (envelope.compacted || isCompactedHistory(input?.messages))) return;
|
|
804
|
+
if (content.inputMessages && Array.isArray(input?.messages) && input.messages.length > 0) {
|
|
805
|
+
envelope.inputRaw = input.messages;
|
|
806
|
+
envelope.inputBaseCount ??= input.messages.length;
|
|
807
|
+
}
|
|
808
|
+
if (content.systemInstructions && typeof input?.systemPrompt === "string") {
|
|
809
|
+
envelope.systemInstructions = input.systemPrompt;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function isCompactedHistory(messages) {
|
|
813
|
+
if (!Array.isArray(messages) || messages.length === 0) return false;
|
|
814
|
+
const first = messages[0];
|
|
815
|
+
if (!first || first.role !== "user") return false;
|
|
816
|
+
return flueMessageText(first.content).startsWith(COMPACTION_SUMMARY_PREFIX);
|
|
817
|
+
}
|
|
818
|
+
function projectInput(envelope) {
|
|
819
|
+
const raw = envelope.inputRaw ?? [];
|
|
820
|
+
const base = envelope.inputBaseCount ?? raw.length;
|
|
821
|
+
return [
|
|
822
|
+
...projectFlueMessages(raw.slice(0, base)),
|
|
823
|
+
...projectFlueMessages(raw.slice(base)).filter((message) => message.role !== "assistant")
|
|
824
|
+
];
|
|
825
|
+
}
|
|
826
|
+
function envelopeContentAttributes(envelope) {
|
|
827
|
+
const input = envelope.inputRaw ? encodeMessagesValue(projectInput(envelope), content, "input_messages") : void 0;
|
|
828
|
+
const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
|
|
829
|
+
const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
|
|
830
|
+
return {
|
|
831
|
+
...input !== void 0 ? { [GEN_AI_CONTENT.inputMessages]: input } : {},
|
|
832
|
+
...system !== void 0 ? { [GEN_AI_CONTENT.systemInstructions]: system } : {},
|
|
833
|
+
...output !== void 0 ? { [GEN_AI_CONTENT.outputMessages]: output } : {}
|
|
665
834
|
};
|
|
666
835
|
}
|
|
667
836
|
function resolveEnvelopeRef(event) {
|
|
@@ -781,9 +950,7 @@ function positionAttributes(position) {
|
|
|
781
950
|
return {
|
|
782
951
|
...position.turnId ? { [RUNTYPE.turnId]: position.turnId } : {},
|
|
783
952
|
[RUNTYPE.turnIndex]: position.index,
|
|
784
|
-
//
|
|
785
|
-
// turns would otherwise inflate the run's iteration count, which ingest
|
|
786
|
-
// computes as the highest position across every span in the trace.
|
|
953
|
+
// WHY(packages/flue-otel/src/projection.ts): Do not stamp delegated turns with root iteration positions; they would inflate the run iteration count.
|
|
787
954
|
...position.atEnvelopeScope ? { [RUNTYPE.iteration]: position.index } : {}
|
|
788
955
|
};
|
|
789
956
|
}
|
|
@@ -809,9 +976,6 @@ function closeIntent(ref, event, isError, errorInfo) {
|
|
|
809
976
|
...isError ? {
|
|
810
977
|
error: {
|
|
811
978
|
type: errorInfo?.type ?? "_OTHER",
|
|
812
|
-
// The class NAME only. `errorInfo.message` and `.stack` are content
|
|
813
|
-
// and this release emits none: a provider message routinely quotes
|
|
814
|
-
// the prompt back, and a stack ships filesystem layout.
|
|
815
979
|
...errorInfo?.name ? { exceptionType: errorInfo.name } : {}
|
|
816
980
|
}
|
|
817
981
|
} : {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/flue-otel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
"@opentelemetry/api": "^1.9.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
+
"@flue/runtime": "^2.0.3",
|
|
31
32
|
"@opentelemetry/api": "^1.9.0",
|
|
32
33
|
"@opentelemetry/context-async-hooks": "^2.1.0",
|
|
33
34
|
"@opentelemetry/sdk-trace-base": "^2.1.0",
|
|
34
|
-
"@runtypelabs/execution-ingest": "0.6.
|
|
35
|
-
"@runtypelabs/shared": "3.
|
|
35
|
+
"@runtypelabs/execution-ingest": "0.6.25",
|
|
36
|
+
"@runtypelabs/shared": "3.41.0",
|
|
36
37
|
"tsup": "^8.0.2",
|
|
37
38
|
"typescript": "^6.0.3",
|
|
38
39
|
"vitest": "^4.1.0"
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
"build": "tsup",
|
|
69
70
|
"dev": "tsup --watch",
|
|
70
71
|
"clean": "rm -rf dist",
|
|
71
|
-
"typecheck": "tsgo --noEmit",
|
|
72
|
+
"typecheck": "tsgo --noEmit && tsgo --noEmit -p tsconfig.tests.json",
|
|
72
73
|
"test": "vitest run",
|
|
73
74
|
"test:watch": "vitest watch",
|
|
74
75
|
"lint": "eslint . --max-warnings 0"
|
package/CHANGELOG.md
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
# @runtypelabs/flue-otel
|
|
2
|
-
|
|
3
|
-
## 0.4.0
|
|
4
|
-
|
|
5
|
-
### Minor Changes
|
|
6
|
-
|
|
7
|
-
- 21f5bbb: Make the producing instrumentation's version detectable server-side. `@runtypelabs/flue-otel` now stamps `runtype.adapter.name` / `runtype.adapter.version` on each run's `invoke_agent` span, so a trace identifies its producer without the customer wiring `runtypeFlueResourceAttributes` into their SDK `Resource`. OTLP trace ingest resolves the pair (resource level first, envelope span second) and records it on the run's log row as `otlpAdapterName` / `otlpAdapterVersion`, regardless of logging policy.
|
|
8
|
-
|
|
9
|
-
## 0.3.0
|
|
10
|
-
|
|
11
|
-
### Minor Changes
|
|
12
|
-
|
|
13
|
-
- 01df868: Add an opt-in `content` option that emits tool arguments (`gen_ai.tool.call.arguments`, at span open) and tool results (`gen_ai.tool.call.result`, before span end) on `execute_tool` spans, so a Flue run's tool cards in Runtype show their input and output. Off by default; per-kind switches, a per-value `maxChars` ceiling with an explicit truncation marker (clamped to the ingest per-attribute ceiling), and a `redact` hook. A failed tool's result, the host's shell call, and prompts/completions are still never sent.
|
|
14
|
-
|
|
15
|
-
## 0.2.2
|
|
16
|
-
|
|
17
|
-
### Patch Changes
|
|
18
|
-
|
|
19
|
-
- 5f00348: Carry Workers AI `gatewayLogId` and `providerFinishReason` onto the chat span
|
|
20
|
-
|
|
21
|
-
Flue's `turn` event carries two allowlisted raw-provider fields that
|
|
22
|
-
`@runtypelabs/flue-otel` previously dropped. Both are now emitted on the `chat`
|
|
23
|
-
span as `runtype.*` extension attributes, gated on presence so an absent value
|
|
24
|
-
costs nothing:
|
|
25
|
-
- `runtype.gateway.log_id` — the response's own gateway log id (e.g.
|
|
26
|
-
Cloudflare AI Gateway's `cf-aig-log-id`), a pointer to the request in the
|
|
27
|
-
customer's own AI Gateway dashboard without shipping the content.
|
|
28
|
-
- `runtype.provider.finish_reason` — the provider's exact finish value before
|
|
29
|
-
normalization (e.g. Workers AI's `tool_calls` behind the normalized
|
|
30
|
-
`toolUse`), for diagnosis when a run stops unexpectedly.
|
|
31
|
-
|
|
32
|
-
Both attribute names are added to the shared `RUNTYPE_ATTRIBUTES` vocabulary
|
|
33
|
-
(`packages/shared/src/otlp-runtype-semconv.ts`) so the emit side and the ingest
|
|
34
|
-
side stay in step. They are additive diagnostics and do not count toward the
|
|
35
|
-
achieved fidelity tier.
|
|
36
|
-
|
|
37
|
-
## 0.2.1
|
|
38
|
-
|
|
39
|
-
### Patch Changes
|
|
40
|
-
|
|
41
|
-
- 625c455: Rewrite the README from first principles. It now leads with install, quick
|
|
42
|
-
start and the no-SDK recipe, then configuration, attribution, shutdown, and
|
|
43
|
-
what the package emits, instead of narrating how the package differs from
|
|
44
|
-
earlier instrumentations.
|
|
45
|
-
|
|
46
|
-
## 0.2.0
|
|
47
|
-
|
|
48
|
-
### Minor Changes
|
|
49
|
-
|
|
50
|
-
- 2439b84: New package: `@runtypelabs/flue-otel`, an OpenTelemetry instrumentation for Flue
|
|
51
|
-
agents that reports runs to Runtype at `t2-runtype` fidelity.
|
|
52
|
-
|
|
53
|
-
Stock `@flue/opentelemetry` already exports GenAI semconv spans, and the ingest
|
|
54
|
-
normalizer shipped in #7336 lifts those to t2 by reading Flue's own `flue.*`
|
|
55
|
-
identifiers. This package is the client-side complement, for the facts a
|
|
56
|
-
server-side normalizer structurally cannot reach:
|
|
57
|
-
- **Absolute loop position.** `runtype.turn.index` / `runtype.iteration` are
|
|
58
|
-
deliberately NOT derived at ingest, because Flue puts no absolute ordinal on
|
|
59
|
-
the wire and ranking opaque turn ids ranks whatever the export batch happened
|
|
60
|
-
to carry — a five-turn run whose closing batch holds two turns would be
|
|
61
|
-
recorded as two iterations. In process the count is exact.
|
|
62
|
-
- **A run-level usage roll-up on the envelope.** Summed from `turn` leaves, so a
|
|
63
|
-
split export cannot lose the total: the batch carrying the terminal carries
|
|
64
|
-
the complete sum by construction.
|
|
65
|
-
- **Per-agent attribution for N agents in one process**, via the span-level
|
|
66
|
-
`runtype.agent.id` placement, keyed by Flue agent name.
|
|
67
|
-
- **One `invoke_agent` span per trace.** Stock opens a nested one for every
|
|
68
|
-
`task` delegation, which is a coin flip over which invocation bounds the run
|
|
69
|
-
(ingest's envelope selection is first-match over exporter-controlled array
|
|
70
|
-
order). Here a delegation is reported as an `execute_tool` span typed
|
|
71
|
-
`subagent`, and the sub-agent's work gets a non-agent `flue.task` span.
|
|
72
|
-
|
|
73
|
-
Works on both Flue lines (`>=1.0.0-beta.9` and 2.x) from one entry point, via a
|
|
74
|
-
capability probe rather than version comparison. Depends on `@opentelemetry/api`
|
|
75
|
-
only — no SDK, no provider, no exporter, no flush; the application owns those.
|
|
76
|
-
|
|
77
|
-
Emits no content in this release: no prompts, completions, tool arguments or
|
|
78
|
-
results, error messages, or stack traces.
|
|
79
|
-
|
|
80
|
-
`@runtypelabs/shared` exports the wire stop-reason tuple as `WIRE_STOP_REASONS`
|
|
81
|
-
(the zod enum is now derived from it, so the two cannot disagree). The ingest
|
|
82
|
-
reader writes `runtype.stop_reason` to `agent_executions.stop_reason` verbatim
|
|
83
|
-
with no enum validation, so an external producer has to spell those six strings
|
|
84
|
-
itself; exporting the tuple is what lets the producer's contract test pin its
|
|
85
|
-
copy and turn a rename into a red build instead of a silently wrong column.
|
|
86
|
-
|
|
87
|
-
### Patch Changes
|
|
88
|
-
|
|
89
|
-
- 3b4f69c: License `@runtypelabs/flue-otel` under MIT rather than Apache-2.0, and ship the
|
|
90
|
-
license text in the published tarball.
|
|
91
|
-
|
|
92
|
-
The package declared `Apache-2.0` in `package.json` and in its README while
|
|
93
|
-
carrying no license file at all, so the published artifact asserted a grant
|
|
94
|
-
whose terms it never included. `LICENSE` is now added to `files`, matching how
|
|
95
|
-
every other published package here (`cli`, `python-sdk`, `ruby-sdk`,
|
|
96
|
-
`java-sdk`, `php-sdk`) ships its license.
|
|
97
|
-
|
|
98
|
-
- 3b4f69c: Normalize package authorship and copyright metadata.
|
|
99
|
-
|
|
100
|
-
The `author` field reads `Runtype` rather than `Runtype Labs` across every
|
|
101
|
-
workspace package that set it, and the copyright holder in each license file is
|
|
102
|
-
now `Runtype, Inc` — replacing `Travrse Labs, Inc` in the CLI and the four Fern
|
|
103
|
-
SDKs, and the `Runtype Labs` that the new `flue-otel` license was written with.
|
|
104
|
-
|
|
105
|
-
Metadata only: no source, dependency, or behavior change.
|