@lmnr-ai/lmnr 0.8.36 → 0.8.38

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.
Files changed (29) hide show
  1. package/dist/cli.cjs +2 -2
  2. package/dist/cli.d.cts +1 -1
  3. package/dist/cli.d.mts +1 -1
  4. package/dist/cli.mjs +2 -2
  5. package/dist/{dist-y1eeOsL1.cjs → dist-DGDsKbQ8.cjs} +77 -3
  6. package/dist/dist-DGDsKbQ8.cjs.map +1 -0
  7. package/dist/{dist-CKEmP0bk.mjs → dist-DVpPVAC5.mjs} +66 -4
  8. package/dist/dist-DVpPVAC5.mjs.map +1 -0
  9. package/dist/{evaluations-BQ_n7gj5.d.mts → evaluations-CzqDQADS.d.mts} +98 -30
  10. package/dist/{evaluations-BToNCOH0.d.cts → evaluations-F6Zzae_Y.d.cts} +98 -30
  11. package/dist/{file-utils-CceRl1Dm.mjs → file-utils-D3Jj4yAt.mjs} +2 -2
  12. package/dist/{file-utils-CceRl1Dm.mjs.map → file-utils-D3Jj4yAt.mjs.map} +1 -1
  13. package/dist/{file-utils-C9iU5M-4.cjs → file-utils-wA-Z77kv.cjs} +2 -2
  14. package/dist/{file-utils-C9iU5M-4.cjs.map → file-utils-wA-Z77kv.cjs.map} +1 -1
  15. package/dist/index.cjs +323 -521
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +13943 -7780
  18. package/dist/index.d.mts +13943 -7780
  19. package/dist/index.mjs +325 -523
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs +71 -55
  22. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs.map +1 -1
  23. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.cts +4410 -61
  24. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.mts +4410 -61
  25. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs +71 -55
  26. package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs.map +1 -1
  27. package/package.json +26 -26
  28. package/dist/dist-CKEmP0bk.mjs.map +0 -1
  29. package/dist/dist-y1eeOsL1.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-CVvi-lCc.cjs");
3
- const require_dist = require("./dist-y1eeOsL1.cjs");
3
+ const require_dist = require("./dist-DGDsKbQ8.cjs");
4
4
  const require_consts = require("./consts-DV6_Ea-8.cjs");
5
5
  let _opentelemetry_api = require("@opentelemetry/api");
6
6
  let _opentelemetry_context_async_hooks = require("@opentelemetry/context-async-hooks");
