@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 CHANGED
@@ -341,11 +341,15 @@ createRuntypeFlueInstrumentation({
341
341
  })
342
342
  ```
343
343
 
344
- - **`inputMessages`** is the conversation as the run's _last_ model turn saw
345
- it, read from Flue's normalized `turn_request.request.input.messages`: every
346
- agent turn replaces the capture, so a user message dispatched mid-run (a
347
- signal delivery, `useDispatchMessage()`) is included, and so is the run's own
348
- intermediate assistant text. One copy per run, on the envelope. It lands on
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) and any Flue **signal** — Flue delivers its own
365
- steering (`<signal type="…">…</signal>`, `useAgentFinish` continuations, stream
366
- recovery) to the model as a user message wrapping one XML element, and those
367
- are not something the user said. Tool content has its own switches above; the
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; at the default ceiling they fit
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 = /^\s*<([A-Za-z][\w-]*)\b[^>]*>[\s\S]*<\/\1>\s*$/;
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" && SIGNAL_MESSAGE.test(text)) return void 0;
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
- if (typeof content === "string") return content;
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.maxChars);
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.maxChars);
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 = JSON.stringify(all[start - 1]).length + (start < all.length ? 1 : 0);
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
- if (start === 0) return JSON.stringify(all);
190
- if (start < all.length) return JSON.stringify(all.slice(start));
191
- return encodeTruncatedMessage(all[all.length - 1], maxChars);
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([{ role: message.role, parts: [{ type: "text", content: cut }] }]),
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
- let budget = maxChars - wrap("").length;
216
- for (let round = 0; round < 8 && budget > 0; round += 1) {
217
- encoded = wrap(truncate(text, budget));
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 -= encoded.length - maxChars;
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
- let budget = maxChars - TRUNCATED_WRAPPER_OVERHEAD;
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.0";
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 (content.inputMessages) {
812
- const messages = projectFlueMessages(input?.messages);
813
- if (messages.length > 0) envelope.inputMessages = messages;
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.inputMessages ? encodeMessagesValue(envelope.inputMessages, content, "input_messages") : void 0;
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's first model turn was given as `gen_ai.input.messages` on the
270
- * `invoke_agent` span: text-only user and assistant rows from Flue's normalized
271
- * `turn_request.request.input.messages`. Later turns of the same run only add tool traffic.
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's first model turn was given as `gen_ai.input.messages` on the
270
- * `invoke_agent` span: text-only user and assistant rows from Flue's normalized
271
- * `turn_request.request.input.messages`. Later turns of the same run only add tool traffic.
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 = /^\s*<([A-Za-z][\w-]*)\b[^>]*>[\s\S]*<\/\1>\s*$/;
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" && SIGNAL_MESSAGE.test(text)) return void 0;
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
- 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");
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.maxChars);
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.maxChars);
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 = JSON.stringify(all[start - 1]).length + (start < all.length ? 1 : 0);
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
- 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);
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([{ role: message.role, parts: [{ type: "text", content: cut }] }]),
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
- let budget = maxChars - wrap("").length;
180
- for (let round = 0; round < 8 && budget > 0; round += 1) {
181
- encoded = wrap(truncate(text, budget));
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 -= encoded.length - maxChars;
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
- let budget = maxChars - TRUNCATED_WRAPPER_OVERHEAD;
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.0";
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 (content.inputMessages) {
776
- const messages = projectFlueMessages(input?.messages);
777
- if (messages.length > 0) envelope.inputMessages = messages;
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.inputMessages ? encodeMessagesValue(envelope.inputMessages, content, "input_messages") : void 0;
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.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",