@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as __require } from "./rolldown-runtime-2rV9d50f.mjs";
2
- import { A as SPAN_OUTPUT, B as TracingLevel, C as PARENT_SPAN_IDS_PATH, D as SPAN_INPUT, E as SPAN_IDS_PATH, F as TRACE_TYPE, I as USER_ID, L as version, M as SPAN_SDK_VERSION, N as SPAN_TYPE, O as SPAN_INSTRUMENTATION_SOURCE, P as TRACE_HAS_BROWSER_SESSION, R as DEBUG_SESSION_DIR, S as LaminarAttributes, T as SESSION_ID, V as errorMessage, _ as tryToOtelSpanContext, b as ASSOCIATION_PROPERTIES_OVERRIDES, c as initializeLogger, d as newUUID, f as normalizeOtelSpanId, g as parseOtelHeaders, h as otelTraceIdToUUID, i as deserializeLaminarSpanContext, j as SPAN_PATH, k as SPAN_LANGUAGE_VERSION, l as loadEnv, m as otelSpanIdToUUID, n as NIL_UUID, o as getFrontendUrl, p as normalizeOtelTraceId, r as Semaphore, s as getOtelEnvVar, t as LaminarClient, u as metadataToAttributes, v as validateTracingConfig, w as PARENT_SPAN_PATH, x as HUMAN_EVALUATOR_OPTIONS, y as ASSOCIATION_PROPERTIES, z as DEBUG_SESSION_FILE } from "./dist-CKEmP0bk.mjs";
2
+ import { A as SPAN_INSTRUMENTATION_SOURCE, B as DEBUG_SESSION_DIR, C as PARENT_SPAN_IDS_PATH, D as SPAN_INPUT, E as SPAN_IDS_PATH, F as SPAN_TYPE, H as TracingLevel, I as TRACE_HAS_BROWSER_SESSION, L as TRACE_TYPE, M as SPAN_OUTPUT, N as SPAN_PATH, O as SPAN_INSTRUMENTATION_SCOPE_NAME, P as SPAN_SDK_VERSION, R as USER_ID, S as LaminarAttributes, T as SESSION_ID, U as errorMessage, V as DEBUG_SESSION_FILE, _ as tryToOtelSpanContext, b as ASSOCIATION_PROPERTIES_OVERRIDES, c as initializeLogger, d as newUUID, f as normalizeOtelSpanId, g as parseOtelHeaders, h as otelTraceIdToUUID, i as deserializeLaminarSpanContext, j as SPAN_LANGUAGE_VERSION, k as SPAN_INSTRUMENTATION_SCOPE_VERSION, l as loadEnv, m as otelSpanIdToUUID, n as NIL_UUID, o as getFrontendUrl, p as normalizeOtelTraceId, r as Semaphore, s as getOtelEnvVar, t as LaminarClient, u as metadataToAttributes, v as validateTracingConfig, w as PARENT_SPAN_PATH, x as HUMAN_EVALUATOR_OPTIONS, y as ASSOCIATION_PROPERTIES, z as version } from "./dist-DVpPVAC5.mjs";
3
3
  import { n as TRACEPARENT_HEADER, t as LAMINAR_SPAN_CONTEXT_HEADER } from "./consts-3AfXaMBI.mjs";
4
4
  import { DiagConsoleLogger, DiagLogLevel, ROOT_CONTEXT, SpanKind, SpanStatusCode, TraceFlags, context, createContextKey, diag, isSpanContextValid, trace } from "@opentelemetry/api";
5
5
  import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
@@ -17,11 +17,10 @@ import { OpenAIInstrumentation } from "@traceloop/instrumentation-openai";
17
17
  import { PineconeInstrumentation } from "@traceloop/instrumentation-pinecone";
18
18
  import { QdrantInstrumentation } from "@traceloop/instrumentation-qdrant";
19
19
  import { TogetherInstrumentation } from "@traceloop/instrumentation-together";
20
- import { AIPlatformInstrumentation, VertexAIInstrumentation } from "@traceloop/instrumentation-vertexai";
21
20
  import { spawn } from "node:child_process";
22
21
  import { randomUUID } from "node:crypto";
23
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
24
- import { dirname, join, parse, resolve } from "node:path";
22
+ import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
23
+ import { dirname, join, parse, resolve, sep } from "node:path";
25
24
  import * as fs from "fs";
26
25
  import * as net from "net";
27
26
  import * as os from "os";
