@runtypelabs/flue-otel 0.5.0 → 0.5.2

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.2";
279
302
 
280
303
  // src/semconv.ts
281
304
  var GEN_AI = {
@@ -320,12 +343,15 @@ var RUNTYPE = {
320
343
  adapterVersion: "runtype.adapter.version",
321
344
  executionId: "runtype.execution.id",
322
345
  iteration: "runtype.iteration",
346
+ iterations: "runtype.iterations",
347
+ totalCost: "runtype.total_cost",
323
348
  stopReason: "runtype.stop_reason",
324
349
  toolsReported: "runtype.tools.reported",
325
350
  toolType: "runtype.tool.type",
326
351
  turnId: "runtype.turn.id",
327
352
  turnIndex: "runtype.turn.index",
328
353
  providerFinishReason: "runtype.provider.finish_reason",
354
+ providerServiceTier: "runtype.provider.service_tier",
329
355
  gatewayLogId: "runtype.gateway.log_id"
330
356
  };
331
357
  var FLUE = {
@@ -576,6 +602,11 @@ function createFlueProjection(options = {}) {
576
602
  function onCompactionEnd(event) {
577
603
  const ref = compactionKey(event);
578
604
  if (!open.has(ref)) return [];
605
+ if (!event.taskId) {
606
+ const envelope = resolveEnvelopeRef(event);
607
+ const state = envelope ? envelopes.get(envelope) : void 0;
608
+ if (state) state.compacted = true;
609
+ }
579
610
  const intents = sweepDescendants(ref, event.timestamp);
580
611
  intents.push(
581
612
  closeIntent(ref, event, readBoolean(event, "isError") === true, readErrorInfo(event))
@@ -808,16 +839,31 @@ function createFlueProjection(options = {}) {
808
839
  }
809
840
  function captureInput(envelope, request) {
810
841
  const input = request.input;
811
- if (content.inputMessages) {
812
- const messages = projectFlueMessages(input?.messages);
813
- if (messages.length > 0) envelope.inputMessages = messages;
842
+ if (envelope.inputRaw && (envelope.compacted || isCompactedHistory(input?.messages))) return;
843
+ if (content.inputMessages && Array.isArray(input?.messages) && input.messages.length > 0) {
844
+ envelope.inputRaw = input.messages;
845
+ envelope.inputBaseCount ??= input.messages.length;
814
846
  }
815
847
  if (content.systemInstructions && typeof input?.systemPrompt === "string") {
816
848
  envelope.systemInstructions = input.systemPrompt;
817
849
  }
818
850
  }
851
+ function isCompactedHistory(messages) {
852
+ if (!Array.isArray(messages) || messages.length === 0) return false;
853
+ const first = messages[0];
854
+ if (!first || first.role !== "user") return false;
855
+ return flueMessageText(first.content).startsWith(COMPACTION_SUMMARY_PREFIX);
856
+ }
857
+ function projectInput(envelope) {
858
+ const raw = envelope.inputRaw ?? [];
859
+ const base = envelope.inputBaseCount ?? raw.length;
860
+ return [
861
+ ...projectFlueMessages(raw.slice(0, base)),
862
+ ...projectFlueMessages(raw.slice(base)).filter((message) => message.role !== "assistant")
863
+ ];
864
+ }
819
865
  function envelopeContentAttributes(envelope) {
820
- const input = envelope.inputMessages ? encodeMessagesValue(envelope.inputMessages, content, "input_messages") : void 0;
866
+ const input = envelope.inputRaw ? encodeMessagesValue(projectInput(envelope), content, "input_messages") : void 0;
821
867
  const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
822
868
  const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
823
869
  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
  /**
@@ -444,12 +444,15 @@ declare const RUNTYPE: {
444
444
  readonly adapterVersion: "runtype.adapter.version";
445
445
  readonly executionId: "runtype.execution.id";
446
446
  readonly iteration: "runtype.iteration";
447
+ readonly iterations: "runtype.iterations";
448
+ readonly totalCost: "runtype.total_cost";
447
449
  readonly stopReason: "runtype.stop_reason";
448
450
  readonly toolsReported: "runtype.tools.reported";
449
451
  readonly toolType: "runtype.tool.type";
450
452
  readonly turnId: "runtype.turn.id";
451
453
  readonly turnIndex: "runtype.turn.index";
452
454
  readonly providerFinishReason: "runtype.provider.finish_reason";
455
+ readonly providerServiceTier: "runtype.provider.service_tier";
453
456
  readonly gatewayLogId: "runtype.gateway.log_id";
454
457
  };
455
458
  /**
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
  /**
@@ -444,12 +444,15 @@ declare const RUNTYPE: {
444
444
  readonly adapterVersion: "runtype.adapter.version";
445
445
  readonly executionId: "runtype.execution.id";
446
446
  readonly iteration: "runtype.iteration";
447
+ readonly iterations: "runtype.iterations";
448
+ readonly totalCost: "runtype.total_cost";
447
449
  readonly stopReason: "runtype.stop_reason";
448
450
  readonly toolsReported: "runtype.tools.reported";
449
451
  readonly toolType: "runtype.tool.type";
450
452
  readonly turnId: "runtype.turn.id";
451
453
  readonly turnIndex: "runtype.turn.index";
452
454
  readonly providerFinishReason: "runtype.provider.finish_reason";
455
+ readonly providerServiceTier: "runtype.provider.service_tier";
453
456
  readonly gatewayLogId: "runtype.gateway.log_id";
454
457
  };
455
458
  /**
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.2";
243
266
 
244
267
  // src/semconv.ts
245
268
  var GEN_AI = {
@@ -284,12 +307,15 @@ var RUNTYPE = {
284
307
  adapterVersion: "runtype.adapter.version",
285
308
  executionId: "runtype.execution.id",
286
309
  iteration: "runtype.iteration",
310
+ iterations: "runtype.iterations",
311
+ totalCost: "runtype.total_cost",
287
312
  stopReason: "runtype.stop_reason",
288
313
  toolsReported: "runtype.tools.reported",
289
314
  toolType: "runtype.tool.type",
290
315
  turnId: "runtype.turn.id",
291
316
  turnIndex: "runtype.turn.index",
292
317
  providerFinishReason: "runtype.provider.finish_reason",
318
+ providerServiceTier: "runtype.provider.service_tier",
293
319
  gatewayLogId: "runtype.gateway.log_id"
294
320
  };
295
321
  var FLUE = {
@@ -540,6 +566,11 @@ function createFlueProjection(options = {}) {
540
566
  function onCompactionEnd(event) {
541
567
  const ref = compactionKey(event);
542
568
  if (!open.has(ref)) return [];
569
+ if (!event.taskId) {
570
+ const envelope = resolveEnvelopeRef(event);
571
+ const state = envelope ? envelopes.get(envelope) : void 0;
572
+ if (state) state.compacted = true;
573
+ }
543
574
  const intents = sweepDescendants(ref, event.timestamp);
544
575
  intents.push(
545
576
  closeIntent(ref, event, readBoolean(event, "isError") === true, readErrorInfo(event))
@@ -772,16 +803,31 @@ function createFlueProjection(options = {}) {
772
803
  }
773
804
  function captureInput(envelope, request) {
774
805
  const input = request.input;
775
- if (content.inputMessages) {
776
- const messages = projectFlueMessages(input?.messages);
777
- if (messages.length > 0) envelope.inputMessages = messages;
806
+ if (envelope.inputRaw && (envelope.compacted || isCompactedHistory(input?.messages))) return;
807
+ if (content.inputMessages && Array.isArray(input?.messages) && input.messages.length > 0) {
808
+ envelope.inputRaw = input.messages;
809
+ envelope.inputBaseCount ??= input.messages.length;
778
810
  }
779
811
  if (content.systemInstructions && typeof input?.systemPrompt === "string") {
780
812
  envelope.systemInstructions = input.systemPrompt;
781
813
  }
782
814
  }
815
+ function isCompactedHistory(messages) {
816
+ if (!Array.isArray(messages) || messages.length === 0) return false;
817
+ const first = messages[0];
818
+ if (!first || first.role !== "user") return false;
819
+ return flueMessageText(first.content).startsWith(COMPACTION_SUMMARY_PREFIX);
820
+ }
821
+ function projectInput(envelope) {
822
+ const raw = envelope.inputRaw ?? [];
823
+ const base = envelope.inputBaseCount ?? raw.length;
824
+ return [
825
+ ...projectFlueMessages(raw.slice(0, base)),
826
+ ...projectFlueMessages(raw.slice(base)).filter((message) => message.role !== "assistant")
827
+ ];
828
+ }
783
829
  function envelopeContentAttributes(envelope) {
784
- const input = envelope.inputMessages ? encodeMessagesValue(envelope.inputMessages, content, "input_messages") : void 0;
830
+ const input = envelope.inputRaw ? encodeMessagesValue(projectInput(envelope), content, "input_messages") : void 0;
785
831
  const system = envelope.systemInstructions ? encodeSystemInstructionsValue(envelope.systemInstructions, content) : void 0;
786
832
  const output = envelope.outputMessage ? encodeMessagesValue([envelope.outputMessage], content, "output_messages") : void 0;
787
833
  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.2",
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",
@@ -32,8 +32,8 @@
32
32
  "@opentelemetry/api": "^1.9.0",
33
33
  "@opentelemetry/context-async-hooks": "^2.1.0",
34
34
  "@opentelemetry/sdk-trace-base": "^2.1.0",
35
- "@runtypelabs/execution-ingest": "0.6.25",
36
- "@runtypelabs/shared": "3.41.0",
35
+ "@runtypelabs/execution-ingest": "0.7.1",
36
+ "@runtypelabs/shared": "3.48.0",
37
37
  "tsup": "^8.0.2",
38
38
  "typescript": "^6.0.3",
39
39
  "vitest": "^4.1.0"