@runtypelabs/flue-otel 0.4.0 → 0.5.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 +133 -31
- package/dist/index.cjs +166 -45
- package/dist/index.d.cts +81 -299
- package/dist/index.d.ts +81 -299
- package/dist/index.mjs +166 -45
- package/package.json +5 -4
- package/CHANGELOG.md +0 -105
package/dist/index.mjs
CHANGED
|
@@ -42,35 +42,156 @@ 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 = /^\s*<([A-Za-z][\w-]*)\b[^>]*>[\s\S]*<\/\1>\s*$/;
|
|
49
|
+
function projectFlueMessages(value) {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
const out = [];
|
|
52
|
+
for (const entry of value) {
|
|
53
|
+
const message = projectFlueMessage(entry);
|
|
54
|
+
if (message) out.push(message);
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
function projectFlueMessage(value) {
|
|
59
|
+
if (!isRecord(value)) return void 0;
|
|
60
|
+
const role = value.role;
|
|
61
|
+
if (typeof role !== "string" || !FLUE_ROLES.has(role)) return void 0;
|
|
62
|
+
const text = flueMessageText(value.content);
|
|
63
|
+
if (text === "") return void 0;
|
|
64
|
+
if (role === "user" && SIGNAL_MESSAGE.test(text)) return void 0;
|
|
65
|
+
return { role, parts: [{ type: "text", content: text }] };
|
|
66
|
+
}
|
|
67
|
+
function flueMessageText(content) {
|
|
68
|
+
if (typeof content === "string") return content;
|
|
69
|
+
if (!Array.isArray(content)) return "";
|
|
70
|
+
const texts = [];
|
|
71
|
+
for (const part of content) {
|
|
72
|
+
if (!isRecord(part) || part.type !== "text") continue;
|
|
73
|
+
if (typeof part.text === "string" && part.text.length > 0) texts.push(part.text);
|
|
74
|
+
}
|
|
75
|
+
return texts.join("\n");
|
|
76
|
+
}
|
|
77
|
+
function normalizeContentMessages(value) {
|
|
78
|
+
if (!Array.isArray(value)) return [];
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const entry of value) {
|
|
81
|
+
if (!isRecord(entry)) continue;
|
|
82
|
+
const role = entry.role;
|
|
83
|
+
if (typeof role !== "string" || !ROLES.has(role)) continue;
|
|
84
|
+
const text = contentMessageText(entry);
|
|
85
|
+
if (text === "") continue;
|
|
86
|
+
out.push({ role, parts: [{ type: "text", content: text }] });
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function contentTextOf(value) {
|
|
91
|
+
if (typeof value === "string") return value;
|
|
92
|
+
return Array.isArray(value) ? contentMessageText({ parts: value }) : "";
|
|
93
|
+
}
|
|
94
|
+
function contentMessageText(message) {
|
|
95
|
+
if (typeof message.content === "string") return message.content;
|
|
96
|
+
const parts = Array.isArray(message.parts) ? message.parts : Array.isArray(message.content) ? message.content : [];
|
|
97
|
+
const texts = [];
|
|
98
|
+
for (const part of parts) {
|
|
99
|
+
if (!isRecord(part) || part.type !== "text") continue;
|
|
100
|
+
const text = typeof part.content === "string" && part.content.length > 0 ? part.content : typeof part.text === "string" && part.text.length > 0 ? part.text : "";
|
|
101
|
+
if (text !== "") texts.push(text);
|
|
102
|
+
}
|
|
103
|
+
return texts.join("\n");
|
|
104
|
+
}
|
|
105
|
+
function isRecord(value) {
|
|
106
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
107
|
+
}
|
|
108
|
+
|
|
45
109
|
// src/content.ts
|
|
46
110
|
var DEFAULT_CONTENT_MAX_CHARS = 65536;
|
|
47
111
|
var INGEST_CONTENT_ATTRIBUTE_CEILING = 262144;
|
|
48
|
-
|
|
112
|
+
var INGEST_CONTENT_SPAN_CEILING = 393216;
|
|
113
|
+
function resolveContentPolicy(setting) {
|
|
114
|
+
const options = setting === false ? void 0 : setting;
|
|
115
|
+
const on = (value) => setting !== false && value !== false;
|
|
49
116
|
const requested = options?.maxChars;
|
|
50
117
|
const maxChars = typeof requested === "number" && Number.isFinite(requested) && requested > 0 ? Math.min(Math.floor(requested), INGEST_CONTENT_ATTRIBUTE_CEILING) : DEFAULT_CONTENT_MAX_CHARS;
|
|
51
118
|
return {
|
|
52
|
-
toolArguments: options?.toolArguments
|
|
53
|
-
toolResults: options?.toolResults
|
|
119
|
+
toolArguments: on(options?.toolArguments),
|
|
120
|
+
toolResults: on(options?.toolResults),
|
|
121
|
+
inputMessages: on(options?.inputMessages),
|
|
122
|
+
outputMessages: on(options?.outputMessages),
|
|
123
|
+
systemInstructions: on(options?.systemInstructions),
|
|
54
124
|
maxChars,
|
|
125
|
+
envelopeMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 3)),
|
|
55
126
|
redact: options?.redact
|
|
56
127
|
};
|
|
57
128
|
}
|
|
58
129
|
function encodeContentValue(value, policy, context3) {
|
|
59
130
|
if (value === void 0) return void 0;
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
try {
|
|
63
|
-
redacted = policy.redact(value, context3);
|
|
64
|
-
} catch {
|
|
65
|
-
return void 0;
|
|
66
|
-
}
|
|
67
|
-
if (redacted === void 0) return void 0;
|
|
68
|
-
}
|
|
131
|
+
const redacted = applyRedact(value, policy, context3);
|
|
132
|
+
if (redacted === void 0) return void 0;
|
|
69
133
|
if (context3.kind === "arguments") return encodeArguments(redacted, policy.maxChars);
|
|
70
134
|
const encoded = stringify(redacted);
|
|
71
135
|
if (encoded === void 0) return void 0;
|
|
72
136
|
return truncate(encoded, policy.maxChars);
|
|
73
137
|
}
|
|
138
|
+
function encodeMessagesValue(messages, policy, kind) {
|
|
139
|
+
if (messages.length === 0) return void 0;
|
|
140
|
+
const redacted = applyRedact(messages, policy, { kind });
|
|
141
|
+
if (redacted === void 0) return void 0;
|
|
142
|
+
const all = normalizeContentMessages(redacted);
|
|
143
|
+
if (all.length === 0) return void 0;
|
|
144
|
+
const maxChars = policy.envelopeMaxChars;
|
|
145
|
+
let start = all.length;
|
|
146
|
+
let total = 2;
|
|
147
|
+
while (start > 0) {
|
|
148
|
+
const length = JSON.stringify(all[start - 1]).length + (start < all.length ? 1 : 0);
|
|
149
|
+
if (total + length > maxChars) break;
|
|
150
|
+
total += length;
|
|
151
|
+
start -= 1;
|
|
152
|
+
}
|
|
153
|
+
if (start === 0) return JSON.stringify(all);
|
|
154
|
+
if (start < all.length) return JSON.stringify(all.slice(start));
|
|
155
|
+
return encodeTruncatedMessage(all[all.length - 1], maxChars);
|
|
156
|
+
}
|
|
157
|
+
function encodeSystemInstructionsValue(value, policy) {
|
|
158
|
+
if (value.length === 0) return void 0;
|
|
159
|
+
const redacted = applyRedact(value, policy, { kind: "system_instructions" });
|
|
160
|
+
if (redacted === void 0) return void 0;
|
|
161
|
+
const text = contentTextOf(redacted);
|
|
162
|
+
if (text.length === 0) return void 0;
|
|
163
|
+
return encodeTextParts(text, policy.envelopeMaxChars);
|
|
164
|
+
}
|
|
165
|
+
function encodeTruncatedMessage(message, maxChars) {
|
|
166
|
+
const text = message.parts.map((part) => part.content).join("\n");
|
|
167
|
+
return fitJson(
|
|
168
|
+
(cut) => JSON.stringify([{ role: message.role, parts: [{ type: "text", content: cut }] }]),
|
|
169
|
+
text,
|
|
170
|
+
maxChars
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function encodeTextParts(text, maxChars) {
|
|
174
|
+
return fitJson((cut) => JSON.stringify([{ type: "text", content: cut }]), text, maxChars);
|
|
175
|
+
}
|
|
176
|
+
function fitJson(wrap, text, maxChars) {
|
|
177
|
+
let encoded = wrap(text);
|
|
178
|
+
if (encoded.length <= maxChars) return encoded;
|
|
179
|
+
let budget = maxChars - wrap("").length;
|
|
180
|
+
for (let round = 0; round < 8 && budget > 0; round += 1) {
|
|
181
|
+
encoded = wrap(truncate(text, budget));
|
|
182
|
+
if (encoded.length <= maxChars) return encoded;
|
|
183
|
+
budget -= encoded.length - maxChars;
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
function applyRedact(value, policy, context3) {
|
|
188
|
+
if (!policy.redact) return value;
|
|
189
|
+
try {
|
|
190
|
+
return policy.redact(value, context3);
|
|
191
|
+
} catch {
|
|
192
|
+
return void 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
74
195
|
function encodeArguments(value, maxChars) {
|
|
75
196
|
const shaped = isPlainObject(value) ? value : { value };
|
|
76
197
|
const encoded = stringify(shaped);
|
|
@@ -118,7 +239,7 @@ function isHighSurrogate(code) {
|
|
|
118
239
|
}
|
|
119
240
|
|
|
120
241
|
// package.json
|
|
121
|
-
var version = "0.
|
|
242
|
+
var version = "0.5.0";
|
|
122
243
|
|
|
123
244
|
// src/semconv.ts
|
|
124
245
|
var GEN_AI = {
|
|
@@ -147,6 +268,9 @@ var GEN_AI = {
|
|
|
147
268
|
serverPort: "server.port"
|
|
148
269
|
};
|
|
149
270
|
var GEN_AI_CONTENT = {
|
|
271
|
+
inputMessages: "gen_ai.input.messages",
|
|
272
|
+
outputMessages: "gen_ai.output.messages",
|
|
273
|
+
systemInstructions: "gen_ai.system_instructions",
|
|
150
274
|
toolCallArguments: "gen_ai.tool.call.arguments",
|
|
151
275
|
toolCallResult: "gen_ai.tool.call.result"
|
|
152
276
|
};
|
|
@@ -300,9 +424,6 @@ function createFlueProjection(options = {}) {
|
|
|
300
424
|
...isEnvelope ? { [GEN_AI.operationName]: GEN_AI_OPERATION_INVOKE_AGENT } : {},
|
|
301
425
|
...isEnvelope && event.agentName ? { [GEN_AI.agentName]: event.agentName } : {},
|
|
302
426
|
...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
427
|
...isEnvelope && event.submissionId ? { [RUNTYPE.executionId]: event.submissionId } : {},
|
|
307
428
|
...isEnvelope && agentId ? { [RUNTYPE.agentId]: agentId } : {},
|
|
308
429
|
// WHY(README.md): the resource placement is the customer's to wire and is routinely skipped.
|
|
@@ -366,9 +487,7 @@ function createFlueProjection(options = {}) {
|
|
|
366
487
|
{
|
|
367
488
|
kind: "open",
|
|
368
489
|
ref,
|
|
369
|
-
//
|
|
370
|
-
// agent-invocation span in the trace is a coin flip over which one
|
|
371
|
-
// becomes the run's envelope.
|
|
490
|
+
// WHY(packages/flue-otel/src/projection.ts): Use a task span, not invoke_agent; one trace is persisted as one execution.
|
|
372
491
|
name: agent ? `flue.task ${agent}` : "flue.task",
|
|
373
492
|
spanKind: "internal",
|
|
374
493
|
...parentRef ? { parentRef } : {},
|
|
@@ -408,8 +527,6 @@ function createFlueProjection(options = {}) {
|
|
|
408
527
|
ref,
|
|
409
528
|
name: "flue.compaction",
|
|
410
529
|
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
530
|
requiresParent: true,
|
|
414
531
|
...parentRef ? { parentRef } : {},
|
|
415
532
|
...event.timestamp ? { startTime: event.timestamp } : {},
|
|
@@ -474,6 +591,7 @@ function createFlueProjection(options = {}) {
|
|
|
474
591
|
if (!envelope.modelPinned && purpose === "agent")
|
|
475
592
|
envelope.requestModel = request.requestedModel;
|
|
476
593
|
if (Array.isArray(request.input?.tools)) envelope.toolsReported = true;
|
|
594
|
+
if (position?.atEnvelopeScope) captureInput(envelope, request);
|
|
477
595
|
}
|
|
478
596
|
return [
|
|
479
597
|
{
|
|
@@ -519,20 +637,16 @@ function createFlueProjection(options = {}) {
|
|
|
519
637
|
if (purpose === "agent" && response.finishReason) {
|
|
520
638
|
envelope.lastFinishReason = response.finishReason;
|
|
521
639
|
}
|
|
640
|
+
if (content.outputMessages && purpose === "agent" && !event.taskId && !isError) {
|
|
641
|
+
const output = projectFlueMessage(response.output);
|
|
642
|
+
if (output?.role === "assistant") envelope.outputMessage = output;
|
|
643
|
+
}
|
|
522
644
|
}
|
|
523
645
|
const attributes = {
|
|
524
646
|
...response.responseModel ? { [GEN_AI.responseModel]: response.responseModel } : {},
|
|
525
647
|
...response.responseId ? { [GEN_AI.responseId]: response.responseId } : {},
|
|
526
648
|
...response.finishReason ? { [GEN_AI.finishReasons]: [response.finishReason] } : {},
|
|
527
649
|
...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
650
|
...response.providerFinishReason ? { [RUNTYPE.providerFinishReason]: response.providerFinishReason } : {},
|
|
537
651
|
...response.gatewayLogId ? { [RUNTYPE.gatewayLogId]: response.gatewayLogId } : {}
|
|
538
652
|
};
|
|
@@ -563,8 +677,6 @@ function createFlueProjection(options = {}) {
|
|
|
563
677
|
ref,
|
|
564
678
|
name: "flue.operation shell",
|
|
565
679
|
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
680
|
requiresParent: true,
|
|
569
681
|
...parentRef ? { parentRef } : {},
|
|
570
682
|
...event.timestamp ? { startTime: event.timestamp } : {},
|
|
@@ -596,10 +708,6 @@ function createFlueProjection(options = {}) {
|
|
|
596
708
|
[GEN_AI.toolName]: toolName,
|
|
597
709
|
[GEN_AI.toolCallId]: toolCallId,
|
|
598
710
|
...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
711
|
...isDelegation ? {} : { [GEN_AI.toolType]: "function" },
|
|
604
712
|
...toolType ? { [RUNTYPE.toolType]: toolType } : {},
|
|
605
713
|
...event.origin ? { [FLUE.toolOrigin]: event.origin } : {},
|
|
@@ -658,10 +766,28 @@ function createFlueProjection(options = {}) {
|
|
|
658
766
|
} : {},
|
|
659
767
|
...stopReason ? { [RUNTYPE.stopReason]: stopReason } : {},
|
|
660
768
|
...envelope.toolsReported ? { [RUNTYPE.toolsReported]: true } : {},
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
769
|
+
...envelope.maxIteration !== null ? { [RUNTYPE.iteration]: envelope.maxIteration } : {},
|
|
770
|
+
...envelopeContentAttributes(envelope)
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function captureInput(envelope, request) {
|
|
774
|
+
const input = request.input;
|
|
775
|
+
if (content.inputMessages) {
|
|
776
|
+
const messages = projectFlueMessages(input?.messages);
|
|
777
|
+
if (messages.length > 0) envelope.inputMessages = messages;
|
|
778
|
+
}
|
|
779
|
+
if (content.systemInstructions && typeof input?.systemPrompt === "string") {
|
|
780
|
+
envelope.systemInstructions = input.systemPrompt;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function envelopeContentAttributes(envelope) {
|
|
784
|
+
const input = envelope.inputMessages ? encodeMessagesValue(envelope.inputMessages, content, "input_messages") : void 0;
|
|
785
|
+
const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
|
|
786
|
+
const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
|
|
787
|
+
return {
|
|
788
|
+
...input !== void 0 ? { [GEN_AI_CONTENT.inputMessages]: input } : {},
|
|
789
|
+
...system !== void 0 ? { [GEN_AI_CONTENT.systemInstructions]: system } : {},
|
|
790
|
+
...output !== void 0 ? { [GEN_AI_CONTENT.outputMessages]: output } : {}
|
|
665
791
|
};
|
|
666
792
|
}
|
|
667
793
|
function resolveEnvelopeRef(event) {
|
|
@@ -781,9 +907,7 @@ function positionAttributes(position) {
|
|
|
781
907
|
return {
|
|
782
908
|
...position.turnId ? { [RUNTYPE.turnId]: position.turnId } : {},
|
|
783
909
|
[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.
|
|
910
|
+
// WHY(packages/flue-otel/src/projection.ts): Do not stamp delegated turns with root iteration positions; they would inflate the run iteration count.
|
|
787
911
|
...position.atEnvelopeScope ? { [RUNTYPE.iteration]: position.index } : {}
|
|
788
912
|
};
|
|
789
913
|
}
|
|
@@ -809,9 +933,6 @@ function closeIntent(ref, event, isError, errorInfo) {
|
|
|
809
933
|
...isError ? {
|
|
810
934
|
error: {
|
|
811
935
|
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
936
|
...errorInfo?.name ? { exceptionType: errorInfo.name } : {}
|
|
816
937
|
}
|
|
817
938
|
} : {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/flue-otel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -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.
|