@@ -19,7 +19,6 @@ let _traceloop_instrumentation_openai = require("@traceloop/instrumentation-open
19
19
  let _traceloop_instrumentation_pinecone = require("@traceloop/instrumentation-pinecone");
20
20
  let _traceloop_instrumentation_qdrant = require("@traceloop/instrumentation-qdrant");
21
21
  let _traceloop_instrumentation_together = require("@traceloop/instrumentation-together");
22
- let _traceloop_instrumentation_vertexai = require("@traceloop/instrumentation-vertexai");
23
22
  let node_child_process = require("node:child_process");
24
23
  let node_crypto = require("node:crypto");
25
24
  let node_fs = require("node:fs");
@@ -95,7 +94,7 @@ var LaminarDataset = class extends EvaluationDataset {
95
94
  */
96
95
  async push(paths, recursive = false) {
97
96
  if (!this.client) throw new Error("Client not set");
98
- const { loadFromPaths } = await Promise.resolve().then(() => require("./file-utils-C9iU5M-4.cjs")).then((n) => n.file_utils_exports);
97
+ const { loadFromPaths } = await Promise.resolve().then(() => require("./file-utils-wA-Z77kv.cjs")).then((n) => n.file_utils_exports);
99
98
  const data = await loadFromPaths(Array.isArray(paths) ? paths : [paths], recursive);
100
99
  if (data.length === 0) {
101
100
  console.warn("No data to push. Skipping");
@@ -167,7 +166,10 @@ const stringifyPromptForTelemetry = (prompt) => JSON.stringify(prompt.map((messa
167
166
  ...message,
168
167
  content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "file" ? {
169
168
  ...part,
170
- data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data
169
+ data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data && typeof part.data === "object" && part.data.type === "data" && part.data.data instanceof Uint8Array ? {
170
+ ...part.data,
171
+ data: convertDataContentToBase64String(part.data.data)
172
+ } : part.data
171
173
  } : part)
172
174
  })));
173
175
  /**
@@ -604,28 +606,6 @@ const parseCacheUntil = (value) => {
604
606
  * the SDK only needs both ids present to arm replay.
605
607
  */
606
608
  const replayEnabledForConfig = (config) => config.replayTraceId !== null && config.cacheUntilSpanId !== null;
607
- const ROLLOUT_NOTE_KEY = "rollout.note";
608
- /**
609
- * Resolve the debug run note from the environment.
610
- *
611
- * Only read when debug mode is enabled (LMNR_DEBUG truthy). `LMNR_DEBUG_RUN_NOTES_FILE`
612
- * (a path to a file holding raw markdown) takes precedence over `LMNR_DEBUG_RUN_NOTES`
613
- * (inline raw markdown). A missing / unreadable file is logged and skipped (returns
614
- * null) rather than throwing — a bad note path must never block SDK init.
615
- *
616
- * Returns the raw note string, or null when debug is off or no note var is set.
617
- */
618
- const resolveDebugRunNote = () => {
619
- if (!isTruthy(process.env.LMNR_DEBUG)) return null;
620
- const file = process.env.LMNR_DEBUG_RUN_NOTES_FILE;
621
- if (file) try {
622
- return (0, node_fs.readFileSync)(file, "utf-8");
623
- } catch (err) {
624
- logger$22.warn(`LMNR_DEBUG_RUN_NOTES_FILE=${JSON.stringify(file)} could not be read; ignoring run note: ${require_dist.errorMessage(err)}`);
625
- return null;
626
- }
627
- return process.env.LMNR_DEBUG_RUN_NOTES ?? null;
628
- };
629
609
  /**
630
610
  * Build the debug config from the environment.
631
611
  *
@@ -1841,8 +1821,6 @@ var Laminar = class Laminar {
1841
1821
  this.baseHttpUrl = `${httpUrlWithoutSlash}:${port}`;
1842
1822
  } else this.baseHttpUrl = "";
1843
1823
  const envMetadata = process.env.LMNR_TRACE_METADATA ? JSON.parse(process.env.LMNR_TRACE_METADATA) : {};
1844
- const debugRunNote = resolveDebugRunNote();
1845
- if (debugRunNote !== null) envMetadata[ROLLOUT_NOTE_KEY] = debugRunNote;
1846
1824
  this.globalMetadata = {
1847
1825
  ...envMetadata,
1848
1826
  ...metadata ?? {}
@@ -6262,8 +6240,6 @@ const initInstrumentations = (client, suppressContentTracing, sessionRecordingOp
6262
6240
  }));
6263
6241
  instrumentations.push(new _traceloop_instrumentation_anthropic.AnthropicInstrumentation({ traceContent: !suppressContentTracing }));
6264
6242
  instrumentations.push(new _traceloop_instrumentation_cohere.CohereInstrumentation({ traceContent: !suppressContentTracing }));
6265
- instrumentations.push(new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing }));
6266
- instrumentations.push(new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing }));
6267
6243
  instrumentations.push(new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing }));
6268
6244
  instrumentations.push(new _traceloop_instrumentation_pinecone.PineconeInstrumentation());
6269
6245
  instrumentations.push(new _traceloop_instrumentation_langchain.LangChainInstrumentation({ traceContent: !suppressContentTracing }));
@@ -6315,16 +6291,6 @@ const manuallyInitInstrumentations = (client, instrumentModules, suppressContent
6315
6291
  instrumentations.push(cohereInstrumentation);
6316
6292
  cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
6317
6293
  }
6318
- if (instrumentModules?.google_vertexai) {
6319
- const vertexaiInstrumentation = new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing });
6320
- instrumentations.push(vertexaiInstrumentation);
6321
- vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
6322
- }
6323
- if (instrumentModules?.google_aiplatform) {
6324
- const aiplatformInstrumentation = new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing });
6325
- instrumentations.push(aiplatformInstrumentation);
6326
- aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
6327
- }
6328
6294
  if (instrumentModules?.bedrock) {
6329
6295
  const bedrockInstrumentation = new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing });
6330
6296
  instrumentations.push(bedrockInstrumentation);
@@ -7013,6 +6979,29 @@ const logger$2 = require_dist.initializeLogger();
7013
6979
  const getEvaluationUrl = (projectId, evaluationId, baseUrl, frontendPort) => {
7014
6980
  return `${require_dist.getFrontendUrl(baseUrl, frontendPort)}/project/${projectId}/evaluations/${evaluationId}`;
7015
6981
  };
6982
+ const SESSION_METADATA_KEY = "rollout.session_id";
6983
+ /**
6984
+ * When this eval runs under a debug session, auto-stamp the session id into the
6985
+ * evaluation metadata so the created evaluation links back to it with no extra
6986
+ * step. The session id is resolved by the debug runtime EXACTLY like traces —
6987
+ * `LMNR_DEBUG_SESSION_ID` env → `.lmnr/debug-session.json` → freshly minted — so
6988
+ * a plain `LMNR_DEBUG=1 <run-your-eval>` groups the eval under the current debug
6989
+ * session (no CLI wrapper needed). `getRuntime()` is null when debug mode is off,
6990
+ * so the metadata is returned unchanged.
6991
+ *
6992
+ * The backend writes the `evaluation` block from this key at eval creation; notes
6993
+ * are attached separately as `text` blocks keyed by the same session id
6994
+ * (`lmnr-cli debug session add-note`). Called after `Laminar.initialize()`, so
6995
+ * the runtime (and its resolved session id) already exist.
6996
+ */
6997
+ const withSessionMetadata = (metadata) => {
6998
+ const sessionId = getRuntime()?.sessionId ?? null;
6999
+ if (sessionId === null) return metadata;
7000
+ return {
7001
+ ...metadata ?? {},
7002
+ [SESSION_METADATA_KEY]: sessionId
7003
+ };
7004
+ };
7016
7005
  const getAverageScores = (results) => {
7017
7006
  const perScoreValues = {};
7018
7007
  for (const result of results) for (const key in result.scores) {
@@ -7133,7 +7122,7 @@ var Evaluation = class {
7133
7122
  }
7134
7123
  let resultDatapoints;
7135
7124
  try {
7136
- const evaluation = await this.client.evals.init(this.name, this.groupName, this.metadata);
7125
+ const evaluation = await this.client.evals.init(this.name, this.groupName, withSessionMetadata(this.metadata));
7137
7126
  const url = getEvaluationUrl(evaluation.projectId, evaluation.id, this.progressReporter.baseUrl, this.frontendPort);
7138
7127
  process.stdout.write(`\nCheck results at ${url}\n`);
7139
7128
  this.progressReporter.start({ length: await this.getLength() });
@@ -7326,260 +7315,6 @@ async function evaluate({ data, executor, evaluators, groupName, name, metadata,
7326
7315
  else return await evaluation.run();
7327
7316
  }
7328
7317
  //#endregion
7329
- //#region src/debug/aisdk-normalize.ts
7330
- const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
7331
- /** Mirror Rust `is_url`. */
7332
- const isUrl = (data) => data.startsWith("http://") || data.startsWith("https://") || data.startsWith("data:");
7333
- /** Mirror Rust `infer_image_type`. */
7334
- const inferImageType = (base64) => {
7335
- if (base64.startsWith("/9j/")) return "image/jpeg";
7336
- if (base64.startsWith("iVBORw0KGgo")) return "image/png";
7337
- if (base64.startsWith("R0lGODlh")) return "image/gif";
7338
- if (base64.startsWith("UklGR")) return "image/webp";
7339
- if (base64.startsWith("PHN2Zz")) return "image/svg+xml";
7340
- return "image/png";
7341
- };
7342
- const base64FromBytes = (bytes) => {
7343
- let latin1 = "";
7344
- for (const b of bytes) latin1 += String.fromCharCode(b & 255);
7345
- return btoa(latin1);
7346
- };
7347
- /**
7348
- * Mirror Rust `json_value_to_string`: a JSON string stays itself; anything else
7349
- * is compact-serialized. With serde `preserve_order` on, `JSON.stringify` matches
7350
- * `serde_json::Value::to_string()` byte-for-byte for object key order.
7351
- */
7352
- const jsonValueToString = (value) => typeof value === "string" ? value : JSON.stringify(value);
7353
- /** First string among the candidates (ports serde field aliases). */
7354
- const firstString = (...candidates) => {
7355
- for (const c of candidates) if (typeof c === "string") return c;
7356
- };
7357
- /**
7358
- * Port of `InstrumentationChatMessageImage` (untagged) →
7359
- * `from_instrumentation_content_part`'s `Image` arm. Returns the output
7360
- * `ChatMessageContentPart` object, or `null` if no untagged variant matches.
7361
- */
7362
- const parseImage = (e) => {
7363
- if (isRecord(e.source)) {
7364
- const src = e.source;
7365
- if (typeof src.media_type === "string" && typeof src.data === "string") return {
7366
- type: "image",
7367
- mediaType: src.media_type,
7368
- data: src.data
7369
- };
7370
- if (typeof src.url === "string") return {
7371
- type: "image_url",
7372
- url: src.url,
7373
- detail: typeof src.detail === "string" ? src.detail : null
7374
- };
7375
- }
7376
- if (Array.isArray(e.image) && e.image.every((n) => typeof n === "number")) {
7377
- const b64 = base64FromBytes(e.image);
7378
- return {
7379
- type: "image_url",
7380
- url: `data:${typeof e.mime_type === "string" ? e.mime_type : inferImageType(b64)};base64,${b64}`,
7381
- detail: null
7382
- };
7383
- }
7384
- if (typeof e.image === "string") {
7385
- if (isUrl(e.image)) return {
7386
- type: "image_url",
7387
- url: e.image,
7388
- detail: null
7389
- };
7390
- return {
7391
- type: "image",
7392
- data: e.image,
7393
- mediaType: typeof e.mime_type === "string" ? e.mime_type : "image/png"
7394
- };
7395
- }
7396
- return null;
7397
- };
7398
- /** Port of the `Document` arm (only the Base64 source shape exists). */
7399
- const parseDocument = (e) => {
7400
- if (!isRecord(e.source) || e.source.type !== "base64") return null;
7401
- const data = e.source.data;
7402
- if (typeof data !== "string") return null;
7403
- return {
7404
- type: "document",
7405
- source: {
7406
- type: "base64",
7407
- media_type: firstString(e.source.media_type, e.source.mediaType) ?? "application/octet-stream",
7408
- data
7409
- }
7410
- };
7411
- };
7412
- const DATA_URL_RE = /^data:((?:application|image)\/[a-zA-Z-]+);base64,/;
7413
- /**
7414
- * Port of the `File` arm (OpenAI file-id / base64, AI SDK v1/v2).
7415
- *
7416
- * Known parity gap (v7 + image-by-URL only): on AI SDK v7 the recording path
7417
- * stores `ai.prompt.messages` from the user-facing `ModelMessage[]` (an image
7418
- * passed by URL is a `{type:"image", image:"https://…"}` part), which the
7419
- * server reshapes via `parseImage` into an `image_url`. Replay instead hashes
7420
- * the LanguageModel-level prompt, where that same image is already a
7421
- * `{type:"file", data:"https://…", mediaType:"image/*"}` part — this arm maps
7422
- * it to `image`, not `image_url`, so the two hashes diverge and the lookup
7423
- * MISSes (degrading safely to a live call). This can't be reconciled here: the
7424
- * arm is a faithful port of the server's Rust `parseFile`, so on v6 (where the
7425
- * server itself reshapes a file part) emitting `image_url` would instead BREAK
7426
- * parity, and the replay wrapper has no signal for which SDK version recorded
7427
- * the span. Plain text, multi-part text, tool calls, and base64 images all hash
7428
- * identically across both paths; only URL-referenced images on v7 are affected.
7429
- */
7430
- const parseFile = (e) => {
7431
- if (isRecord(e.file)) {
7432
- const file = e.file;
7433
- if (typeof file.file_id === "string") return {
7434
- type: "text",
7435
- text: JSON.stringify({ file_id: file.file_id })
7436
- };
7437
- if (typeof file.file_data === "string") {
7438
- const m = DATA_URL_RE.exec(file.file_data.slice(0, 50));
7439
- return {
7440
- type: "document",
7441
- source: {
7442
- type: "base64",
7443
- media_type: m ? m[1] : "application/octet-stream",
7444
- data: m ? file.file_data.slice(file.file_data.indexOf(",") + 1) : file.file_data
7445
- }
7446
- };
7447
- }
7448
- return null;
7449
- }
7450
- if (typeof e.data === "string") {
7451
- const mediaType = firstString(e.media_type, e.mediaType, e.mime_type);
7452
- if (mediaType === void 0) return null;
7453
- if (mediaType.startsWith("image/")) return {
7454
- type: "image",
7455
- data: e.data,
7456
- mediaType
7457
- };
7458
- return {
7459
- type: "document",
7460
- source: {
7461
- type: "base64",
7462
- media_type: mediaType,
7463
- data: e.data
7464
- }
7465
- };
7466
- }
7467
- return null;
7468
- };
7469
- /**
7470
- * Port of `serde_json::from_value::<InstrumentationChatMessageContentPart>` for a
7471
- * single part, returning the mapped output `ChatMessageContentPart` object or
7472
- * `null` when no variant matches (an unrecognized `type`, or a recognized tag
7473
- * whose inner shape fails). `null` propagates to an all-or-nothing Text fallback.
7474
- *
7475
- * Output objects carry serde's serialized field names; `skip_serializing_if` is
7476
- * reproduced by omitting the field (ChatMessage.tool_call_id only), and `Option`
7477
- * fields WITHOUT skip (`detail`, tool-call `id`/`arguments`) always emit `null`.
7478
- */
7479
- const parsePart = (el) => {
7480
- if (!isRecord(el)) return null;
7481
- switch (el.type) {
7482
- case "text": {
7483
- const text = firstString(el.text, el.input_text, el.output_text);
7484
- return text === void 0 ? null : {
7485
- type: "text",
7486
- text
7487
- };
7488
- }
7489
- case "image_url": {
7490
- if (!isRecord(el.image_url) || typeof el.image_url.url !== "string") return null;
7491
- const detail = el.image_url.detail;
7492
- return {
7493
- type: "image_url",
7494
- url: el.image_url.url,
7495
- detail: typeof detail === "string" ? detail : null
7496
- };
7497
- }
7498
- case "image": return parseImage(el);
7499
- case "document": return parseDocument(el);
7500
- case "file": return parseFile(el);
7501
- case "tool-call": {
7502
- if (typeof el.toolName !== "string") return null;
7503
- const rawInput = "input" in el ? el.input : el.args;
7504
- return {
7505
- type: "tool_call",
7506
- name: el.toolName,
7507
- id: typeof el.toolCallId === "string" ? el.toolCallId : null,
7508
- arguments: rawInput === void 0 || rawInput === null ? null : rawInput
7509
- };
7510
- }
7511
- case "tool-result":
7512
- if (typeof el.toolCallId !== "string" || typeof el.toolName !== "string") return null;
7513
- if (!("output" in el || "result" in el)) return null;
7514
- return {
7515
- type: "tool-result",
7516
- toolCallId: el.toolCallId,
7517
- output: "output" in el ? el.output : el.result,
7518
- toolName: el.toolName
7519
- };
7520
- default: return null;
7521
- }
7522
- };
7523
- /**
7524
- * Port of `input_chat_messages_from_json`. `input` is the parsed
7525
- * `ai.prompt.messages` array. Each message's `content` becomes either a
7526
- * `ContentPartList` (when EVERY element parses as an instrumentation part) or a
7527
- * Text blob (`json_value_to_string` of the whole content) otherwise. A message
7528
- * missing `role`/`content` would be a server error; we skip it defensively so the
7529
- * hash is still computed over the well-formed remainder.
7530
- */
7531
- const inputChatMessagesFromJson = (input) => {
7532
- if (!Array.isArray(input)) return [];
7533
- const messages = [];
7534
- for (const message of input) {
7535
- if (!isRecord(message)) continue;
7536
- const role = message.role;
7537
- if (typeof role !== "string" || !("content" in message)) continue;
7538
- const otelContent = message.content;
7539
- let parts = null;
7540
- if (Array.isArray(otelContent)) {
7541
- parts = [];
7542
- for (const el of otelContent) {
7543
- const parsed = parsePart(el);
7544
- if (parsed === null) {
7545
- parts = null;
7546
- break;
7547
- }
7548
- parts.push(parsed);
7549
- }
7550
- }
7551
- const out = {
7552
- role,
7553
- content: parts !== null ? parts : jsonValueToString(otelContent)
7554
- };
7555
- if (typeof message.tool_call_id === "string") out.tool_call_id = message.tool_call_id;
7556
- messages.push(out);
7557
- }
7558
- return messages;
7559
- };
7560
- /**
7561
- * Reshape an AI SDK call's `options.prompt` into the reconstructed message array
7562
- * the server hashes. Version-agnostic: `stringifyPromptForTelemetry` already
7563
- * normalizes V2/V3/V4 prompts (Uint8Array file `data` → base64) into the stored
7564
- * `ai.prompt.messages` JSON, and the reshape operates on that JSON identically
7565
- * across versions.
7566
- *
7567
- * Returns `null` when the prompt cannot be reshaped at all (a `stringify` /
7568
- * `JSON.parse` failure). The caller MUST treat `null` as "can't compute a real
7569
- * key" and degrade to a live call WITHOUT latching: hashing a default/empty
7570
- * payload here would key the lookup off the wrong bytes, force a spurious MISS,
7571
- * and wrongly latch live mode for the rest of the replay.
7572
- */
7573
- const extractInputMessages = (options) => {
7574
- let aiPromptMessages;
7575
- try {
7576
- aiPromptMessages = JSON.parse(stringifyPromptForTelemetry(options.prompt));
7577
- } catch {
7578
- return null;
7579
- }
7580
- return inputChatMessagesFromJson(aiPromptMessages);
7581
- };
7582
- //#endregion
7583
7318
  //#region src/debug/hash.ts
7584
7319
  /**
7585
7320
  * Debug-replay input hashing.
@@ -7587,13 +7322,13 @@ const extractInputMessages = (options) => {
7587
7322
  * Computes the per-LLM-call `input_hash` the server-side debugger cache (v2) is
7588
7323
  * keyed by. The SDK and the Rust app-server MUST produce byte-identical hashes,
7589
7324
  * so this is a faithful port of the app-server primitives:
7590
- * - `canonical_json` — `app-server/src/traces/input_dedup.rs`
7591
- * - `extract_system_message`— `app-server/src/traces/prompt_hash.rs`
7592
- * - `debug_input_hash` — `app-server/src/traces/debug_cache.rs` (plan 01 §3)
7325
+ * - `canonical_json` — `app-server/src/traces/input_dedup.rs`
7326
+ * - `debug_input_hash` — `app-server/src/traces/input_dedup.rs`
7593
7327
  *
7594
- * The whole non-system message array is hashed as ONE blob: strip the first
7595
- * `role === "system"` message (when its text is non-empty), canonicalize the
7596
- * remaining array, then blake3 over its UTF-8 bytes and hex-encode the digest.
7328
+ * The whole non-system message array is hashed as ONE blob: strip EVERY
7329
+ * `role === "system"` message (regardless of content or position), canonicalize
7330
+ * the remaining array, then blake3 over its UTF-8 bytes and hex-encode the
7331
+ * digest.
7597
7332
  *
7598
7333
  * Keep this file line-comparable with the Python `debug/hash.py`.
7599
7334
  */
@@ -7614,53 +7349,25 @@ const canonicalJson = (value) => {
7614
7349
  return JSON.stringify(value);
7615
7350
  };
7616
7351
  /**
7617
- * Extract the system prompt text from a system-role message, or "" when none.
7618
- *
7619
- * Faithful port of Rust `extract_system_message`'s text resolution: a plain
7620
- * string content, else an array of `{text}` blocks joined by a space (only when
7621
- * the join is non-empty), else `parts[0].text` / `parts[0].content`.
7622
- */
7623
- const systemMessageText = (sysMsg) => {
7624
- const content = sysMsg.content;
7625
- if (typeof content === "string") return content;
7626
- if (Array.isArray(content)) {
7627
- const joined = content.map((block) => block !== null && typeof block === "object" ? block.text : void 0).filter((t) => typeof t === "string").join(" ");
7628
- if (joined.length > 0) return joined;
7629
- }
7630
- const parts = sysMsg.parts;
7631
- if (Array.isArray(parts) && parts.length > 0) {
7632
- const first = parts[0];
7633
- if (first !== null && typeof first === "object") {
7634
- const f = first;
7635
- const t = typeof f.text === "string" ? f.text : f.content;
7636
- if (typeof t === "string") return t;
7637
- }
7638
- }
7639
- return "";
7640
- };
7641
- /**
7642
- * Drop the first `role === "system"` message, matching the server.
7352
+ * Drop EVERY `role === "system"` message, matching the server.
7643
7353
  *
7644
- * Returns the array unchanged when there is no system message OR when the system
7645
- * message's resolved text is empty (Rust `extract_system_message` returns `None`
7646
- * in both cases, leaving the system message in the hashed array).
7354
+ * Content and position are irrelevant: an empty system string, a block-array
7355
+ * system, or repeated/trailing system messages are all stripped. The coding
7356
+ * agent may add/remove/edit its own system prompt(s) between iterations and
7357
+ * those edits must not change the cache key.
7647
7358
  */
7648
- const stripSystemMessage = (messages) => {
7649
- const sysIdx = messages.findIndex((m) => m !== null && typeof m === "object" && m.role === "system");
7650
- if (sysIdx === -1) return messages;
7651
- if (systemMessageText(messages[sysIdx]).length === 0) return messages;
7652
- return messages.filter((_, i) => i !== sysIdx);
7653
- };
7359
+ const stripSystemMessages = (messages) => messages.filter((m) => !(m !== null && typeof m === "object" && m.role === "system"));
7654
7360
  /**
7655
- * Compute the hex blake3 `input_hash` for a reconstructed message array.
7361
+ * Compute the hex blake3 `input_hash` for a message array.
7656
7362
  *
7657
- * `messages` must already be the server's reconstructed shape (the SDK reshapes
7658
- * provider-specific prompts via `aisdk-normalize.ts` so the hashed bytes match
7659
- * what `spans_v0` stores). The system message is stripped, the remainder
7660
- * canonicalized, and the digest hex-encoded (64 chars).
7363
+ * `messages` is the verbatim LanguageModel-level prompt
7364
+ * (`JSON.parse(stringifyPromptForTelemetry(options.prompt))`) the same bytes
7365
+ * the SDK records as `gen_ai.input.messages` and the server hashes on ingest.
7366
+ * System messages are stripped, the remainder canonicalized, and the digest
7367
+ * hex-encoded (64 chars).
7661
7368
  */
7662
7369
  const debugInputHash = (messages) => {
7663
- const canonical = canonicalJson(stripSystemMessage(messages));
7370
+ const canonical = canonicalJson(stripSystemMessages(messages));
7664
7371
  return (0, _noble_hashes_utils_js.bytesToHex)((0, _noble_hashes_blake3_js.blake3)(new TextEncoder().encode(canonical)));
7665
7372
  };
7666
7373
  //#endregion
@@ -7720,6 +7427,107 @@ const removeActiveLlmSpan = (span) => {
7720
7427
  /** Most recently pushed still-open LLM span, or undefined if none. */
7721
7428
  const peekActiveLlmSpan = () => openLlmSpans.length > 0 ? openLlmSpans[openLlmSpans.length - 1] : void 0;
7722
7429
  //#endregion
7430
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
7431
+ const serializeJSON$1 = (value) => {
7432
+ if (typeof value === "string") return value;
7433
+ try {
7434
+ return JSON.stringify(value);
7435
+ } catch {
7436
+ return "[unserializable]";
7437
+ }
7438
+ };
7439
+ const normalizeProvider$1 = (provider) => {
7440
+ if (!provider) return void 0;
7441
+ return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
7442
+ };
7443
+ const applyUsage = (attributes, usage) => {
7444
+ if (!usage || typeof usage !== "object") return;
7445
+ if (typeof usage.inputTokens === "number") attributes[require_dist.LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
7446
+ if (typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
7447
+ if (typeof usage.totalTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
7448
+ else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
7449
+ if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
7450
+ else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
7451
+ if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
7452
+ else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
7453
+ if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
7454
+ };
7455
+ const verbatimStandardizedMessages = (event) => {
7456
+ const messages = [];
7457
+ const sys = event.instructions || event.system;
7458
+ if (typeof sys === "string" && sys.length > 0) messages.push({
7459
+ role: "system",
7460
+ content: sys
7461
+ });
7462
+ else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
7463
+ else if (sys && typeof sys === "object") messages.push(sys);
7464
+ if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(m);
7465
+ return messages;
7466
+ };
7467
+ const buildVerbatimOutputMessages = (content) => {
7468
+ if (!Array.isArray(content) || content.length === 0) return void 0;
7469
+ try {
7470
+ return JSON.stringify([{
7471
+ role: "assistant",
7472
+ content
7473
+ }]);
7474
+ } catch {
7475
+ return;
7476
+ }
7477
+ };
7478
+ const buildTextOutputMessages = (text) => text.length > 0 ? buildVerbatimOutputMessages([{
7479
+ type: "text",
7480
+ text
7481
+ }]) : void 0;
7482
+ const applyUsageToSpan = (span, usage) => {
7483
+ const attrs = {};
7484
+ applyUsage(attrs, usage);
7485
+ for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
7486
+ };
7487
+ const applyFinishReason = (span, finishReason) => {
7488
+ span.setAttribute("gen_ai.response.finish_reason", finishReason);
7489
+ span.setAttribute("ai.response.finishReason", finishReason);
7490
+ };
7491
+ const applyRequestModelAttributes = (span, event) => {
7492
+ if (typeof event.provider === "string") {
7493
+ const provider = normalizeProvider$1(event.provider);
7494
+ if (provider) span.setAttribute(require_dist.LaminarAttributes.PROVIDER, provider);
7495
+ span.setAttribute("ai.model.provider", event.provider);
7496
+ }
7497
+ if (typeof event.modelId === "string") {
7498
+ span.setAttribute(require_dist.LaminarAttributes.REQUEST_MODEL, event.modelId);
7499
+ span.setAttribute("ai.model.id", event.modelId);
7500
+ }
7501
+ };
7502
+ const compareHrTime = (a, b) => {
7503
+ if (a[0] !== b[0]) return a[0] - b[0];
7504
+ return a[1] - b[1];
7505
+ };
7506
+ const readSpanEndTime = (span) => {
7507
+ const innerEnd = span._span?.endTime;
7508
+ if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
7509
+ const outerEnd = span.endTime;
7510
+ if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
7511
+ };
7512
+ const verbatimPromptString = (prompt) => {
7513
+ try {
7514
+ const serialized = stringifyPromptForTelemetry(prompt);
7515
+ return typeof serialized === "string" ? serialized : null;
7516
+ } catch {
7517
+ return null;
7518
+ }
7519
+ };
7520
+ const verbatimPromptMessages = (prompt) => {
7521
+ const serialized = verbatimPromptString(prompt);
7522
+ if (serialized === null) return null;
7523
+ try {
7524
+ const parsed = JSON.parse(serialized);
7525
+ return Array.isArray(parsed) ? parsed : null;
7526
+ } catch {
7527
+ return null;
7528
+ }
7529
+ };
7530
+ //#endregion
7723
7531
  //#region src/opentelemetry-lib/instrumentation/aisdk/base-language-model.ts
7724
7532
  /**
7725
7533
  * Base class for Laminar language model wrappers.
@@ -7762,7 +7570,7 @@ var BaseLaminarLanguageModel = class {
7762
7570
  async doGenerateOrStreamWithCaching(options, originalFn, buildFromCached) {
7763
7571
  if (!replayEnabled() || Laminar.debugRunLive) return originalFn(options);
7764
7572
  const spanToMark = peekActiveLlmSpan() ?? Laminar.getCurrentSpan();
7765
- const messages = extractInputMessages(options);
7573
+ const messages = verbatimPromptMessages(options.prompt);
7766
7574
  if (messages === null || messages.length === 0) return originalFn(options);
7767
7575
  const inputHash = debugInputHash(messages);
7768
7576
  const outcome = await getRuntime()?.lookupCache(inputHash) ?? { kind: "live" };
@@ -7810,17 +7618,45 @@ var BaseLaminarLanguageModel = class {
7810
7618
  }];
7811
7619
  if (output === null || typeof output !== "object") return [];
7812
7620
  const handleItem = (item) => {
7813
- if (item.type === "text") return [{
7814
- type: "text",
7815
- text: item.text ?? item.content ?? ""
7816
- }];
7817
- if (["tool-call", "tool_call"].includes(item.type)) return [{
7621
+ if (item.type === "text") {
7622
+ if (typeof item.text === "string") return [{
7623
+ ...item,
7624
+ type: "text",
7625
+ text: item.text
7626
+ }];
7627
+ return [{
7628
+ type: "text",
7629
+ text: item.content ?? ""
7630
+ }];
7631
+ }
7632
+ if (item.type === "tool-call") return [{
7633
+ ...item,
7818
7634
  type: "tool-call",
7819
7635
  toolCallId: item.toolCallId ?? item.id,
7820
7636
  toolName: item.toolName ?? item.name,
7821
- input: JSON.stringify(item.input ?? item.arguments)
7637
+ input: typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? item.arguments)
7822
7638
  }];
7823
- if (["reasoning", "thinking"].includes(item.type)) return [{
7639
+ if (item.type === "tool_call") {
7640
+ const rawInput = item.input ?? item.arguments;
7641
+ return [{
7642
+ type: "tool-call",
7643
+ toolCallId: item.toolCallId ?? item.id,
7644
+ toolName: item.toolName ?? item.name,
7645
+ input: typeof rawInput === "string" ? rawInput : JSON.stringify(rawInput)
7646
+ }];
7647
+ }
7648
+ if (item.type === "reasoning") {
7649
+ if (typeof item.text === "string") return [{
7650
+ ...item,
7651
+ type: "reasoning",
7652
+ text: item.text
7653
+ }];
7654
+ return [{
7655
+ type: "reasoning",
7656
+ text: item.content ?? ""
7657
+ }];
7658
+ }
7659
+ if (item.type === "thinking") return [{
7824
7660
  type: "reasoning",
7825
7661
  text: item.text ?? item.content ?? ""
7826
7662
  }];
@@ -7886,7 +7722,8 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
7886
7722
  const id = `text-${textIndex++}`;
7887
7723
  parts.push({
7888
7724
  type: "text-start",
7889
- id
7725
+ id,
7726
+ providerMetadata: block.providerMetadata
7890
7727
  });
7891
7728
  parts.push({
7892
7729
  type: "text-delta",
@@ -7914,16 +7751,15 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
7914
7751
  id
7915
7752
  });
7916
7753
  parts.push({
7917
- type: "tool-call",
7918
- toolCallId: block.toolCallId,
7919
- toolName: block.toolName,
7920
- input: block.input
7754
+ ...block,
7755
+ type: "tool-call"
7921
7756
  });
7922
7757
  } else if (block.type === "reasoning") {
7923
7758
  const id = `reasoning-${reasoningIndex++}`;
7924
7759
  parts.push({
7925
7760
  type: "reasoning-start",
7926
- id
7761
+ id,
7762
+ providerMetadata: block.providerMetadata
7927
7763
  });
7928
7764
  parts.push({
7929
7765
  type: "reasoning-delta",
@@ -7988,7 +7824,8 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
7988
7824
  const id = `text-${textIndex++}`;
7989
7825
  parts.push({
7990
7826
  type: "text-start",
7991
- id
7827
+ id,
7828
+ providerMetadata: block.providerMetadata
7992
7829
  });
7993
7830
  parts.push({
7994
7831
  type: "text-delta",
@@ -8016,16 +7853,15 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
8016
7853
  id
8017
7854
  });
8018
7855
  parts.push({
8019
- type: "tool-call",
8020
- toolCallId: block.toolCallId,
8021
- toolName: block.toolName,
8022
- input: block.input
7856
+ ...block,
7857
+ type: "tool-call"
8023
7858
  });
8024
7859
  } else if (block.type === "reasoning") {
8025
7860
  const id = `reasoning-${reasoningIndex++}`;
8026
7861
  parts.push({
8027
7862
  type: "reasoning-start",
8028
- id
7863
+ id,
7864
+ providerMetadata: block.providerMetadata
8029
7865
  });
8030
7866
  parts.push({
8031
7867
  type: "reasoning-delta",
@@ -8090,7 +7926,8 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
8090
7926
  const id = `text-${textIndex++}`;
8091
7927
  parts.push({
8092
7928
  type: "text-start",
8093
- id
7929
+ id,
7930
+ providerMetadata: block.providerMetadata
8094
7931
  });
8095
7932
  parts.push({
8096
7933
  type: "text-delta",
@@ -8118,16 +7955,15 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
8118
7955
  id
8119
7956
  });
8120
7957
  parts.push({
8121
- type: "tool-call",
8122
- toolCallId: block.toolCallId,
8123
- toolName: block.toolName,
8124
- input: block.input
7958
+ ...block,
7959
+ type: "tool-call"
8125
7960
  });
8126
7961
  } else if (block.type === "reasoning") {
8127
7962
  const id = `reasoning-${reasoningIndex++}`;
8128
7963
  parts.push({
8129
7964
  type: "reasoning-start",
8130
- id
7965
+ id,
7966
+ providerMetadata: block.providerMetadata
8131
7967
  });
8132
7968
  parts.push({
8133
7969
  type: "reasoning-delta",
@@ -8207,136 +8043,94 @@ function wrapLanguageModel(languageModel) {
8207
8043
  return new LaminarLanguageModelV2(languageModel);
8208
8044
  }
8209
8045
  //#endregion
8210
- //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
8211
- const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
8212
- //#endregion
8213
- //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
8214
- const serializeJSON$1 = (value) => {
8215
- if (typeof value === "string") return value;
8046
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/package-versions.ts
8047
+ let cached;
8048
+ const readPackageVersion = (specifier) => {
8216
8049
  try {
8217
- return JSON.stringify(value);
8050
+ const pkg = require(`${specifier}/package.json`);
8051
+ return typeof pkg?.version === "string" ? pkg.version : void 0;
8218
8052
  } catch {
8219
- return "[unserializable]";
8053
+ return;
8220
8054
  }
8221
8055
  };
8222
- const normalizeProvider$1 = (provider) => {
8223
- if (!provider) return void 0;
8224
- return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
8225
- };
8226
- const applyUsage = (attributes, usage) => {
8227
- if (!usage || typeof usage !== "object") return;
8228
- if (typeof usage.inputTokens === "number") attributes[require_dist.LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
8229
- if (typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
8230
- if (typeof usage.totalTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
8231
- else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[require_dist.LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
8232
- if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
8233
- else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
8234
- if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
8235
- else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
8236
- if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
8237
- };
8238
- const standardizedPromptToMessages = (event) => {
8239
- const messages = [];
8240
- const sys = event.instructions || event.system;
8241
- if (typeof sys === "string" && sys.length > 0) messages.push({
8242
- role: "system",
8243
- content: sys
8244
- });
8245
- else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
8246
- else if (sys && typeof sys === "object") messages.push(sys);
8247
- if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(convertRawStringTextToTextBlock(m));
8248
- return messages;
8249
- };
8250
- const convertRawStringTextToTextBlock = (m) => ({
8251
- ...m,
8252
- content: typeof m?.content === "string" ? [{
8253
- type: "text",
8254
- text: m.content
8255
- }] : m?.content
8256
- });
8257
- const normalizeToolCalls = (toolCalls) => {
8258
- if (!Array.isArray(toolCalls)) return [];
8259
- return toolCalls.map((tc) => ({
8260
- toolCallType: "function",
8261
- toolCallId: tc?.toolCallId,
8262
- toolName: tc?.toolName,
8263
- args: typeof tc?.input === "string" ? tc.input : serializeJSON$1(tc?.input ?? {})
8264
- }));
8265
- };
8266
- const buildGenAiOutputMessages = (args) => {
8267
- const parts = [];
8268
- if (args.reasoningText && args.reasoningText.length > 0) parts.push({
8269
- type: "thinking",
8270
- content: args.reasoningText
8271
- });
8272
- if (args.text && args.text.length > 0) parts.push({
8273
- type: "text",
8274
- content: args.text
8275
- });
8276
- for (const tc of args.toolCalls ?? []) {
8277
- let argsObj = tc.args;
8278
- if (typeof argsObj === "string") try {
8279
- argsObj = JSON.parse(argsObj);
8056
+ /**
8057
+ * A `node_modules` search-path entry is a package-manager-internal virtual
8058
+ * store (pnpm's `.pnpm`, yarn's `.yarn`, etc.) if any path segment starts
8059
+ * with `.` — excluding the `.`/`..` relative-path segments themselves, which
8060
+ * are unrelated. Packages resolved from inside such a store are some
8061
+ * dependency's OWN transitive deps (e.g. `ai`'s `@ai-sdk/provider`), not
8062
+ * what the host project declared, so those entries must be skipped.
8063
+ */
8064
+ const isPackageManagerInternalPath = (path) => path.split(node_path.sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
8065
+ /**
8066
+ * Given the ordered `node_modules` search-path list Node would use to
8067
+ * resolve a module from this file's location, find the closest directory
8068
+ * that actually has an `@ai-sdk` scope — skipping package-manager-internal
8069
+ * stores and return the packages listed under it. The first path with a
8070
+ * usable `@ai-sdk` directory wins; levels are not merged, so an unrelated
8071
+ * ancestor/monorepo-root's `@ai-sdk` scope is never mixed in once a closer
8072
+ * one has already matched.
8073
+ */
8074
+ const findAiSdkScopeEntries = (searchPaths) => {
8075
+ for (const path of searchPaths) {
8076
+ if (isPackageManagerInternalPath(path)) continue;
8077
+ try {
8078
+ const entries = (0, node_fs.readdirSync)((0, node_path.join)(path, "@ai-sdk"));
8079
+ if (entries.length > 0) return entries;
8280
8080
  } catch {}
8281
- parts.push({
8282
- type: "tool_call",
8283
- id: tc.toolCallId,
8284
- name: tc.toolName,
8285
- arguments: argsObj
8286
- });
8287
8081
  }
8288
- if (parts.length === 0) return void 0;
8289
- return JSON.stringify([{
8290
- role: "assistant",
8291
- parts
8292
- }]);
8293
- };
8294
- const extractTextFromContent = (content) => {
8295
- if (!Array.isArray(content)) return "";
8296
- return content.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
8297
- };
8298
- const extractReasoningFromContent = (content) => {
8299
- if (!Array.isArray(content)) return "";
8300
- return content.filter((p) => p && p.type === "reasoning" && typeof p.text === "string").map((p) => p.text).join("");
8301
- };
8302
- const applyUsageToSpan = (span, usage) => {
8303
- const attrs = {};
8304
- applyUsage(attrs, usage);
8305
- for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
8082
+ return [];
8306
8083
  };
8307
- const applyFinishReason = (span, finishReason) => {
8308
- span.setAttribute("gen_ai.response.finish_reason", finishReason);
8309
- span.setAttribute("ai.response.finishReason", finishReason);
8310
- };
8311
- const applyRequestModelAttributes = (span, event) => {
8312
- if (typeof event.provider === "string") {
8313
- const provider = normalizeProvider$1(event.provider);
8314
- if (provider) span.setAttribute(require_dist.LaminarAttributes.PROVIDER, provider);
8315
- span.setAttribute("ai.model.provider", event.provider);
8084
+ const discoverAiSdkPackages = () => {
8085
+ const packages = {};
8086
+ let searchPaths;
8087
+ try {
8088
+ searchPaths = require.resolve.paths("@ai-sdk/__probe__") ?? [];
8089
+ } catch {
8090
+ searchPaths = [];
8316
8091
  }
8317
- if (typeof event.modelId === "string") {
8318
- span.setAttribute(require_dist.LaminarAttributes.REQUEST_MODEL, event.modelId);
8319
- span.setAttribute("ai.model.id", event.modelId);
8092
+ for (const entry of findAiSdkScopeEntries(searchPaths)) {
8093
+ const specifier = `@ai-sdk/${entry}`;
8094
+ const version = readPackageVersion(specifier);
8095
+ if (version) packages[specifier] = version;
8320
8096
  }
8097
+ return packages;
8321
8098
  };
8322
- const applyPromptMessages = (span, event) => {
8323
- const msgs = standardizedPromptToMessages(event);
8324
- if (msgs.length === 0) return;
8325
- const serialized = serializeJSON$1(msgs);
8326
- span.setAttribute("ai.prompt.messages", serialized);
8327
- span.setAttribute(require_dist.SPAN_INPUT, serialized);
8328
- };
8329
- const compareHrTime = (a, b) => {
8330
- if (a[0] !== b[0]) return a[0] - b[0];
8331
- return a[1] - b[1];
8099
+ /**
8100
+ * Reads installed `ai` / `@ai-sdk/*` package versions once and caches the
8101
+ * result for the lifetime of the process — this is expected to be called on
8102
+ * every span creation, so the actual `require`/`readdirSync` work must not
8103
+ * repeat.
8104
+ */
8105
+ const getAiSdkPackageVersions = () => {
8106
+ if (cached) return cached;
8107
+ try {
8108
+ cached = {
8109
+ aiVersion: readPackageVersion("ai"),
8110
+ aiSdkPackages: discoverAiSdkPackages()
8111
+ };
8112
+ } catch {
8113
+ cached = { aiSdkPackages: {} };
8114
+ }
8115
+ return cached;
8332
8116
  };
8333
- const readSpanEndTime = (span) => {
8334
- const innerEnd = span._span?.endTime;
8335
- if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
8336
- const outerEnd = span.endTime;
8337
- if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
8117
+ /**
8118
+ * Flat span-attribute object for the `ai` package's instrumentation scope
8119
+ * plus one `lmnr.span.instrumentation.@ai-sdk/<pkg>.version` entry per
8120
+ * resolvable `@ai-sdk/*` package. Merge this into every span this
8121
+ * integration creates.
8122
+ */
8123
+ const buildAiSdkInstrumentationAttributes = () => {
8124
+ const { aiVersion, aiSdkPackages } = getAiSdkPackageVersions();
8125
+ const attributes = { [require_dist.SPAN_INSTRUMENTATION_SCOPE_NAME]: "ai" };
8126
+ if (aiVersion) attributes[require_dist.SPAN_INSTRUMENTATION_SCOPE_VERSION] = aiVersion;
8127
+ for (const [pkg, version] of Object.entries(aiSdkPackages)) attributes[`lmnr.span.instrumentation.${pkg}.version`] = version;
8128
+ return attributes;
8338
8129
  };
8339
8130
  //#endregion
8131
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
8132
+ const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
8133
+ //#endregion
8340
8134
  //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/index.ts
8341
8135
  /**
8342
8136
  * The AI SDK v7 diagnostics channel name. Every lifecycle event fires
@@ -8358,6 +8152,8 @@ var LaminarAiSdkTelemetry = class {
8358
8152
  this.llmByKey = /* @__PURE__ */ new Map();
8359
8153
  this.toolByCallId = /* @__PURE__ */ new Map();
8360
8154
  this.activeStreamStepByCallId = /* @__PURE__ */ new Map();
8155
+ this.promptByCallId = /* @__PURE__ */ new Map();
8156
+ this.logger = require_dist.initializeLogger();
8361
8157
  this.onStart = (event) => {
8362
8158
  const callId = event?.callId;
8363
8159
  if (!callId) return;
@@ -8369,6 +8165,7 @@ var LaminarAiSdkTelemetry = class {
8369
8165
  kind: _opentelemetry_api.SpanKind.CLIENT,
8370
8166
  attributes: {
8371
8167
  [require_dist.SPAN_TYPE]: "DEFAULT",
8168
+ ...buildAiSdkInstrumentationAttributes(),
8372
8169
  ...typeof event.functionId === "string" ? {
8373
8170
  "operation.name": event.functionId,
8374
8171
  "ai.prompt": "_"
@@ -8389,8 +8186,10 @@ var LaminarAiSdkTelemetry = class {
8389
8186
  if (typeof event.functionId === "string") span.setAttribute("ai.functionId", event.functionId);
8390
8187
  if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany" || event.operationId === "ai.rerank") span.setAttribute(require_dist.SPAN_TYPE, "LLM");
8391
8188
  if (this.recordInputs) {
8392
- if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) applyPromptMessages(span, event);
8393
- else if (event.value !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(event.value));
8189
+ if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) {
8190
+ const msgs = verbatimStandardizedMessages(event);
8191
+ if (msgs.length > 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(msgs));
8192
+ } else if (event.value !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(event.value));
8394
8193
  else if (event.query !== void 0 || event.documents !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1({
8395
8194
  query: event.query,
8396
8195
  documents: event.documents
@@ -8405,17 +8204,25 @@ var LaminarAiSdkTelemetry = class {
8405
8204
  });
8406
8205
  };
8407
8206
  this.onStepStart = (event) => {
8408
- if (!this.createStepSpan) return;
8409
8207
  const callId = event?.callId;
8410
8208
  const stepNumber = event?.stepNumber ?? 0;
8411
8209
  if (!callId) return;
8210
+ if (Array.isArray(event?.promptMessages)) {
8211
+ const serialized = verbatimPromptString(event.promptMessages);
8212
+ if (serialized !== null) this.promptByCallId.set(callId, serialized);
8213
+ }
8214
+ if (!this.createStepSpan) return;
8412
8215
  const op = this.operationByCallId.get(callId);
8413
8216
  if (!op) return;
8414
8217
  const span = getTracer().startSpan(`ai.step`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
8415
8218
  const spanCtx = _opentelemetry_api.trace.setSpan(op.ctx, span);
8416
8219
  span.setAttribute(require_dist.SPAN_TYPE, "DEFAULT");
8220
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8417
8221
  span.setAttribute("ai.step.number", stepNumber);
8418
- if (this.recordInputs) applyPromptMessages(span, event);
8222
+ if (this.recordInputs) {
8223
+ const msgs = verbatimStandardizedMessages(event);
8224
+ if (msgs.length > 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(msgs));
8225
+ }
8419
8226
  this.stepByKey.set(stepKey(callId, stepNumber), {
8420
8227
  span,
8421
8228
  ctx: spanCtx,
@@ -8431,8 +8238,17 @@ var LaminarAiSdkTelemetry = class {
8431
8238
  if (!parentCtx) return;
8432
8239
  const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
8433
8240
  span.setAttribute(require_dist.SPAN_TYPE, "LLM");
8241
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8434
8242
  applyRequestModelAttributes(span, event);
8435
- if (this.recordInputs) applyPromptMessages(span, event);
8243
+ if (this.recordInputs) {
8244
+ if (event.tools) try {
8245
+ span.setAttribute("gen_ai.tool.definitions", JSON.stringify(event.tools));
8246
+ } catch (e) {
8247
+ this.logger.debug(`Failed to record span tool definitions: ${require_dist.errorMessage(e)}`);
8248
+ }
8249
+ const serialized = this.promptByCallId.get(callId);
8250
+ if (serialized !== void 0) span.setAttribute("gen_ai.input.messages", serialized);
8251
+ }
8436
8252
  this.llmByKey.set(stepKey(callId, stepNumber), {
8437
8253
  span,
8438
8254
  textDeltas: []
@@ -8455,18 +8271,14 @@ var LaminarAiSdkTelemetry = class {
8455
8271
  if (typeof event.responseId === "string") span.setAttribute("gen_ai.response.id", event.responseId);
8456
8272
  applyUsageToSpan(span, event.usage);
8457
8273
  if (this.recordOutputs) {
8458
- const content = Array.isArray(event.content) ? event.content : void 0;
8459
- const outputMessages = buildGenAiOutputMessages({
8460
- text: extractTextFromContent(content),
8461
- reasoningText: extractReasoningFromContent(content),
8462
- toolCalls: normalizeToolCalls(content ? content.filter((p) => p && p.type === "tool-call") : [])
8463
- });
8274
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8464
8275
  if (outputMessages) span.setAttribute("gen_ai.output.messages", outputMessages);
8465
8276
  }
8466
8277
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
8467
8278
  span.end();
8468
8279
  removeActiveLlmSpan(span);
8469
8280
  this.llmByKey.delete(stepKey(callId, stepNumber));
8281
+ this.promptByCallId.delete(callId);
8470
8282
  };
8471
8283
  this.onChunk = (event) => {
8472
8284
  const chunk = event?.chunk;
@@ -8495,7 +8307,7 @@ var LaminarAiSdkTelemetry = class {
8495
8307
  const llm = this.llmByKey.get(key);
8496
8308
  if (llm) {
8497
8309
  if (this.recordOutputs && llm.textDeltas.length > 0) {
8498
- const outputMessages = buildGenAiOutputMessages({ text: llm.textDeltas.join("") });
8310
+ const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
8499
8311
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8500
8312
  }
8501
8313
  applyUsageToSpan(llm.span, event.usage);
@@ -8503,21 +8315,13 @@ var LaminarAiSdkTelemetry = class {
8503
8315
  removeActiveLlmSpan(llm.span);
8504
8316
  this.llmByKey.delete(key);
8505
8317
  }
8318
+ this.promptByCallId.delete(callId);
8506
8319
  const step = this.stepByKey.get(key);
8507
8320
  if (!step) return;
8508
8321
  if (typeof event.finishReason === "string") applyFinishReason(step.span, event.finishReason);
8509
8322
  if (this.recordOutputs) {
8510
- const outputPayload = {};
8511
- if (typeof event.text === "string" && event.text.length > 0) {
8512
- outputPayload.text = event.text;
8513
- step.span.setAttribute("ai.response.text", event.text);
8514
- }
8515
- const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
8516
- if (normalizedToolCallsList.length > 0) {
8517
- outputPayload.toolCalls = normalizedToolCallsList;
8518
- step.span.setAttribute("ai.response.toolCalls", serializeJSON$1(normalizedToolCallsList));
8519
- }
8520
- if (Object.keys(outputPayload).length > 0) step.span.setAttribute(require_dist.SPAN_OUTPUT, serializeJSON$1(outputPayload));
8323
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8324
+ if (outputMessages) step.span.setAttribute(require_dist.SPAN_OUTPUT, outputMessages);
8521
8325
  }
8522
8326
  step.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
8523
8327
  step.span.end();
@@ -8530,7 +8334,12 @@ var LaminarAiSdkTelemetry = class {
8530
8334
  if (!op) return;
8531
8335
  const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
8532
8336
  span.setAttribute(require_dist.SPAN_TYPE, "LLM");
8337
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8533
8338
  applyRequestModelAttributes(span, event);
8339
+ if (this.recordInputs && Array.isArray(event?.promptMessages)) {
8340
+ const serialized = verbatimPromptString(event.promptMessages);
8341
+ if (serialized !== null) span.setAttribute("gen_ai.input.messages", serialized);
8342
+ }
8534
8343
  this.llmByKey.set(stepKey(callId, 0), {
8535
8344
  span,
8536
8345
  textDeltas: []
@@ -8550,7 +8359,7 @@ var LaminarAiSdkTelemetry = class {
8550
8359
  }
8551
8360
  applyUsageToSpan(llm.span, event.usage);
8552
8361
  if (this.recordOutputs && typeof event.objectText === "string") {
8553
- const outputMessages = buildGenAiOutputMessages({ text: event.objectText });
8362
+ const outputMessages = buildTextOutputMessages(event.objectText);
8554
8363
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8555
8364
  }
8556
8365
  llm.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
@@ -8569,6 +8378,7 @@ var LaminarAiSdkTelemetry = class {
8569
8378
  const span = getTracer().startSpan(`ai.tool ${toolName ?? "unknown"}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
8570
8379
  const spanCtx = _opentelemetry_api.trace.setSpan(parentCtx, span);
8571
8380
  span.setAttribute(require_dist.SPAN_TYPE, "TOOL");
8381
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8572
8382
  if (toolName) span.setAttribute("ai.toolCall.name", toolName);
8573
8383
  span.setAttribute("ai.toolCall.id", toolCallId);
8574
8384
  if (this.recordInputs && toolCall?.input !== void 0) {
@@ -8646,17 +8456,8 @@ var LaminarAiSdkTelemetry = class {
8646
8456
  if (typeof event.finishReason === "string") applyFinishReason(op.span, event.finishReason);
8647
8457
  if (op.operationId === "ai.embed" || op.operationId === "ai.embedMany" || op.operationId === "ai.rerank") applyUsageToSpan(op.span, event.totalUsage ?? event.usage);
8648
8458
  if (this.recordOutputs) {
8649
- const hasText = typeof event.text === "string" && event.text.length > 0;
8650
- if (hasText) {
8651
- op.span.setAttribute("ai.response.text", event.text);
8652
- op.span.setAttribute(require_dist.SPAN_OUTPUT, event.text);
8653
- }
8654
- const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
8655
- if (normalizedToolCallsList.length > 0) {
8656
- const serialized = serializeJSON$1(normalizedToolCallsList);
8657
- op.span.setAttribute("ai.response.toolCalls", serialized);
8658
- if (!hasText) op.span.setAttribute(require_dist.SPAN_OUTPUT, serialized);
8659
- }
8459
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8460
+ if (outputMessages) op.span.setAttribute(require_dist.SPAN_OUTPUT, outputMessages);
8660
8461
  if (event.embedding !== void 0) {
8661
8462
  const emb = event.embedding;
8662
8463
  if (Array.isArray(emb) && Array.isArray(emb[0])) op.span.setAttribute("ai.embed.count", emb.length);
@@ -8760,7 +8561,7 @@ var LaminarAiSdkTelemetry = class {
8760
8561
  };
8761
8562
  for (const [key, llm] of this.llmByKey) if (key.startsWith(prefix)) {
8762
8563
  if (this.recordOutputs && llm.textDeltas.length > 0) {
8763
- const outputMessages = buildGenAiOutputMessages({ text: llm.textDeltas.join("") });
8564
+ const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
8764
8565
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8765
8566
  }
8766
8567
  llm.span.end();
@@ -8778,6 +8579,7 @@ var LaminarAiSdkTelemetry = class {
8778
8579
  for (const key of this.llmByKey.keys()) if (key.startsWith(prefix)) this.llmByKey.delete(key);
8779
8580
  for (const [toolCallId, tool] of this.toolByCallId) if (tool.callId === callId) this.toolByCallId.delete(toolCallId);
8780
8581
  for (const key of this.stepByKey.keys()) if (key.startsWith(prefix)) this.stepByKey.delete(key);
8582
+ this.promptByCallId.delete(callId);
8781
8583
  return latest;
8782
8584
  }
8783
8585
  };