@@ -88,7 +87,7 @@ var LaminarDataset = class extends EvaluationDataset {
88
87
  */
89
88
  async push(paths, recursive = false) {
90
89
  if (!this.client) throw new Error("Client not set");
91
- const { loadFromPaths } = await import("./file-utils-CceRl1Dm.mjs").then((n) => n.t);
90
+ const { loadFromPaths } = await import("./file-utils-D3Jj4yAt.mjs").then((n) => n.t);
92
91
  const data = await loadFromPaths(Array.isArray(paths) ? paths : [paths], recursive);
93
92
  if (data.length === 0) {
94
93
  console.warn("No data to push. Skipping");
@@ -160,7 +159,10 @@ const stringifyPromptForTelemetry = (prompt) => JSON.stringify(prompt.map((messa
160
159
  ...message,
161
160
  content: typeof message.content === "string" ? message.content : message.content.map((part) => part.type === "file" ? {
162
161
  ...part,
163
- data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data
162
+ data: part.data instanceof Uint8Array ? convertDataContentToBase64String(part.data) : part.data && typeof part.data === "object" && part.data.type === "data" && part.data.data instanceof Uint8Array ? {
163
+ ...part.data,
164
+ data: convertDataContentToBase64String(part.data.data)
165
+ } : part.data
164
166
  } : part)
165
167
  })));
166
168
  /**
@@ -597,28 +599,6 @@ const parseCacheUntil = (value) => {
597
599
  * the SDK only needs both ids present to arm replay.
598
600
  */
599
601
  const replayEnabledForConfig = (config) => config.replayTraceId !== null && config.cacheUntilSpanId !== null;
600
- const ROLLOUT_NOTE_KEY = "rollout.note";
601
- /**
602
- * Resolve the debug run note from the environment.
603
- *
604
- * Only read when debug mode is enabled (LMNR_DEBUG truthy). `LMNR_DEBUG_RUN_NOTES_FILE`
605
- * (a path to a file holding raw markdown) takes precedence over `LMNR_DEBUG_RUN_NOTES`
606
- * (inline raw markdown). A missing / unreadable file is logged and skipped (returns
607
- * null) rather than throwing — a bad note path must never block SDK init.
608
- *
609
- * Returns the raw note string, or null when debug is off or no note var is set.
610
- */
611
- const resolveDebugRunNote = () => {
612
- if (!isTruthy(process.env.LMNR_DEBUG)) return null;
613
- const file = process.env.LMNR_DEBUG_RUN_NOTES_FILE;
614
- if (file) try {
615
- return readFileSync(file, "utf-8");
616
- } catch (err) {
617
- logger$22.warn(`LMNR_DEBUG_RUN_NOTES_FILE=${JSON.stringify(file)} could not be read; ignoring run note: ${errorMessage(err)}`);
618
- return null;
619
- }
620
- return process.env.LMNR_DEBUG_RUN_NOTES ?? null;
621
- };
622
602
  /**
623
603
  * Build the debug config from the environment.
624
604
  *
@@ -1834,8 +1814,6 @@ var Laminar = class Laminar {
1834
1814
  this.baseHttpUrl = `${httpUrlWithoutSlash}:${port}`;
1835
1815
  } else this.baseHttpUrl = "";
1836
1816
  const envMetadata = process.env.LMNR_TRACE_METADATA ? JSON.parse(process.env.LMNR_TRACE_METADATA) : {};
1837
- const debugRunNote = resolveDebugRunNote();
1838
- if (debugRunNote !== null) envMetadata[ROLLOUT_NOTE_KEY] = debugRunNote;
1839
1817
  this.globalMetadata = {
1840
1818
  ...envMetadata,
1841
1819
  ...metadata ?? {}
@@ -6255,8 +6233,6 @@ const initInstrumentations = (client, suppressContentTracing, sessionRecordingOp
6255
6233
  }));
6256
6234
  instrumentations.push(new AnthropicInstrumentation({ traceContent: !suppressContentTracing }));
6257
6235
  instrumentations.push(new CohereInstrumentation({ traceContent: !suppressContentTracing }));
6258
- instrumentations.push(new VertexAIInstrumentation({ traceContent: !suppressContentTracing }));
6259
- instrumentations.push(new AIPlatformInstrumentation({ traceContent: !suppressContentTracing }));
6260
6236
  instrumentations.push(new BedrockInstrumentation({ traceContent: !suppressContentTracing }));
6261
6237
  instrumentations.push(new PineconeInstrumentation());
6262
6238
  instrumentations.push(new LangChainInstrumentation({ traceContent: !suppressContentTracing }));
@@ -6308,16 +6284,6 @@ const manuallyInitInstrumentations = (client, instrumentModules, suppressContent
6308
6284
  instrumentations.push(cohereInstrumentation);
6309
6285
  cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
6310
6286
  }
6311
- if (instrumentModules?.google_vertexai) {
6312
- const vertexaiInstrumentation = new VertexAIInstrumentation({ traceContent: !suppressContentTracing });
6313
- instrumentations.push(vertexaiInstrumentation);
6314
- vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
6315
- }
6316
- if (instrumentModules?.google_aiplatform) {
6317
- const aiplatformInstrumentation = new AIPlatformInstrumentation({ traceContent: !suppressContentTracing });
6318
- instrumentations.push(aiplatformInstrumentation);
6319
- aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
6320
- }
6321
6287
  if (instrumentModules?.bedrock) {
6322
6288
  const bedrockInstrumentation = new BedrockInstrumentation({ traceContent: !suppressContentTracing });
6323
6289
  instrumentations.push(bedrockInstrumentation);
@@ -7006,6 +6972,29 @@ const logger$2 = initializeLogger();
7006
6972
  const getEvaluationUrl = (projectId, evaluationId, baseUrl, frontendPort) => {
7007
6973
  return `${getFrontendUrl(baseUrl, frontendPort)}/project/${projectId}/evaluations/${evaluationId}`;
7008
6974
  };
6975
+ const SESSION_METADATA_KEY = "rollout.session_id";
6976
+ /**
6977
+ * When this eval runs under a debug session, auto-stamp the session id into the
6978
+ * evaluation metadata so the created evaluation links back to it with no extra
6979
+ * step. The session id is resolved by the debug runtime EXACTLY like traces —
6980
+ * `LMNR_DEBUG_SESSION_ID` env → `.lmnr/debug-session.json` → freshly minted — so
6981
+ * a plain `LMNR_DEBUG=1 <run-your-eval>` groups the eval under the current debug
6982
+ * session (no CLI wrapper needed). `getRuntime()` is null when debug mode is off,
6983
+ * so the metadata is returned unchanged.
6984
+ *
6985
+ * The backend writes the `evaluation` block from this key at eval creation; notes
6986
+ * are attached separately as `text` blocks keyed by the same session id
6987
+ * (`lmnr-cli debug session add-note`). Called after `Laminar.initialize()`, so
6988
+ * the runtime (and its resolved session id) already exist.
6989
+ */
6990
+ const withSessionMetadata = (metadata) => {
6991
+ const sessionId = getRuntime()?.sessionId ?? null;
6992
+ if (sessionId === null) return metadata;
6993
+ return {
6994
+ ...metadata ?? {},
6995
+ [SESSION_METADATA_KEY]: sessionId
6996
+ };
6997
+ };
7009
6998
  const getAverageScores = (results) => {
7010
6999
  const perScoreValues = {};
7011
7000
  for (const result of results) for (const key in result.scores) {
@@ -7126,7 +7115,7 @@ var Evaluation = class {
7126
7115
  }
7127
7116
  let resultDatapoints;
7128
7117
  try {
7129
- const evaluation = await this.client.evals.init(this.name, this.groupName, this.metadata);
7118
+ const evaluation = await this.client.evals.init(this.name, this.groupName, withSessionMetadata(this.metadata));
7130
7119
  const url = getEvaluationUrl(evaluation.projectId, evaluation.id, this.progressReporter.baseUrl, this.frontendPort);
7131
7120
  process.stdout.write(`\nCheck results at ${url}\n`);
7132
7121
  this.progressReporter.start({ length: await this.getLength() });
@@ -7319,260 +7308,6 @@ async function evaluate({ data, executor, evaluators, groupName, name, metadata,
7319
7308
  else return await evaluation.run();
7320
7309
  }
7321
7310
  //#endregion
7322
- //#region src/debug/aisdk-normalize.ts
7323
- const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
7324
- /** Mirror Rust `is_url`. */
7325
- const isUrl = (data) => data.startsWith("http://") || data.startsWith("https://") || data.startsWith("data:");
7326
- /** Mirror Rust `infer_image_type`. */
7327
- const inferImageType = (base64) => {
7328
- if (base64.startsWith("/9j/")) return "image/jpeg";
7329
- if (base64.startsWith("iVBORw0KGgo")) return "image/png";
7330
- if (base64.startsWith("R0lGODlh")) return "image/gif";
7331
- if (base64.startsWith("UklGR")) return "image/webp";
7332
- if (base64.startsWith("PHN2Zz")) return "image/svg+xml";
7333
- return "image/png";
7334
- };
7335
- const base64FromBytes = (bytes) => {
7336
- let latin1 = "";
7337
- for (const b of bytes) latin1 += String.fromCharCode(b & 255);
7338
- return btoa(latin1);
7339
- };
7340
- /**
7341
- * Mirror Rust `json_value_to_string`: a JSON string stays itself; anything else
7342
- * is compact-serialized. With serde `preserve_order` on, `JSON.stringify` matches
7343
- * `serde_json::Value::to_string()` byte-for-byte for object key order.
7344
- */
7345
- const jsonValueToString = (value) => typeof value === "string" ? value : JSON.stringify(value);
7346
- /** First string among the candidates (ports serde field aliases). */
7347
- const firstString = (...candidates) => {
7348
- for (const c of candidates) if (typeof c === "string") return c;
7349
- };
7350
- /**
7351
- * Port of `InstrumentationChatMessageImage` (untagged) →
7352
- * `from_instrumentation_content_part`'s `Image` arm. Returns the output
7353
- * `ChatMessageContentPart` object, or `null` if no untagged variant matches.
7354
- */
7355
- const parseImage = (e) => {
7356
- if (isRecord(e.source)) {
7357
- const src = e.source;
7358
- if (typeof src.media_type === "string" && typeof src.data === "string") return {
7359
- type: "image",
7360
- mediaType: src.media_type,
7361
- data: src.data
7362
- };
7363
- if (typeof src.url === "string") return {
7364
- type: "image_url",
7365
- url: src.url,
7366
- detail: typeof src.detail === "string" ? src.detail : null
7367
- };
7368
- }
7369
- if (Array.isArray(e.image) && e.image.every((n) => typeof n === "number")) {
7370
- const b64 = base64FromBytes(e.image);
7371
- return {
7372
- type: "image_url",
7373
- url: `data:${typeof e.mime_type === "string" ? e.mime_type : inferImageType(b64)};base64,${b64}`,
7374
- detail: null
7375
- };
7376
- }
7377
- if (typeof e.image === "string") {
7378
- if (isUrl(e.image)) return {
7379
- type: "image_url",
7380
- url: e.image,
7381
- detail: null
7382
- };
7383
- return {
7384
- type: "image",
7385
- data: e.image,
7386
- mediaType: typeof e.mime_type === "string" ? e.mime_type : "image/png"
7387
- };
7388
- }
7389
- return null;
7390
- };
7391
- /** Port of the `Document` arm (only the Base64 source shape exists). */
7392
- const parseDocument = (e) => {
7393
- if (!isRecord(e.source) || e.source.type !== "base64") return null;
7394
- const data = e.source.data;
7395
- if (typeof data !== "string") return null;
7396
- return {
7397
- type: "document",
7398
- source: {
7399
- type: "base64",
7400
- media_type: firstString(e.source.media_type, e.source.mediaType) ?? "application/octet-stream",
7401
- data
7402
- }
7403
- };
7404
- };
7405
- const DATA_URL_RE = /^data:((?:application|image)\/[a-zA-Z-]+);base64,/;
7406
- /**
7407
- * Port of the `File` arm (OpenAI file-id / base64, AI SDK v1/v2).
7408
- *
7409
- * Known parity gap (v7 + image-by-URL only): on AI SDK v7 the recording path
7410
- * stores `ai.prompt.messages` from the user-facing `ModelMessage[]` (an image
7411
- * passed by URL is a `{type:"image", image:"https://…"}` part), which the
7412
- * server reshapes via `parseImage` into an `image_url`. Replay instead hashes
7413
- * the LanguageModel-level prompt, where that same image is already a
7414
- * `{type:"file", data:"https://…", mediaType:"image/*"}` part — this arm maps
7415
- * it to `image`, not `image_url`, so the two hashes diverge and the lookup
7416
- * MISSes (degrading safely to a live call). This can't be reconciled here: the
7417
- * arm is a faithful port of the server's Rust `parseFile`, so on v6 (where the
7418
- * server itself reshapes a file part) emitting `image_url` would instead BREAK
7419
- * parity, and the replay wrapper has no signal for which SDK version recorded
7420
- * the span. Plain text, multi-part text, tool calls, and base64 images all hash
7421
- * identically across both paths; only URL-referenced images on v7 are affected.
7422
- */
7423
- const parseFile = (e) => {
7424
- if (isRecord(e.file)) {
7425
- const file = e.file;
7426
- if (typeof file.file_id === "string") return {
7427
- type: "text",
7428
- text: JSON.stringify({ file_id: file.file_id })
7429
- };
7430
- if (typeof file.file_data === "string") {
7431
- const m = DATA_URL_RE.exec(file.file_data.slice(0, 50));
7432
- return {
7433
- type: "document",
7434
- source: {
7435
- type: "base64",
7436
- media_type: m ? m[1] : "application/octet-stream",
7437
- data: m ? file.file_data.slice(file.file_data.indexOf(",") + 1) : file.file_data
7438
- }
7439
- };
7440
- }
7441
- return null;
7442
- }
7443
- if (typeof e.data === "string") {
7444
- const mediaType = firstString(e.media_type, e.mediaType, e.mime_type);
7445
- if (mediaType === void 0) return null;
7446
- if (mediaType.startsWith("image/")) return {
7447
- type: "image",
7448
- data: e.data,
7449
- mediaType
7450
- };
7451
- return {
7452
- type: "document",
7453
- source: {
7454
- type: "base64",
7455
- media_type: mediaType,
7456
- data: e.data
7457
- }
7458
- };
7459
- }
7460
- return null;
7461
- };
7462
- /**
7463
- * Port of `serde_json::from_value::<InstrumentationChatMessageContentPart>` for a
7464
- * single part, returning the mapped output `ChatMessageContentPart` object or
7465
- * `null` when no variant matches (an unrecognized `type`, or a recognized tag
7466
- * whose inner shape fails). `null` propagates to an all-or-nothing Text fallback.
7467
- *
7468
- * Output objects carry serde's serialized field names; `skip_serializing_if` is
7469
- * reproduced by omitting the field (ChatMessage.tool_call_id only), and `Option`
7470
- * fields WITHOUT skip (`detail`, tool-call `id`/`arguments`) always emit `null`.
7471
- */
7472
- const parsePart = (el) => {
7473
- if (!isRecord(el)) return null;
7474
- switch (el.type) {
7475
- case "text": {
7476
- const text = firstString(el.text, el.input_text, el.output_text);
7477
- return text === void 0 ? null : {
7478
- type: "text",
7479
- text
7480
- };
7481
- }
7482
- case "image_url": {
7483
- if (!isRecord(el.image_url) || typeof el.image_url.url !== "string") return null;
7484
- const detail = el.image_url.detail;
7485
- return {
7486
- type: "image_url",
7487
- url: el.image_url.url,
7488
- detail: typeof detail === "string" ? detail : null
7489
- };
7490
- }
7491
- case "image": return parseImage(el);
7492
- case "document": return parseDocument(el);
7493
- case "file": return parseFile(el);
7494
- case "tool-call": {
7495
- if (typeof el.toolName !== "string") return null;
7496
- const rawInput = "input" in el ? el.input : el.args;
7497
- return {
7498
- type: "tool_call",
7499
- name: el.toolName,
7500
- id: typeof el.toolCallId === "string" ? el.toolCallId : null,
7501
- arguments: rawInput === void 0 || rawInput === null ? null : rawInput
7502
- };
7503
- }
7504
- case "tool-result":
7505
- if (typeof el.toolCallId !== "string" || typeof el.toolName !== "string") return null;
7506
- if (!("output" in el || "result" in el)) return null;
7507
- return {
7508
- type: "tool-result",
7509
- toolCallId: el.toolCallId,
7510
- output: "output" in el ? el.output : el.result,
7511
- toolName: el.toolName
7512
- };
7513
- default: return null;
7514
- }
7515
- };
7516
- /**
7517
- * Port of `input_chat_messages_from_json`. `input` is the parsed
7518
- * `ai.prompt.messages` array. Each message's `content` becomes either a
7519
- * `ContentPartList` (when EVERY element parses as an instrumentation part) or a
7520
- * Text blob (`json_value_to_string` of the whole content) otherwise. A message
7521
- * missing `role`/`content` would be a server error; we skip it defensively so the
7522
- * hash is still computed over the well-formed remainder.
7523
- */
7524
- const inputChatMessagesFromJson = (input) => {
7525
- if (!Array.isArray(input)) return [];
7526
- const messages = [];
7527
- for (const message of input) {
7528
- if (!isRecord(message)) continue;
7529
- const role = message.role;
7530
- if (typeof role !== "string" || !("content" in message)) continue;
7531
- const otelContent = message.content;
7532
- let parts = null;
7533
- if (Array.isArray(otelContent)) {
7534
- parts = [];
7535
- for (const el of otelContent) {
7536
- const parsed = parsePart(el);
7537
- if (parsed === null) {
7538
- parts = null;
7539
- break;
7540
- }
7541
- parts.push(parsed);
7542
- }
7543
- }
7544
- const out = {
7545
- role,
7546
- content: parts !== null ? parts : jsonValueToString(otelContent)
7547
- };
7548
- if (typeof message.tool_call_id === "string") out.tool_call_id = message.tool_call_id;
7549
- messages.push(out);
7550
- }
7551
- return messages;
7552
- };
7553
- /**
7554
- * Reshape an AI SDK call's `options.prompt` into the reconstructed message array
7555
- * the server hashes. Version-agnostic: `stringifyPromptForTelemetry` already
7556
- * normalizes V2/V3/V4 prompts (Uint8Array file `data` → base64) into the stored
7557
- * `ai.prompt.messages` JSON, and the reshape operates on that JSON identically
7558
- * across versions.
7559
- *
7560
- * Returns `null` when the prompt cannot be reshaped at all (a `stringify` /
7561
- * `JSON.parse` failure). The caller MUST treat `null` as "can't compute a real
7562
- * key" and degrade to a live call WITHOUT latching: hashing a default/empty
7563
- * payload here would key the lookup off the wrong bytes, force a spurious MISS,
7564
- * and wrongly latch live mode for the rest of the replay.
7565
- */
7566
- const extractInputMessages = (options) => {
7567
- let aiPromptMessages;
7568
- try {
7569
- aiPromptMessages = JSON.parse(stringifyPromptForTelemetry(options.prompt));
7570
- } catch {
7571
- return null;
7572
- }
7573
- return inputChatMessagesFromJson(aiPromptMessages);
7574
- };
7575
- //#endregion
7576
7311
  //#region src/debug/hash.ts
7577
7312
  /**
7578
7313
  * Debug-replay input hashing.
@@ -7580,13 +7315,13 @@ const extractInputMessages = (options) => {
7580
7315
  * Computes the per-LLM-call `input_hash` the server-side debugger cache (v2) is
7581
7316
  * keyed by. The SDK and the Rust app-server MUST produce byte-identical hashes,
7582
7317
  * so this is a faithful port of the app-server primitives:
7583
- * - `canonical_json` — `app-server/src/traces/input_dedup.rs`
7584
- * - `extract_system_message`— `app-server/src/traces/prompt_hash.rs`
7585
- * - `debug_input_hash` — `app-server/src/traces/debug_cache.rs` (plan 01 §3)
7318
+ * - `canonical_json` — `app-server/src/traces/input_dedup.rs`
7319
+ * - `debug_input_hash` — `app-server/src/traces/input_dedup.rs`
7586
7320
  *
7587
- * The whole non-system message array is hashed as ONE blob: strip the first
7588
- * `role === "system"` message (when its text is non-empty), canonicalize the
7589
- * remaining array, then blake3 over its UTF-8 bytes and hex-encode the digest.
7321
+ * The whole non-system message array is hashed as ONE blob: strip EVERY
7322
+ * `role === "system"` message (regardless of content or position), canonicalize
7323
+ * the remaining array, then blake3 over its UTF-8 bytes and hex-encode the
7324
+ * digest.
7590
7325
  *
7591
7326
  * Keep this file line-comparable with the Python `debug/hash.py`.
7592
7327
  */
@@ -7607,53 +7342,25 @@ const canonicalJson = (value) => {
7607
7342
  return JSON.stringify(value);
7608
7343
  };
7609
7344
  /**
7610
- * Extract the system prompt text from a system-role message, or "" when none.
7611
- *
7612
- * Faithful port of Rust `extract_system_message`'s text resolution: a plain
7613
- * string content, else an array of `{text}` blocks joined by a space (only when
7614
- * the join is non-empty), else `parts[0].text` / `parts[0].content`.
7615
- */
7616
- const systemMessageText = (sysMsg) => {
7617
- const content = sysMsg.content;
7618
- if (typeof content === "string") return content;
7619
- if (Array.isArray(content)) {
7620
- const joined = content.map((block) => block !== null && typeof block === "object" ? block.text : void 0).filter((t) => typeof t === "string").join(" ");
7621
- if (joined.length > 0) return joined;
7622
- }
7623
- const parts = sysMsg.parts;
7624
- if (Array.isArray(parts) && parts.length > 0) {
7625
- const first = parts[0];
7626
- if (first !== null && typeof first === "object") {
7627
- const f = first;
7628
- const t = typeof f.text === "string" ? f.text : f.content;
7629
- if (typeof t === "string") return t;
7630
- }
7631
- }
7632
- return "";
7633
- };
7634
- /**
7635
- * Drop the first `role === "system"` message, matching the server.
7345
+ * Drop EVERY `role === "system"` message, matching the server.
7636
7346
  *
7637
- * Returns the array unchanged when there is no system message OR when the system
7638
- * message's resolved text is empty (Rust `extract_system_message` returns `None`
7639
- * in both cases, leaving the system message in the hashed array).
7347
+ * Content and position are irrelevant: an empty system string, a block-array
7348
+ * system, or repeated/trailing system messages are all stripped. The coding
7349
+ * agent may add/remove/edit its own system prompt(s) between iterations and
7350
+ * those edits must not change the cache key.
7640
7351
  */
7641
- const stripSystemMessage = (messages) => {
7642
- const sysIdx = messages.findIndex((m) => m !== null && typeof m === "object" && m.role === "system");
7643
- if (sysIdx === -1) return messages;
7644
- if (systemMessageText(messages[sysIdx]).length === 0) return messages;
7645
- return messages.filter((_, i) => i !== sysIdx);
7646
- };
7352
+ const stripSystemMessages = (messages) => messages.filter((m) => !(m !== null && typeof m === "object" && m.role === "system"));
7647
7353
  /**
7648
- * Compute the hex blake3 `input_hash` for a reconstructed message array.
7354
+ * Compute the hex blake3 `input_hash` for a message array.
7649
7355
  *
7650
- * `messages` must already be the server's reconstructed shape (the SDK reshapes
7651
- * provider-specific prompts via `aisdk-normalize.ts` so the hashed bytes match
7652
- * what `spans_v0` stores). The system message is stripped, the remainder
7653
- * canonicalized, and the digest hex-encoded (64 chars).
7356
+ * `messages` is the verbatim LanguageModel-level prompt
7357
+ * (`JSON.parse(stringifyPromptForTelemetry(options.prompt))`) the same bytes
7358
+ * the SDK records as `gen_ai.input.messages` and the server hashes on ingest.
7359
+ * System messages are stripped, the remainder canonicalized, and the digest
7360
+ * hex-encoded (64 chars).
7654
7361
  */
7655
7362
  const debugInputHash = (messages) => {
7656
- const canonical = canonicalJson(stripSystemMessage(messages));
7363
+ const canonical = canonicalJson(stripSystemMessages(messages));
7657
7364
  return bytesToHex(blake3(new TextEncoder().encode(canonical)));
7658
7365
  };
7659
7366
  //#endregion
@@ -7713,6 +7420,107 @@ const removeActiveLlmSpan = (span) => {
7713
7420
  /** Most recently pushed still-open LLM span, or undefined if none. */
7714
7421
  const peekActiveLlmSpan = () => openLlmSpans.length > 0 ? openLlmSpans[openLlmSpans.length - 1] : void 0;
7715
7422
  //#endregion
7423
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
7424
+ const serializeJSON$1 = (value) => {
7425
+ if (typeof value === "string") return value;
7426
+ try {
7427
+ return JSON.stringify(value);
7428
+ } catch {
7429
+ return "[unserializable]";
7430
+ }
7431
+ };
7432
+ const normalizeProvider$1 = (provider) => {
7433
+ if (!provider) return void 0;
7434
+ return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
7435
+ };
7436
+ const applyUsage = (attributes, usage) => {
7437
+ if (!usage || typeof usage !== "object") return;
7438
+ if (typeof usage.inputTokens === "number") attributes[LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
7439
+ if (typeof usage.outputTokens === "number") attributes[LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
7440
+ if (typeof usage.totalTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
7441
+ else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
7442
+ if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
7443
+ else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
7444
+ if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
7445
+ else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
7446
+ if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
7447
+ };
7448
+ const verbatimStandardizedMessages = (event) => {
7449
+ const messages = [];
7450
+ const sys = event.instructions || event.system;
7451
+ if (typeof sys === "string" && sys.length > 0) messages.push({
7452
+ role: "system",
7453
+ content: sys
7454
+ });
7455
+ else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
7456
+ else if (sys && typeof sys === "object") messages.push(sys);
7457
+ if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(m);
7458
+ return messages;
7459
+ };
7460
+ const buildVerbatimOutputMessages = (content) => {
7461
+ if (!Array.isArray(content) || content.length === 0) return void 0;
7462
+ try {
7463
+ return JSON.stringify([{
7464
+ role: "assistant",
7465
+ content
7466
+ }]);
7467
+ } catch {
7468
+ return;
7469
+ }
7470
+ };
7471
+ const buildTextOutputMessages = (text) => text.length > 0 ? buildVerbatimOutputMessages([{
7472
+ type: "text",
7473
+ text
7474
+ }]) : void 0;
7475
+ const applyUsageToSpan = (span, usage) => {
7476
+ const attrs = {};
7477
+ applyUsage(attrs, usage);
7478
+ for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
7479
+ };
7480
+ const applyFinishReason = (span, finishReason) => {
7481
+ span.setAttribute("gen_ai.response.finish_reason", finishReason);
7482
+ span.setAttribute("ai.response.finishReason", finishReason);
7483
+ };
7484
+ const applyRequestModelAttributes = (span, event) => {
7485
+ if (typeof event.provider === "string") {
7486
+ const provider = normalizeProvider$1(event.provider);
7487
+ if (provider) span.setAttribute(LaminarAttributes.PROVIDER, provider);
7488
+ span.setAttribute("ai.model.provider", event.provider);
7489
+ }
7490
+ if (typeof event.modelId === "string") {
7491
+ span.setAttribute(LaminarAttributes.REQUEST_MODEL, event.modelId);
7492
+ span.setAttribute("ai.model.id", event.modelId);
7493
+ }
7494
+ };
7495
+ const compareHrTime = (a, b) => {
7496
+ if (a[0] !== b[0]) return a[0] - b[0];
7497
+ return a[1] - b[1];
7498
+ };
7499
+ const readSpanEndTime = (span) => {
7500
+ const innerEnd = span._span?.endTime;
7501
+ if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
7502
+ const outerEnd = span.endTime;
7503
+ if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
7504
+ };
7505
+ const verbatimPromptString = (prompt) => {
7506
+ try {
7507
+ const serialized = stringifyPromptForTelemetry(prompt);
7508
+ return typeof serialized === "string" ? serialized : null;
7509
+ } catch {
7510
+ return null;
7511
+ }
7512
+ };
7513
+ const verbatimPromptMessages = (prompt) => {
7514
+ const serialized = verbatimPromptString(prompt);
7515
+ if (serialized === null) return null;
7516
+ try {
7517
+ const parsed = JSON.parse(serialized);
7518
+ return Array.isArray(parsed) ? parsed : null;
7519
+ } catch {
7520
+ return null;
7521
+ }
7522
+ };
7523
+ //#endregion
7716
7524
  //#region src/opentelemetry-lib/instrumentation/aisdk/base-language-model.ts
7717
7525
  /**
7718
7526
  * Base class for Laminar language model wrappers.
@@ -7755,7 +7563,7 @@ var BaseLaminarLanguageModel = class {
7755
7563
  async doGenerateOrStreamWithCaching(options, originalFn, buildFromCached) {
7756
7564
  if (!replayEnabled() || Laminar.debugRunLive) return originalFn(options);
7757
7565
  const spanToMark = peekActiveLlmSpan() ?? Laminar.getCurrentSpan();
7758
- const messages = extractInputMessages(options);
7566
+ const messages = verbatimPromptMessages(options.prompt);
7759
7567
  if (messages === null || messages.length === 0) return originalFn(options);
7760
7568
  const inputHash = debugInputHash(messages);
7761
7569
  const outcome = await getRuntime()?.lookupCache(inputHash) ?? { kind: "live" };
@@ -7803,17 +7611,45 @@ var BaseLaminarLanguageModel = class {
7803
7611
  }];
7804
7612
  if (output === null || typeof output !== "object") return [];
7805
7613
  const handleItem = (item) => {
7806
- if (item.type === "text") return [{
7807
- type: "text",
7808
- text: item.text ?? item.content ?? ""
7809
- }];
7810
- if (["tool-call", "tool_call"].includes(item.type)) return [{
7614
+ if (item.type === "text") {
7615
+ if (typeof item.text === "string") return [{
7616
+ ...item,
7617
+ type: "text",
7618
+ text: item.text
7619
+ }];
7620
+ return [{
7621
+ type: "text",
7622
+ text: item.content ?? ""
7623
+ }];
7624
+ }
7625
+ if (item.type === "tool-call") return [{
7626
+ ...item,
7811
7627
  type: "tool-call",
7812
7628
  toolCallId: item.toolCallId ?? item.id,
7813
7629
  toolName: item.toolName ?? item.name,
7814
- input: JSON.stringify(item.input ?? item.arguments)
7630
+ input: typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? item.arguments)
7815
7631
  }];
7816
- if (["reasoning", "thinking"].includes(item.type)) return [{
7632
+ if (item.type === "tool_call") {
7633
+ const rawInput = item.input ?? item.arguments;
7634
+ return [{
7635
+ type: "tool-call",
7636
+ toolCallId: item.toolCallId ?? item.id,
7637
+ toolName: item.toolName ?? item.name,
7638
+ input: typeof rawInput === "string" ? rawInput : JSON.stringify(rawInput)
7639
+ }];
7640
+ }
7641
+ if (item.type === "reasoning") {
7642
+ if (typeof item.text === "string") return [{
7643
+ ...item,
7644
+ type: "reasoning",
7645
+ text: item.text
7646
+ }];
7647
+ return [{
7648
+ type: "reasoning",
7649
+ text: item.content ?? ""
7650
+ }];
7651
+ }
7652
+ if (item.type === "thinking") return [{
7817
7653
  type: "reasoning",
7818
7654
  text: item.text ?? item.content ?? ""
7819
7655
  }];
@@ -7879,7 +7715,8 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
7879
7715
  const id = `text-${textIndex++}`;
7880
7716
  parts.push({
7881
7717
  type: "text-start",
7882
- id
7718
+ id,
7719
+ providerMetadata: block.providerMetadata
7883
7720
  });
7884
7721
  parts.push({
7885
7722
  type: "text-delta",
@@ -7907,16 +7744,15 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
7907
7744
  id
7908
7745
  });
7909
7746
  parts.push({
7910
- type: "tool-call",
7911
- toolCallId: block.toolCallId,
7912
- toolName: block.toolName,
7913
- input: block.input
7747
+ ...block,
7748
+ type: "tool-call"
7914
7749
  });
7915
7750
  } else if (block.type === "reasoning") {
7916
7751
  const id = `reasoning-${reasoningIndex++}`;
7917
7752
  parts.push({
7918
7753
  type: "reasoning-start",
7919
- id
7754
+ id,
7755
+ providerMetadata: block.providerMetadata
7920
7756
  });
7921
7757
  parts.push({
7922
7758
  type: "reasoning-delta",
@@ -7981,7 +7817,8 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
7981
7817
  const id = `text-${textIndex++}`;
7982
7818
  parts.push({
7983
7819
  type: "text-start",
7984
- id
7820
+ id,
7821
+ providerMetadata: block.providerMetadata
7985
7822
  });
7986
7823
  parts.push({
7987
7824
  type: "text-delta",
@@ -8009,16 +7846,15 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
8009
7846
  id
8010
7847
  });
8011
7848
  parts.push({
8012
- type: "tool-call",
8013
- toolCallId: block.toolCallId,
8014
- toolName: block.toolName,
8015
- input: block.input
7849
+ ...block,
7850
+ type: "tool-call"
8016
7851
  });
8017
7852
  } else if (block.type === "reasoning") {
8018
7853
  const id = `reasoning-${reasoningIndex++}`;
8019
7854
  parts.push({
8020
7855
  type: "reasoning-start",
8021
- id
7856
+ id,
7857
+ providerMetadata: block.providerMetadata
8022
7858
  });
8023
7859
  parts.push({
8024
7860
  type: "reasoning-delta",
@@ -8083,7 +7919,8 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
8083
7919
  const id = `text-${textIndex++}`;
8084
7920
  parts.push({
8085
7921
  type: "text-start",
8086
- id
7922
+ id,
7923
+ providerMetadata: block.providerMetadata
8087
7924
  });
8088
7925
  parts.push({
8089
7926
  type: "text-delta",
@@ -8111,16 +7948,15 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
8111
7948
  id
8112
7949
  });
8113
7950
  parts.push({
8114
- type: "tool-call",
8115
- toolCallId: block.toolCallId,
8116
- toolName: block.toolName,
8117
- input: block.input
7951
+ ...block,
7952
+ type: "tool-call"
8118
7953
  });
8119
7954
  } else if (block.type === "reasoning") {
8120
7955
  const id = `reasoning-${reasoningIndex++}`;
8121
7956
  parts.push({
8122
7957
  type: "reasoning-start",
8123
- id
7958
+ id,
7959
+ providerMetadata: block.providerMetadata
8124
7960
  });
8125
7961
  parts.push({
8126
7962
  type: "reasoning-delta",
@@ -8200,136 +8036,94 @@ function wrapLanguageModel(languageModel) {
8200
8036
  return new LaminarLanguageModelV2(languageModel);
8201
8037
  }
8202
8038
  //#endregion
8203
- //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
8204
- const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
8205
- //#endregion
8206
- //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
8207
- const serializeJSON$1 = (value) => {
8208
- if (typeof value === "string") return value;
8039
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/package-versions.ts
8040
+ let cached;
8041
+ const readPackageVersion = (specifier) => {
8209
8042
  try {
8210
- return JSON.stringify(value);
8043
+ const pkg = __require(`${specifier}/package.json`);
8044
+ return typeof pkg?.version === "string" ? pkg.version : void 0;
8211
8045
  } catch {
8212
- return "[unserializable]";
8046
+ return;
8213
8047
  }
8214
8048
  };
8215
- const normalizeProvider$1 = (provider) => {
8216
- if (!provider) return void 0;
8217
- return provider.split(".").shift()?.toLowerCase().trim() || provider.toLowerCase().trim() || void 0;
8218
- };
8219
- const applyUsage = (attributes, usage) => {
8220
- if (!usage || typeof usage !== "object") return;
8221
- if (typeof usage.inputTokens === "number") attributes[LaminarAttributes.INPUT_TOKEN_COUNT] = usage.inputTokens;
8222
- if (typeof usage.outputTokens === "number") attributes[LaminarAttributes.OUTPUT_TOKEN_COUNT] = usage.outputTokens;
8223
- if (typeof usage.totalTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = usage.totalTokens;
8224
- else if (typeof usage.inputTokens === "number" || typeof usage.outputTokens === "number") attributes[LaminarAttributes.TOTAL_TOKEN_COUNT] = (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
8225
- if (typeof usage.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.reasoningTokens;
8226
- else if (typeof usage.outputTokenDetails?.reasoningTokens === "number") attributes["gen_ai.usage.reasoning_tokens"] = usage.outputTokenDetails.reasoningTokens;
8227
- if (typeof usage.cachedInputTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.cachedInputTokens;
8228
- else if (typeof usage.inputTokenDetails?.cacheReadTokens === "number") attributes["gen_ai.usage.cache_read_input_tokens"] = usage.inputTokenDetails.cacheReadTokens;
8229
- if (typeof usage.inputTokenDetails?.cacheWriteTokens === "number") attributes["gen_ai.usage.cache_creation_input_tokens"] = usage.inputTokenDetails.cacheWriteTokens;
8230
- };
8231
- const standardizedPromptToMessages = (event) => {
8232
- const messages = [];
8233
- const sys = event.instructions || event.system;
8234
- if (typeof sys === "string" && sys.length > 0) messages.push({
8235
- role: "system",
8236
- content: sys
8237
- });
8238
- else if (Array.isArray(sys)) for (const m of sys) messages.push(m);
8239
- else if (sys && typeof sys === "object") messages.push(sys);
8240
- if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(convertRawStringTextToTextBlock(m));
8241
- return messages;
8242
- };
8243
- const convertRawStringTextToTextBlock = (m) => ({
8244
- ...m,
8245
- content: typeof m?.content === "string" ? [{
8246
- type: "text",
8247
- text: m.content
8248
- }] : m?.content
8249
- });
8250
- const normalizeToolCalls = (toolCalls) => {
8251
- if (!Array.isArray(toolCalls)) return [];
8252
- return toolCalls.map((tc) => ({
8253
- toolCallType: "function",
8254
- toolCallId: tc?.toolCallId,
8255
- toolName: tc?.toolName,
8256
- args: typeof tc?.input === "string" ? tc.input : serializeJSON$1(tc?.input ?? {})
8257
- }));
8258
- };
8259
- const buildGenAiOutputMessages = (args) => {
8260
- const parts = [];
8261
- if (args.reasoningText && args.reasoningText.length > 0) parts.push({
8262
- type: "thinking",
8263
- content: args.reasoningText
8264
- });
8265
- if (args.text && args.text.length > 0) parts.push({
8266
- type: "text",
8267
- content: args.text
8268
- });
8269
- for (const tc of args.toolCalls ?? []) {
8270
- let argsObj = tc.args;
8271
- if (typeof argsObj === "string") try {
8272
- argsObj = JSON.parse(argsObj);
8049
+ /**
8050
+ * A `node_modules` search-path entry is a package-manager-internal virtual
8051
+ * store (pnpm's `.pnpm`, yarn's `.yarn`, etc.) if any path segment starts
8052
+ * with `.` — excluding the `.`/`..` relative-path segments themselves, which
8053
+ * are unrelated. Packages resolved from inside such a store are some
8054
+ * dependency's OWN transitive deps (e.g. `ai`'s `@ai-sdk/provider`), not
8055
+ * what the host project declared, so those entries must be skipped.
8056
+ */
8057
+ const isPackageManagerInternalPath = (path) => path.split(sep).some((segment) => segment.startsWith(".") && segment !== "." && segment !== "..");
8058
+ /**
8059
+ * Given the ordered `node_modules` search-path list Node would use to
8060
+ * resolve a module from this file's location, find the closest directory
8061
+ * that actually has an `@ai-sdk` scope — skipping package-manager-internal
8062
+ * stores and return the packages listed under it. The first path with a
8063
+ * usable `@ai-sdk` directory wins; levels are not merged, so an unrelated
8064
+ * ancestor/monorepo-root's `@ai-sdk` scope is never mixed in once a closer
8065
+ * one has already matched.
8066
+ */
8067
+ const findAiSdkScopeEntries = (searchPaths) => {
8068
+ for (const path of searchPaths) {
8069
+ if (isPackageManagerInternalPath(path)) continue;
8070
+ try {
8071
+ const entries = readdirSync(join(path, "@ai-sdk"));
8072
+ if (entries.length > 0) return entries;
8273
8073
  } catch {}
8274
- parts.push({
8275
- type: "tool_call",
8276
- id: tc.toolCallId,
8277
- name: tc.toolName,
8278
- arguments: argsObj
8279
- });
8280
8074
  }
8281
- if (parts.length === 0) return void 0;
8282
- return JSON.stringify([{
8283
- role: "assistant",
8284
- parts
8285
- }]);
8286
- };
8287
- const extractTextFromContent = (content) => {
8288
- if (!Array.isArray(content)) return "";
8289
- return content.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
8290
- };
8291
- const extractReasoningFromContent = (content) => {
8292
- if (!Array.isArray(content)) return "";
8293
- return content.filter((p) => p && p.type === "reasoning" && typeof p.text === "string").map((p) => p.text).join("");
8294
- };
8295
- const applyUsageToSpan = (span, usage) => {
8296
- const attrs = {};
8297
- applyUsage(attrs, usage);
8298
- for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
8075
+ return [];
8299
8076
  };
8300
- const applyFinishReason = (span, finishReason) => {
8301
- span.setAttribute("gen_ai.response.finish_reason", finishReason);
8302
- span.setAttribute("ai.response.finishReason", finishReason);
8303
- };
8304
- const applyRequestModelAttributes = (span, event) => {
8305
- if (typeof event.provider === "string") {
8306
- const provider = normalizeProvider$1(event.provider);
8307
- if (provider) span.setAttribute(LaminarAttributes.PROVIDER, provider);
8308
- span.setAttribute("ai.model.provider", event.provider);
8077
+ const discoverAiSdkPackages = () => {
8078
+ const packages = {};
8079
+ let searchPaths;
8080
+ try {
8081
+ searchPaths = __require.resolve.paths("@ai-sdk/__probe__") ?? [];
8082
+ } catch {
8083
+ searchPaths = [];
8309
8084
  }
8310
- if (typeof event.modelId === "string") {
8311
- span.setAttribute(LaminarAttributes.REQUEST_MODEL, event.modelId);
8312
- span.setAttribute("ai.model.id", event.modelId);
8085
+ for (const entry of findAiSdkScopeEntries(searchPaths)) {
8086
+ const specifier = `@ai-sdk/${entry}`;
8087
+ const version = readPackageVersion(specifier);
8088
+ if (version) packages[specifier] = version;
8313
8089
  }
8090
+ return packages;
8314
8091
  };
8315
- const applyPromptMessages = (span, event) => {
8316
- const msgs = standardizedPromptToMessages(event);
8317
- if (msgs.length === 0) return;
8318
- const serialized = serializeJSON$1(msgs);
8319
- span.setAttribute("ai.prompt.messages", serialized);
8320
- span.setAttribute(SPAN_INPUT, serialized);
8321
- };
8322
- const compareHrTime = (a, b) => {
8323
- if (a[0] !== b[0]) return a[0] - b[0];
8324
- return a[1] - b[1];
8092
+ /**
8093
+ * Reads installed `ai` / `@ai-sdk/*` package versions once and caches the
8094
+ * result for the lifetime of the process — this is expected to be called on
8095
+ * every span creation, so the actual `require`/`readdirSync` work must not
8096
+ * repeat.
8097
+ */
8098
+ const getAiSdkPackageVersions = () => {
8099
+ if (cached) return cached;
8100
+ try {
8101
+ cached = {
8102
+ aiVersion: readPackageVersion("ai"),
8103
+ aiSdkPackages: discoverAiSdkPackages()
8104
+ };
8105
+ } catch {
8106
+ cached = { aiSdkPackages: {} };
8107
+ }
8108
+ return cached;
8325
8109
  };
8326
- const readSpanEndTime = (span) => {
8327
- const innerEnd = span._span?.endTime;
8328
- if (innerEnd && (innerEnd[0] !== 0 || innerEnd[1] !== 0)) return innerEnd;
8329
- const outerEnd = span.endTime;
8330
- if (outerEnd && (outerEnd[0] !== 0 || outerEnd[1] !== 0)) return outerEnd;
8110
+ /**
8111
+ * Flat span-attribute object for the `ai` package's instrumentation scope
8112
+ * plus one `lmnr.span.instrumentation.@ai-sdk/<pkg>.version` entry per
8113
+ * resolvable `@ai-sdk/*` package. Merge this into every span this
8114
+ * integration creates.
8115
+ */
8116
+ const buildAiSdkInstrumentationAttributes = () => {
8117
+ const { aiVersion, aiSdkPackages } = getAiSdkPackageVersions();
8118
+ const attributes = { [SPAN_INSTRUMENTATION_SCOPE_NAME]: "ai" };
8119
+ if (aiVersion) attributes[SPAN_INSTRUMENTATION_SCOPE_VERSION] = aiVersion;
8120
+ for (const [pkg, version] of Object.entries(aiSdkPackages)) attributes[`lmnr.span.instrumentation.${pkg}.version`] = version;
8121
+ return attributes;
8331
8122
  };
8332
8123
  //#endregion
8124
+ //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
8125
+ const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
8126
+ //#endregion
8333
8127
  //#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/index.ts
8334
8128
  /**
8335
8129
  * The AI SDK v7 diagnostics channel name. Every lifecycle event fires
@@ -8351,6 +8145,8 @@ var LaminarAiSdkTelemetry = class {
8351
8145
  this.llmByKey = /* @__PURE__ */ new Map();
8352
8146
  this.toolByCallId = /* @__PURE__ */ new Map();
8353
8147
  this.activeStreamStepByCallId = /* @__PURE__ */ new Map();
8148
+ this.promptByCallId = /* @__PURE__ */ new Map();
8149
+ this.logger = initializeLogger();
8354
8150
  this.onStart = (event) => {
8355
8151
  const callId = event?.callId;
8356
8152
  if (!callId) return;
@@ -8362,6 +8158,7 @@ var LaminarAiSdkTelemetry = class {
8362
8158
  kind: SpanKind.CLIENT,
8363
8159
  attributes: {
8364
8160
  [SPAN_TYPE]: "DEFAULT",
8161
+ ...buildAiSdkInstrumentationAttributes(),
8365
8162
  ...typeof event.functionId === "string" ? {
8366
8163
  "operation.name": event.functionId,
8367
8164
  "ai.prompt": "_"
@@ -8382,8 +8179,10 @@ var LaminarAiSdkTelemetry = class {
8382
8179
  if (typeof event.functionId === "string") span.setAttribute("ai.functionId", event.functionId);
8383
8180
  if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany" || event.operationId === "ai.rerank") span.setAttribute(SPAN_TYPE, "LLM");
8384
8181
  if (this.recordInputs) {
8385
- if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) applyPromptMessages(span, event);
8386
- else if (event.value !== void 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(event.value));
8182
+ if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0) {
8183
+ const msgs = verbatimStandardizedMessages(event);
8184
+ if (msgs.length > 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(msgs));
8185
+ } else if (event.value !== void 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(event.value));
8387
8186
  else if (event.query !== void 0 || event.documents !== void 0) span.setAttribute(SPAN_INPUT, serializeJSON$1({
8388
8187
  query: event.query,
8389
8188
  documents: event.documents
@@ -8398,17 +8197,25 @@ var LaminarAiSdkTelemetry = class {
8398
8197
  });
8399
8198
  };
8400
8199
  this.onStepStart = (event) => {
8401
- if (!this.createStepSpan) return;
8402
8200
  const callId = event?.callId;
8403
8201
  const stepNumber = event?.stepNumber ?? 0;
8404
8202
  if (!callId) return;
8203
+ if (Array.isArray(event?.promptMessages)) {
8204
+ const serialized = verbatimPromptString(event.promptMessages);
8205
+ if (serialized !== null) this.promptByCallId.set(callId, serialized);
8206
+ }
8207
+ if (!this.createStepSpan) return;
8405
8208
  const op = this.operationByCallId.get(callId);
8406
8209
  if (!op) return;
8407
8210
  const span = getTracer().startSpan(`ai.step`, { kind: SpanKind.CLIENT }, op.ctx);
8408
8211
  const spanCtx = trace.setSpan(op.ctx, span);
8409
8212
  span.setAttribute(SPAN_TYPE, "DEFAULT");
8213
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8410
8214
  span.setAttribute("ai.step.number", stepNumber);
8411
- if (this.recordInputs) applyPromptMessages(span, event);
8215
+ if (this.recordInputs) {
8216
+ const msgs = verbatimStandardizedMessages(event);
8217
+ if (msgs.length > 0) span.setAttribute(SPAN_INPUT, serializeJSON$1(msgs));
8218
+ }
8412
8219
  this.stepByKey.set(stepKey(callId, stepNumber), {
8413
8220
  span,
8414
8221
  ctx: spanCtx,
@@ -8424,8 +8231,17 @@ var LaminarAiSdkTelemetry = class {
8424
8231
  if (!parentCtx) return;
8425
8232
  const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: SpanKind.CLIENT }, parentCtx);
8426
8233
  span.setAttribute(SPAN_TYPE, "LLM");
8234
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8427
8235
  applyRequestModelAttributes(span, event);
8428
- if (this.recordInputs) applyPromptMessages(span, event);
8236
+ if (this.recordInputs) {
8237
+ if (event.tools) try {
8238
+ span.setAttribute("gen_ai.tool.definitions", JSON.stringify(event.tools));
8239
+ } catch (e) {
8240
+ this.logger.debug(`Failed to record span tool definitions: ${errorMessage(e)}`);
8241
+ }
8242
+ const serialized = this.promptByCallId.get(callId);
8243
+ if (serialized !== void 0) span.setAttribute("gen_ai.input.messages", serialized);
8244
+ }
8429
8245
  this.llmByKey.set(stepKey(callId, stepNumber), {
8430
8246
  span,
8431
8247
  textDeltas: []
@@ -8448,18 +8264,14 @@ var LaminarAiSdkTelemetry = class {
8448
8264
  if (typeof event.responseId === "string") span.setAttribute("gen_ai.response.id", event.responseId);
8449
8265
  applyUsageToSpan(span, event.usage);
8450
8266
  if (this.recordOutputs) {
8451
- const content = Array.isArray(event.content) ? event.content : void 0;
8452
- const outputMessages = buildGenAiOutputMessages({
8453
- text: extractTextFromContent(content),
8454
- reasoningText: extractReasoningFromContent(content),
8455
- toolCalls: normalizeToolCalls(content ? content.filter((p) => p && p.type === "tool-call") : [])
8456
- });
8267
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8457
8268
  if (outputMessages) span.setAttribute("gen_ai.output.messages", outputMessages);
8458
8269
  }
8459
8270
  span.setStatus({ code: SpanStatusCode.OK });
8460
8271
  span.end();
8461
8272
  removeActiveLlmSpan(span);
8462
8273
  this.llmByKey.delete(stepKey(callId, stepNumber));
8274
+ this.promptByCallId.delete(callId);
8463
8275
  };
8464
8276
  this.onChunk = (event) => {
8465
8277
  const chunk = event?.chunk;
@@ -8488,7 +8300,7 @@ var LaminarAiSdkTelemetry = class {
8488
8300
  const llm = this.llmByKey.get(key);
8489
8301
  if (llm) {
8490
8302
  if (this.recordOutputs && llm.textDeltas.length > 0) {
8491
- const outputMessages = buildGenAiOutputMessages({ text: llm.textDeltas.join("") });
8303
+ const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
8492
8304
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8493
8305
  }
8494
8306
  applyUsageToSpan(llm.span, event.usage);
@@ -8496,21 +8308,13 @@ var LaminarAiSdkTelemetry = class {
8496
8308
  removeActiveLlmSpan(llm.span);
8497
8309
  this.llmByKey.delete(key);
8498
8310
  }
8311
+ this.promptByCallId.delete(callId);
8499
8312
  const step = this.stepByKey.get(key);
8500
8313
  if (!step) return;
8501
8314
  if (typeof event.finishReason === "string") applyFinishReason(step.span, event.finishReason);
8502
8315
  if (this.recordOutputs) {
8503
- const outputPayload = {};
8504
- if (typeof event.text === "string" && event.text.length > 0) {
8505
- outputPayload.text = event.text;
8506
- step.span.setAttribute("ai.response.text", event.text);
8507
- }
8508
- const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
8509
- if (normalizedToolCallsList.length > 0) {
8510
- outputPayload.toolCalls = normalizedToolCallsList;
8511
- step.span.setAttribute("ai.response.toolCalls", serializeJSON$1(normalizedToolCallsList));
8512
- }
8513
- if (Object.keys(outputPayload).length > 0) step.span.setAttribute(SPAN_OUTPUT, serializeJSON$1(outputPayload));
8316
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8317
+ if (outputMessages) step.span.setAttribute(SPAN_OUTPUT, outputMessages);
8514
8318
  }
8515
8319
  step.span.setStatus({ code: SpanStatusCode.OK });
8516
8320
  step.span.end();
@@ -8523,7 +8327,12 @@ var LaminarAiSdkTelemetry = class {
8523
8327
  if (!op) return;
8524
8328
  const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: SpanKind.CLIENT }, op.ctx);
8525
8329
  span.setAttribute(SPAN_TYPE, "LLM");
8330
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8526
8331
  applyRequestModelAttributes(span, event);
8332
+ if (this.recordInputs && Array.isArray(event?.promptMessages)) {
8333
+ const serialized = verbatimPromptString(event.promptMessages);
8334
+ if (serialized !== null) span.setAttribute("gen_ai.input.messages", serialized);
8335
+ }
8527
8336
  this.llmByKey.set(stepKey(callId, 0), {
8528
8337
  span,
8529
8338
  textDeltas: []
@@ -8543,7 +8352,7 @@ var LaminarAiSdkTelemetry = class {
8543
8352
  }
8544
8353
  applyUsageToSpan(llm.span, event.usage);
8545
8354
  if (this.recordOutputs && typeof event.objectText === "string") {
8546
- const outputMessages = buildGenAiOutputMessages({ text: event.objectText });
8355
+ const outputMessages = buildTextOutputMessages(event.objectText);
8547
8356
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8548
8357
  }
8549
8358
  llm.span.setStatus({ code: SpanStatusCode.OK });
@@ -8562,6 +8371,7 @@ var LaminarAiSdkTelemetry = class {
8562
8371
  const span = getTracer().startSpan(`ai.tool ${toolName ?? "unknown"}`, { kind: SpanKind.CLIENT }, parentCtx);
8563
8372
  const spanCtx = trace.setSpan(parentCtx, span);
8564
8373
  span.setAttribute(SPAN_TYPE, "TOOL");
8374
+ span.setAttributes(buildAiSdkInstrumentationAttributes());
8565
8375
  if (toolName) span.setAttribute("ai.toolCall.name", toolName);
8566
8376
  span.setAttribute("ai.toolCall.id", toolCallId);
8567
8377
  if (this.recordInputs && toolCall?.input !== void 0) {
@@ -8639,17 +8449,8 @@ var LaminarAiSdkTelemetry = class {
8639
8449
  if (typeof event.finishReason === "string") applyFinishReason(op.span, event.finishReason);
8640
8450
  if (op.operationId === "ai.embed" || op.operationId === "ai.embedMany" || op.operationId === "ai.rerank") applyUsageToSpan(op.span, event.totalUsage ?? event.usage);
8641
8451
  if (this.recordOutputs) {
8642
- const hasText = typeof event.text === "string" && event.text.length > 0;
8643
- if (hasText) {
8644
- op.span.setAttribute("ai.response.text", event.text);
8645
- op.span.setAttribute(SPAN_OUTPUT, event.text);
8646
- }
8647
- const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
8648
- if (normalizedToolCallsList.length > 0) {
8649
- const serialized = serializeJSON$1(normalizedToolCallsList);
8650
- op.span.setAttribute("ai.response.toolCalls", serialized);
8651
- if (!hasText) op.span.setAttribute(SPAN_OUTPUT, serialized);
8652
- }
8452
+ const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
8453
+ if (outputMessages) op.span.setAttribute(SPAN_OUTPUT, outputMessages);
8653
8454
  if (event.embedding !== void 0) {
8654
8455
  const emb = event.embedding;
8655
8456
  if (Array.isArray(emb) && Array.isArray(emb[0])) op.span.setAttribute("ai.embed.count", emb.length);
@@ -8753,7 +8554,7 @@ var LaminarAiSdkTelemetry = class {
8753
8554
  };
8754
8555
  for (const [key, llm] of this.llmByKey) if (key.startsWith(prefix)) {
8755
8556
  if (this.recordOutputs && llm.textDeltas.length > 0) {
8756
- const outputMessages = buildGenAiOutputMessages({ text: llm.textDeltas.join("") });
8557
+ const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
8757
8558
  if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
8758
8559
  }
8759
8560
  llm.span.end();
@@ -8771,6 +8572,7 @@ var LaminarAiSdkTelemetry = class {
8771
8572
  for (const key of this.llmByKey.keys()) if (key.startsWith(prefix)) this.llmByKey.delete(key);
8772
8573
  for (const [toolCallId, tool] of this.toolByCallId) if (tool.callId === callId) this.toolByCallId.delete(toolCallId);
8773
8574
  for (const key of this.stepByKey.keys()) if (key.startsWith(prefix)) this.stepByKey.delete(key);
8575
+ this.promptByCallId.delete(callId);
8774
8576
  return latest;
8775
8577
  }
8776
8578
  };