@runtypelabs/flue-otel 0.5.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 +23 -10
- package/dist/index.cjs +77 -34
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +77 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -341,11 +341,15 @@ createRuntypeFlueInstrumentation({
|
|
|
341
341
|
})
|
|
342
342
|
```
|
|
343
343
|
|
|
344
|
-
- **`inputMessages`** is the conversation
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
344
|
+
- **`inputMessages`** is the conversation the run was given, read from Flue's
|
|
345
|
+
normalized `turn_request.request.input.messages`: the first agent turn's
|
|
346
|
+
history, plus any `kind: 'user'` message delivered mid-run that a later
|
|
347
|
+
turn's history carries. The run's own assistant rows are not repeated here —
|
|
348
|
+
the tool spans and `outputMessages` carry them — and a turn after a
|
|
349
|
+
compaction, which sees a summary in place of the conversation, never
|
|
350
|
+
replaces an earlier capture (Flue does not flag such a request, so the
|
|
351
|
+
`compaction` observation and the summary row it puts first are the signal).
|
|
352
|
+
One copy per run, on the envelope. It lands on
|
|
349
353
|
the run as its transcript and is the seed eval capture freezes a case from.
|
|
350
354
|
- **`outputMessages`** is the assistant text of the _last_ agent turn that
|
|
351
355
|
produced any, read from `turn.response.output`. A final step that only called
|
|
@@ -361,10 +365,18 @@ Messages are encoded as the GenAI semconv structured-message shape,
|
|
|
361
365
|
`[{ role, parts: [{ type: 'text', content }] }]`, and they are **text parts
|
|
362
366
|
only**: a `toolResult` message, a tool-call part, a thinking part and an image
|
|
363
367
|
part are dropped, as is any `system` row Flue put in the history (that is what
|
|
364
|
-
`systemInstructions` is for)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
+
`systemInstructions` is for). A Flue **signal** — the steering Flue delivers to
|
|
369
|
+
the model as a user message (`<signal type="…">…</signal>` deliveries and
|
|
370
|
+
`dispatch()` inputs, `useAgentFinish` continuations, stream recovery, the
|
|
371
|
+
compaction summary) — is exported as a `system` row, verbatim with its framing,
|
|
372
|
+
so it is never read as something the user said and the reader sees exactly what
|
|
373
|
+
the model saw. Recognition is a text-shape test on Flue's exact framing (one
|
|
374
|
+
element with a leading `type` attribute, body on its own lines, one text part),
|
|
375
|
+
so it can only _reduce_ misclassification: HTML elements that conventionally
|
|
376
|
+
lead with `type` (`<script>`, `<style>`, `<button>`, …) and multi-part messages
|
|
377
|
+
always stay user rows, but a user message that exactly reproduces the framing
|
|
378
|
+
with another tag lands as a `system` row. Tool content has its own switches
|
|
379
|
+
above; the
|
|
368
380
|
rest is never exported. `AgentMessage`, which Flue marks unstable, is still
|
|
369
381
|
never read — the `Llm*` request and response payloads are byte-identical on
|
|
370
382
|
1.x and 2.x.
|
|
@@ -374,7 +386,8 @@ the newest messages survive; a lone message that still does not fit has its
|
|
|
374
386
|
text cut with the truncation marker, and the JSON stays valid either way. The
|
|
375
387
|
system prompt is cut inside its text part. Because all three share one span,
|
|
376
388
|
each is additionally capped at a third of ingest's per-span ceiling
|
|
377
|
-
(128 KiB), whatever `maxChars` says
|
|
389
|
+
(128 KiB), whatever `maxChars` says — as a tool's arguments and result are each
|
|
390
|
+
capped at half of it (192 KiB); at the default ceiling everything fits
|
|
378
391
|
comfortably.
|
|
379
392
|
|
|
380
393
|
### Redaction
|
package/dist/index.cjs
CHANGED
|
@@ -81,7 +81,22 @@ function mapSettlementOutcome(outcome) {
|
|
|
81
81
|
// src/messages.ts
|
|
82
82
|
var ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant"]);
|
|
83
83
|
var FLUE_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
84
|
-
var SIGNAL_MESSAGE =
|
|
84
|
+
var SIGNAL_MESSAGE = /^<([A-Za-z_][\w.-]*) type="[^"]*"[^>]*>\n[\s\S]*\n<\/\1>$/;
|
|
85
|
+
var COMPACTION_SUMMARY_PREFIX = '<compaction type="context_summary"';
|
|
86
|
+
var HTML_TYPED_TAGS = /* @__PURE__ */ new Set([
|
|
87
|
+
"a",
|
|
88
|
+
"button",
|
|
89
|
+
"embed",
|
|
90
|
+
"input",
|
|
91
|
+
"link",
|
|
92
|
+
"menu",
|
|
93
|
+
"object",
|
|
94
|
+
"ol",
|
|
95
|
+
"script",
|
|
96
|
+
"source",
|
|
97
|
+
"style",
|
|
98
|
+
"ul"
|
|
99
|
+
]);
|
|
85
100
|
function projectFlueMessages(value) {
|
|
86
101
|
if (!Array.isArray(value)) return [];
|
|
87
102
|
const out = [];
|
|
@@ -97,18 +112,22 @@ function projectFlueMessage(value) {
|
|
|
97
112
|
if (typeof role !== "string" || !FLUE_ROLES.has(role)) return void 0;
|
|
98
113
|
const text = flueMessageText(value.content);
|
|
99
114
|
if (text === "") return void 0;
|
|
100
|
-
if (role === "user" &&
|
|
115
|
+
if (role === "user" && isFlueSignal(value.content, text)) {
|
|
116
|
+
return { role: "system", parts: [{ type: "text", content: text }] };
|
|
117
|
+
}
|
|
101
118
|
return { role, parts: [{ type: "text", content: text }] };
|
|
102
119
|
}
|
|
120
|
+
function isFlueSignal(content, text) {
|
|
121
|
+
if (!isSingleText(content)) return false;
|
|
122
|
+
const signal = SIGNAL_MESSAGE.exec(text);
|
|
123
|
+
return signal !== null && !HTML_TYPED_TAGS.has(signal[1].toLowerCase());
|
|
124
|
+
}
|
|
125
|
+
function isSingleText(content) {
|
|
126
|
+
if (typeof content === "string") return true;
|
|
127
|
+
return Array.isArray(content) && content.length === 1;
|
|
128
|
+
}
|
|
103
129
|
function flueMessageText(content) {
|
|
104
|
-
|
|
105
|
-
if (!Array.isArray(content)) return "";
|
|
106
|
-
const texts = [];
|
|
107
|
-
for (const part of content) {
|
|
108
|
-
if (!isRecord(part) || part.type !== "text") continue;
|
|
109
|
-
if (typeof part.text === "string" && part.text.length > 0) texts.push(part.text);
|
|
110
|
-
}
|
|
111
|
-
return texts.join("\n");
|
|
130
|
+
return contentMessageText({ content });
|
|
112
131
|
}
|
|
113
132
|
function normalizeContentMessages(value) {
|
|
114
133
|
if (!Array.isArray(value)) return [];
|
|
@@ -159,6 +178,7 @@ function resolveContentPolicy(setting) {
|
|
|
159
178
|
systemInstructions: on(options?.systemInstructions),
|
|
160
179
|
maxChars,
|
|
161
180
|
envelopeMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 3)),
|
|
181
|
+
toolMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 2)),
|
|
162
182
|
redact: options?.redact
|
|
163
183
|
};
|
|
164
184
|
}
|
|
@@ -166,10 +186,10 @@ function encodeContentValue(value, policy, context3) {
|
|
|
166
186
|
if (value === void 0) return void 0;
|
|
167
187
|
const redacted = applyRedact(value, policy, context3);
|
|
168
188
|
if (redacted === void 0) return void 0;
|
|
169
|
-
if (context3.kind === "arguments") return encodeArguments(redacted, policy.
|
|
189
|
+
if (context3.kind === "arguments") return encodeArguments(redacted, policy.toolMaxChars);
|
|
170
190
|
const encoded = stringify(redacted);
|
|
171
191
|
if (encoded === void 0) return void 0;
|
|
172
|
-
return truncate(encoded, policy.
|
|
192
|
+
return truncate(encoded, policy.toolMaxChars);
|
|
173
193
|
}
|
|
174
194
|
function encodeMessagesValue(messages, policy, kind) {
|
|
175
195
|
if (messages.length === 0) return void 0;
|
|
@@ -178,17 +198,23 @@ function encodeMessagesValue(messages, policy, kind) {
|
|
|
178
198
|
const all = normalizeContentMessages(redacted);
|
|
179
199
|
if (all.length === 0) return void 0;
|
|
180
200
|
const maxChars = policy.envelopeMaxChars;
|
|
201
|
+
const perMessage = all.length > 1 ? Math.floor((maxChars - 3) / 2) : maxChars - 2;
|
|
202
|
+
const encoded = [];
|
|
203
|
+
for (const message of all) {
|
|
204
|
+
const one = JSON.stringify(message);
|
|
205
|
+
encoded.push(one.length <= perMessage ? one : encodeTruncatedMessage(message, perMessage) ?? "");
|
|
206
|
+
}
|
|
181
207
|
let start = all.length;
|
|
182
208
|
let total = 2;
|
|
183
209
|
while (start > 0) {
|
|
184
|
-
const length =
|
|
210
|
+
const length = encoded[start - 1].length + (start < all.length ? 1 : 0);
|
|
185
211
|
if (total + length > maxChars) break;
|
|
186
212
|
total += length;
|
|
187
213
|
start -= 1;
|
|
188
214
|
}
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
return
|
|
215
|
+
const kept = encoded.slice(start).filter((one) => one !== "");
|
|
216
|
+
if (kept.length === 0) return void 0;
|
|
217
|
+
return "[" + kept.join(",") + "]";
|
|
192
218
|
}
|
|
193
219
|
function encodeSystemInstructionsValue(value, policy) {
|
|
194
220
|
if (value.length === 0) return void 0;
|
|
@@ -201,7 +227,7 @@ function encodeSystemInstructionsValue(value, policy) {
|
|
|
201
227
|
function encodeTruncatedMessage(message, maxChars) {
|
|
202
228
|
const text = message.parts.map((part) => part.content).join("\n");
|
|
203
229
|
return fitJson(
|
|
204
|
-
(cut) => JSON.stringify(
|
|
230
|
+
(cut) => JSON.stringify({ role: message.role, parts: [{ type: "text", content: cut }] }),
|
|
205
231
|
text,
|
|
206
232
|
maxChars
|
|
207
233
|
);
|
|
@@ -212,11 +238,15 @@ function encodeTextParts(text, maxChars) {
|
|
|
212
238
|
function fitJson(wrap, text, maxChars) {
|
|
213
239
|
let encoded = wrap(text);
|
|
214
240
|
if (encoded.length <= maxChars) return encoded;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
241
|
+
const shell = wrap("").length;
|
|
242
|
+
let budget = maxChars - shell;
|
|
243
|
+
for (let round = 0; round < 8; round += 1) {
|
|
244
|
+
if (budget <= truncationMarker(text.length).length) return void 0;
|
|
245
|
+
const cut = truncate(text, budget);
|
|
246
|
+
encoded = wrap(cut);
|
|
218
247
|
if (encoded.length <= maxChars) return encoded;
|
|
219
|
-
budget
|
|
248
|
+
const rescaled = Math.floor(budget * (maxChars - shell) / (encoded.length - shell));
|
|
249
|
+
budget = Math.min(budget - 1, rescaled);
|
|
220
250
|
}
|
|
221
251
|
return void 0;
|
|
222
252
|
}
|
|
@@ -233,15 +263,8 @@ function encodeArguments(value, maxChars) {
|
|
|
233
263
|
const encoded = stringify(shaped);
|
|
234
264
|
if (encoded === void 0) return void 0;
|
|
235
265
|
if (encoded.length <= maxChars) return encoded;
|
|
236
|
-
|
|
237
|
-
for (let round = 0; round < 8 && budget > 0; round += 1) {
|
|
238
|
-
const wrapped = JSON.stringify({ truncated: truncate(encoded, budget) });
|
|
239
|
-
if (wrapped.length <= maxChars) return wrapped;
|
|
240
|
-
budget -= wrapped.length - maxChars;
|
|
241
|
-
}
|
|
242
|
-
return JSON.stringify({ truncated: truncate(encoded, Math.max(0, budget)) });
|
|
266
|
+
return fitJson((cut) => JSON.stringify({ truncated: cut }), encoded, maxChars);
|
|
243
267
|
}
|
|
244
|
-
var TRUNCATED_WRAPPER_OVERHEAD = 16;
|
|
245
268
|
function isPlainObject(value) {
|
|
246
269
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
247
270
|
}
|
|
@@ -275,7 +298,7 @@ function isHighSurrogate(code) {
|
|
|
275
298
|
}
|
|
276
299
|
|
|
277
300
|
// package.json
|
|
278
|
-
var version = "0.5.
|
|
301
|
+
var version = "0.5.1";
|
|
279
302
|
|
|
280
303
|
// src/semconv.ts
|
|
281
304
|
var GEN_AI = {
|
|
@@ -576,6 +599,11 @@ function createFlueProjection(options = {}) {
|
|
|
576
599
|
function onCompactionEnd(event) {
|
|
577
600
|
const ref = compactionKey(event);
|
|
578
601
|
if (!open.has(ref)) return [];
|
|
602
|
+
if (!event.taskId) {
|
|
603
|
+
const envelope = resolveEnvelopeRef(event);
|
|
604
|
+
const state = envelope ? envelopes.get(envelope) : void 0;
|
|
605
|
+
if (state) state.compacted = true;
|
|
606
|
+
}
|
|
579
607
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
580
608
|
intents.push(
|
|
581
609
|
closeIntent(ref, event, readBoolean(event, "isError") === true, readErrorInfo(event))
|
|
@@ -808,16 +836,31 @@ function createFlueProjection(options = {}) {
|
|
|
808
836
|
}
|
|
809
837
|
function captureInput(envelope, request) {
|
|
810
838
|
const input = request.input;
|
|
811
|
-
if (
|
|
812
|
-
|
|
813
|
-
|
|
839
|
+
if (envelope.inputRaw && (envelope.compacted || isCompactedHistory(input?.messages))) return;
|
|
840
|
+
if (content.inputMessages && Array.isArray(input?.messages) && input.messages.length > 0) {
|
|
841
|
+
envelope.inputRaw = input.messages;
|
|
842
|
+
envelope.inputBaseCount ??= input.messages.length;
|
|
814
843
|
}
|
|
815
844
|
if (content.systemInstructions && typeof input?.systemPrompt === "string") {
|
|
816
845
|
envelope.systemInstructions = input.systemPrompt;
|
|
817
846
|
}
|
|
818
847
|
}
|
|
848
|
+
function isCompactedHistory(messages) {
|
|
849
|
+
if (!Array.isArray(messages) || messages.length === 0) return false;
|
|
850
|
+
const first = messages[0];
|
|
851
|
+
if (!first || first.role !== "user") return false;
|
|
852
|
+
return flueMessageText(first.content).startsWith(COMPACTION_SUMMARY_PREFIX);
|
|
853
|
+
}
|
|
854
|
+
function projectInput(envelope) {
|
|
855
|
+
const raw = envelope.inputRaw ?? [];
|
|
856
|
+
const base = envelope.inputBaseCount ?? raw.length;
|
|
857
|
+
return [
|
|
858
|
+
...projectFlueMessages(raw.slice(0, base)),
|
|
859
|
+
...projectFlueMessages(raw.slice(base)).filter((message) => message.role !== "assistant")
|
|
860
|
+
];
|
|
861
|
+
}
|
|
819
862
|
function envelopeContentAttributes(envelope) {
|
|
820
|
-
const input = envelope.
|
|
863
|
+
const input = envelope.inputRaw ? encodeMessagesValue(projectInput(envelope), content, "input_messages") : void 0;
|
|
821
864
|
const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
|
|
822
865
|
const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
|
|
823
866
|
return {
|
package/dist/index.d.cts
CHANGED
|
@@ -266,9 +266,9 @@ interface FlueContentOptions {
|
|
|
266
266
|
*/
|
|
267
267
|
toolResults?: boolean;
|
|
268
268
|
/**
|
|
269
|
-
* Emit the conversation the run
|
|
270
|
-
*
|
|
271
|
-
*
|
|
269
|
+
* Emit the conversation the run was given as `gen_ai.input.messages` on the `invoke_agent` span:
|
|
270
|
+
* text-only user and assistant rows from Flue's normalized `turn_request.request.input.messages`,
|
|
271
|
+
* plus any user message a later turn's history carries, minus the run's own assistant rows.
|
|
272
272
|
*/
|
|
273
273
|
inputMessages?: boolean;
|
|
274
274
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -266,9 +266,9 @@ interface FlueContentOptions {
|
|
|
266
266
|
*/
|
|
267
267
|
toolResults?: boolean;
|
|
268
268
|
/**
|
|
269
|
-
* Emit the conversation the run
|
|
270
|
-
*
|
|
271
|
-
*
|
|
269
|
+
* Emit the conversation the run was given as `gen_ai.input.messages` on the `invoke_agent` span:
|
|
270
|
+
* text-only user and assistant rows from Flue's normalized `turn_request.request.input.messages`,
|
|
271
|
+
* plus any user message a later turn's history carries, minus the run's own assistant rows.
|
|
272
272
|
*/
|
|
273
273
|
inputMessages?: boolean;
|
|
274
274
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -45,7 +45,22 @@ function mapSettlementOutcome(outcome) {
|
|
|
45
45
|
// src/messages.ts
|
|
46
46
|
var ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant"]);
|
|
47
47
|
var FLUE_ROLES = /* @__PURE__ */ new Set(["user", "assistant"]);
|
|
48
|
-
var SIGNAL_MESSAGE =
|
|
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
|
+
]);
|
|
49
64
|
function projectFlueMessages(value) {
|
|
50
65
|
if (!Array.isArray(value)) return [];
|
|
51
66
|
const out = [];
|
|
@@ -61,18 +76,22 @@ function projectFlueMessage(value) {
|
|
|
61
76
|
if (typeof role !== "string" || !FLUE_ROLES.has(role)) return void 0;
|
|
62
77
|
const text = flueMessageText(value.content);
|
|
63
78
|
if (text === "") return void 0;
|
|
64
|
-
if (role === "user" &&
|
|
79
|
+
if (role === "user" && isFlueSignal(value.content, text)) {
|
|
80
|
+
return { role: "system", parts: [{ type: "text", content: text }] };
|
|
81
|
+
}
|
|
65
82
|
return { role, parts: [{ type: "text", content: text }] };
|
|
66
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
|
+
}
|
|
67
93
|
function flueMessageText(content) {
|
|
68
|
-
|
|
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");
|
|
94
|
+
return contentMessageText({ content });
|
|
76
95
|
}
|
|
77
96
|
function normalizeContentMessages(value) {
|
|
78
97
|
if (!Array.isArray(value)) return [];
|
|
@@ -123,6 +142,7 @@ function resolveContentPolicy(setting) {
|
|
|
123
142
|
systemInstructions: on(options?.systemInstructions),
|
|
124
143
|
maxChars,
|
|
125
144
|
envelopeMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 3)),
|
|
145
|
+
toolMaxChars: Math.min(maxChars, Math.floor(INGEST_CONTENT_SPAN_CEILING / 2)),
|
|
126
146
|
redact: options?.redact
|
|
127
147
|
};
|
|
128
148
|
}
|
|
@@ -130,10 +150,10 @@ function encodeContentValue(value, policy, context3) {
|
|
|
130
150
|
if (value === void 0) return void 0;
|
|
131
151
|
const redacted = applyRedact(value, policy, context3);
|
|
132
152
|
if (redacted === void 0) return void 0;
|
|
133
|
-
if (context3.kind === "arguments") return encodeArguments(redacted, policy.
|
|
153
|
+
if (context3.kind === "arguments") return encodeArguments(redacted, policy.toolMaxChars);
|
|
134
154
|
const encoded = stringify(redacted);
|
|
135
155
|
if (encoded === void 0) return void 0;
|
|
136
|
-
return truncate(encoded, policy.
|
|
156
|
+
return truncate(encoded, policy.toolMaxChars);
|
|
137
157
|
}
|
|
138
158
|
function encodeMessagesValue(messages, policy, kind) {
|
|
139
159
|
if (messages.length === 0) return void 0;
|
|
@@ -142,17 +162,23 @@ function encodeMessagesValue(messages, policy, kind) {
|
|
|
142
162
|
const all = normalizeContentMessages(redacted);
|
|
143
163
|
if (all.length === 0) return void 0;
|
|
144
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
|
+
}
|
|
145
171
|
let start = all.length;
|
|
146
172
|
let total = 2;
|
|
147
173
|
while (start > 0) {
|
|
148
|
-
const length =
|
|
174
|
+
const length = encoded[start - 1].length + (start < all.length ? 1 : 0);
|
|
149
175
|
if (total + length > maxChars) break;
|
|
150
176
|
total += length;
|
|
151
177
|
start -= 1;
|
|
152
178
|
}
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
return
|
|
179
|
+
const kept = encoded.slice(start).filter((one) => one !== "");
|
|
180
|
+
if (kept.length === 0) return void 0;
|
|
181
|
+
return "[" + kept.join(",") + "]";
|
|
156
182
|
}
|
|
157
183
|
function encodeSystemInstructionsValue(value, policy) {
|
|
158
184
|
if (value.length === 0) return void 0;
|
|
@@ -165,7 +191,7 @@ function encodeSystemInstructionsValue(value, policy) {
|
|
|
165
191
|
function encodeTruncatedMessage(message, maxChars) {
|
|
166
192
|
const text = message.parts.map((part) => part.content).join("\n");
|
|
167
193
|
return fitJson(
|
|
168
|
-
(cut) => JSON.stringify(
|
|
194
|
+
(cut) => JSON.stringify({ role: message.role, parts: [{ type: "text", content: cut }] }),
|
|
169
195
|
text,
|
|
170
196
|
maxChars
|
|
171
197
|
);
|
|
@@ -176,11 +202,15 @@ function encodeTextParts(text, maxChars) {
|
|
|
176
202
|
function fitJson(wrap, text, maxChars) {
|
|
177
203
|
let encoded = wrap(text);
|
|
178
204
|
if (encoded.length <= maxChars) return encoded;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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);
|
|
182
211
|
if (encoded.length <= maxChars) return encoded;
|
|
183
|
-
budget
|
|
212
|
+
const rescaled = Math.floor(budget * (maxChars - shell) / (encoded.length - shell));
|
|
213
|
+
budget = Math.min(budget - 1, rescaled);
|
|
184
214
|
}
|
|
185
215
|
return void 0;
|
|
186
216
|
}
|
|
@@ -197,15 +227,8 @@ function encodeArguments(value, maxChars) {
|
|
|
197
227
|
const encoded = stringify(shaped);
|
|
198
228
|
if (encoded === void 0) return void 0;
|
|
199
229
|
if (encoded.length <= maxChars) return encoded;
|
|
200
|
-
|
|
201
|
-
for (let round = 0; round < 8 && budget > 0; round += 1) {
|
|
202
|
-
const wrapped = JSON.stringify({ truncated: truncate(encoded, budget) });
|
|
203
|
-
if (wrapped.length <= maxChars) return wrapped;
|
|
204
|
-
budget -= wrapped.length - maxChars;
|
|
205
|
-
}
|
|
206
|
-
return JSON.stringify({ truncated: truncate(encoded, Math.max(0, budget)) });
|
|
230
|
+
return fitJson((cut) => JSON.stringify({ truncated: cut }), encoded, maxChars);
|
|
207
231
|
}
|
|
208
|
-
var TRUNCATED_WRAPPER_OVERHEAD = 16;
|
|
209
232
|
function isPlainObject(value) {
|
|
210
233
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
211
234
|
}
|
|
@@ -239,7 +262,7 @@ function isHighSurrogate(code) {
|
|
|
239
262
|
}
|
|
240
263
|
|
|
241
264
|
// package.json
|
|
242
|
-
var version = "0.5.
|
|
265
|
+
var version = "0.5.1";
|
|
243
266
|
|
|
244
267
|
// src/semconv.ts
|
|
245
268
|
var GEN_AI = {
|
|
@@ -540,6 +563,11 @@ function createFlueProjection(options = {}) {
|
|
|
540
563
|
function onCompactionEnd(event) {
|
|
541
564
|
const ref = compactionKey(event);
|
|
542
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
|
+
}
|
|
543
571
|
const intents = sweepDescendants(ref, event.timestamp);
|
|
544
572
|
intents.push(
|
|
545
573
|
closeIntent(ref, event, readBoolean(event, "isError") === true, readErrorInfo(event))
|
|
@@ -772,16 +800,31 @@ function createFlueProjection(options = {}) {
|
|
|
772
800
|
}
|
|
773
801
|
function captureInput(envelope, request) {
|
|
774
802
|
const input = request.input;
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
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;
|
|
778
807
|
}
|
|
779
808
|
if (content.systemInstructions && typeof input?.systemPrompt === "string") {
|
|
780
809
|
envelope.systemInstructions = input.systemPrompt;
|
|
781
810
|
}
|
|
782
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
|
+
}
|
|
783
826
|
function envelopeContentAttributes(envelope) {
|
|
784
|
-
const input = envelope.
|
|
827
|
+
const input = envelope.inputRaw ? encodeMessagesValue(projectInput(envelope), content, "input_messages") : void 0;
|
|
785
828
|
const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
|
|
786
829
|
const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
|
|
787
830
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runtypelabs/flue-otel",
|
|
3
|
-
"version": "0.5.
|
|
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",
|