@lmnr-ai/lmnr 0.8.37 → 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.
- package/dist/cli.cjs +2 -2
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/{dist-muQLQb9o.cjs → dist-DGDsKbQ8.cjs} +17 -3
- package/dist/dist-DGDsKbQ8.cjs.map +1 -0
- package/dist/{dist-CdR_Phvb.mjs → dist-DVpPVAC5.mjs} +6 -4
- package/dist/dist-DVpPVAC5.mjs.map +1 -0
- package/dist/{evaluations-DpDwGehc.d.mts → evaluations-CzqDQADS.d.mts} +2 -30
- package/dist/{evaluations-D6_c4gW-.d.cts → evaluations-F6Zzae_Y.d.cts} +2 -30
- package/dist/{file-utils-BE3NvLX0.mjs → file-utils-D3Jj4yAt.mjs} +2 -2
- package/dist/{file-utils-BE3NvLX0.mjs.map → file-utils-D3Jj4yAt.mjs.map} +1 -1
- package/dist/{file-utils-C0oHtdus.cjs → file-utils-wA-Z77kv.cjs} +2 -2
- package/dist/{file-utils-C0oHtdus.cjs.map → file-utils-wA-Z77kv.cjs.map} +1 -1
- package/dist/index.cjs +299 -496
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13943 -7780
- package/dist/index.d.mts +13943 -7780
- package/dist/index.mjs +301 -498
- package/dist/index.mjs.map +1 -1
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs +71 -55
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.cjs.map +1 -1
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.cts +4410 -61
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.d.mts +4410 -61
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs +71 -55
- package/dist/opentelemetry-lib/instrumentation/temporal/workflow-interceptors.mjs.map +1 -1
- package/package.json +26 -26
- package/dist/dist-CdR_Phvb.mjs.map +0 -1
- package/dist/dist-muQLQb9o.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-
|
|
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-
|
|
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
|
/**
|
|
@@ -6238,8 +6240,6 @@ const initInstrumentations = (client, suppressContentTracing, sessionRecordingOp
|
|
|
6238
6240
|
}));
|
|
6239
6241
|
instrumentations.push(new _traceloop_instrumentation_anthropic.AnthropicInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6240
6242
|
instrumentations.push(new _traceloop_instrumentation_cohere.CohereInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6241
|
-
instrumentations.push(new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6242
|
-
instrumentations.push(new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6243
6243
|
instrumentations.push(new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing }));
|
|
6244
6244
|
instrumentations.push(new _traceloop_instrumentation_pinecone.PineconeInstrumentation());
|
|
6245
6245
|
instrumentations.push(new _traceloop_instrumentation_langchain.LangChainInstrumentation({ traceContent: !suppressContentTracing }));
|
|
@@ -6291,16 +6291,6 @@ const manuallyInitInstrumentations = (client, instrumentModules, suppressContent
|
|
|
6291
6291
|
instrumentations.push(cohereInstrumentation);
|
|
6292
6292
|
cohereInstrumentation.manuallyInstrument(instrumentModules.cohere);
|
|
6293
6293
|
}
|
|
6294
|
-
if (instrumentModules?.google_vertexai) {
|
|
6295
|
-
const vertexaiInstrumentation = new _traceloop_instrumentation_vertexai.VertexAIInstrumentation({ traceContent: !suppressContentTracing });
|
|
6296
|
-
instrumentations.push(vertexaiInstrumentation);
|
|
6297
|
-
vertexaiInstrumentation.manuallyInstrument(instrumentModules.google_vertexai);
|
|
6298
|
-
}
|
|
6299
|
-
if (instrumentModules?.google_aiplatform) {
|
|
6300
|
-
const aiplatformInstrumentation = new _traceloop_instrumentation_vertexai.AIPlatformInstrumentation({ traceContent: !suppressContentTracing });
|
|
6301
|
-
instrumentations.push(aiplatformInstrumentation);
|
|
6302
|
-
aiplatformInstrumentation.manuallyInstrument(instrumentModules.google_aiplatform);
|
|
6303
|
-
}
|
|
6304
6294
|
if (instrumentModules?.bedrock) {
|
|
6305
6295
|
const bedrockInstrumentation = new _traceloop_instrumentation_bedrock.BedrockInstrumentation({ traceContent: !suppressContentTracing });
|
|
6306
6296
|
instrumentations.push(bedrockInstrumentation);
|
|
@@ -7325,260 +7315,6 @@ async function evaluate({ data, executor, evaluators, groupName, name, metadata,
|
|
|
7325
7315
|
else return await evaluation.run();
|
|
7326
7316
|
}
|
|
7327
7317
|
//#endregion
|
|
7328
|
-
//#region src/debug/aisdk-normalize.ts
|
|
7329
|
-
const isRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
|
|
7330
|
-
/** Mirror Rust `is_url`. */
|
|
7331
|
-
const isUrl = (data) => data.startsWith("http://") || data.startsWith("https://") || data.startsWith("data:");
|
|
7332
|
-
/** Mirror Rust `infer_image_type`. */
|
|
7333
|
-
const inferImageType = (base64) => {
|
|
7334
|
-
if (base64.startsWith("/9j/")) return "image/jpeg";
|
|
7335
|
-
if (base64.startsWith("iVBORw0KGgo")) return "image/png";
|
|
7336
|
-
if (base64.startsWith("R0lGODlh")) return "image/gif";
|
|
7337
|
-
if (base64.startsWith("UklGR")) return "image/webp";
|
|
7338
|
-
if (base64.startsWith("PHN2Zz")) return "image/svg+xml";
|
|
7339
|
-
return "image/png";
|
|
7340
|
-
};
|
|
7341
|
-
const base64FromBytes = (bytes) => {
|
|
7342
|
-
let latin1 = "";
|
|
7343
|
-
for (const b of bytes) latin1 += String.fromCharCode(b & 255);
|
|
7344
|
-
return btoa(latin1);
|
|
7345
|
-
};
|
|
7346
|
-
/**
|
|
7347
|
-
* Mirror Rust `json_value_to_string`: a JSON string stays itself; anything else
|
|
7348
|
-
* is compact-serialized. With serde `preserve_order` on, `JSON.stringify` matches
|
|
7349
|
-
* `serde_json::Value::to_string()` byte-for-byte for object key order.
|
|
7350
|
-
*/
|
|
7351
|
-
const jsonValueToString = (value) => typeof value === "string" ? value : JSON.stringify(value);
|
|
7352
|
-
/** First string among the candidates (ports serde field aliases). */
|
|
7353
|
-
const firstString = (...candidates) => {
|
|
7354
|
-
for (const c of candidates) if (typeof c === "string") return c;
|
|
7355
|
-
};
|
|
7356
|
-
/**
|
|
7357
|
-
* Port of `InstrumentationChatMessageImage` (untagged) →
|
|
7358
|
-
* `from_instrumentation_content_part`'s `Image` arm. Returns the output
|
|
7359
|
-
* `ChatMessageContentPart` object, or `null` if no untagged variant matches.
|
|
7360
|
-
*/
|
|
7361
|
-
const parseImage = (e) => {
|
|
7362
|
-
if (isRecord(e.source)) {
|
|
7363
|
-
const src = e.source;
|
|
7364
|
-
if (typeof src.media_type === "string" && typeof src.data === "string") return {
|
|
7365
|
-
type: "image",
|
|
7366
|
-
mediaType: src.media_type,
|
|
7367
|
-
data: src.data
|
|
7368
|
-
};
|
|
7369
|
-
if (typeof src.url === "string") return {
|
|
7370
|
-
type: "image_url",
|
|
7371
|
-
url: src.url,
|
|
7372
|
-
detail: typeof src.detail === "string" ? src.detail : null
|
|
7373
|
-
};
|
|
7374
|
-
}
|
|
7375
|
-
if (Array.isArray(e.image) && e.image.every((n) => typeof n === "number")) {
|
|
7376
|
-
const b64 = base64FromBytes(e.image);
|
|
7377
|
-
return {
|
|
7378
|
-
type: "image_url",
|
|
7379
|
-
url: `data:${typeof e.mime_type === "string" ? e.mime_type : inferImageType(b64)};base64,${b64}`,
|
|
7380
|
-
detail: null
|
|
7381
|
-
};
|
|
7382
|
-
}
|
|
7383
|
-
if (typeof e.image === "string") {
|
|
7384
|
-
if (isUrl(e.image)) return {
|
|
7385
|
-
type: "image_url",
|
|
7386
|
-
url: e.image,
|
|
7387
|
-
detail: null
|
|
7388
|
-
};
|
|
7389
|
-
return {
|
|
7390
|
-
type: "image",
|
|
7391
|
-
data: e.image,
|
|
7392
|
-
mediaType: typeof e.mime_type === "string" ? e.mime_type : "image/png"
|
|
7393
|
-
};
|
|
7394
|
-
}
|
|
7395
|
-
return null;
|
|
7396
|
-
};
|
|
7397
|
-
/** Port of the `Document` arm (only the Base64 source shape exists). */
|
|
7398
|
-
const parseDocument = (e) => {
|
|
7399
|
-
if (!isRecord(e.source) || e.source.type !== "base64") return null;
|
|
7400
|
-
const data = e.source.data;
|
|
7401
|
-
if (typeof data !== "string") return null;
|
|
7402
|
-
return {
|
|
7403
|
-
type: "document",
|
|
7404
|
-
source: {
|
|
7405
|
-
type: "base64",
|
|
7406
|
-
media_type: firstString(e.source.media_type, e.source.mediaType) ?? "application/octet-stream",
|
|
7407
|
-
data
|
|
7408
|
-
}
|
|
7409
|
-
};
|
|
7410
|
-
};
|
|
7411
|
-
const DATA_URL_RE = /^data:((?:application|image)\/[a-zA-Z-]+);base64,/;
|
|
7412
|
-
/**
|
|
7413
|
-
* Port of the `File` arm (OpenAI file-id / base64, AI SDK v1/v2).
|
|
7414
|
-
*
|
|
7415
|
-
* Known parity gap (v7 + image-by-URL only): on AI SDK v7 the recording path
|
|
7416
|
-
* stores `ai.prompt.messages` from the user-facing `ModelMessage[]` (an image
|
|
7417
|
-
* passed by URL is a `{type:"image", image:"https://…"}` part), which the
|
|
7418
|
-
* server reshapes via `parseImage` into an `image_url`. Replay instead hashes
|
|
7419
|
-
* the LanguageModel-level prompt, where that same image is already a
|
|
7420
|
-
* `{type:"file", data:"https://…", mediaType:"image/*"}` part — this arm maps
|
|
7421
|
-
* it to `image`, not `image_url`, so the two hashes diverge and the lookup
|
|
7422
|
-
* MISSes (degrading safely to a live call). This can't be reconciled here: the
|
|
7423
|
-
* arm is a faithful port of the server's Rust `parseFile`, so on v6 (where the
|
|
7424
|
-
* server itself reshapes a file part) emitting `image_url` would instead BREAK
|
|
7425
|
-
* parity, and the replay wrapper has no signal for which SDK version recorded
|
|
7426
|
-
* the span. Plain text, multi-part text, tool calls, and base64 images all hash
|
|
7427
|
-
* identically across both paths; only URL-referenced images on v7 are affected.
|
|
7428
|
-
*/
|
|
7429
|
-
const parseFile = (e) => {
|
|
7430
|
-
if (isRecord(e.file)) {
|
|
7431
|
-
const file = e.file;
|
|
7432
|
-
if (typeof file.file_id === "string") return {
|
|
7433
|
-
type: "text",
|
|
7434
|
-
text: JSON.stringify({ file_id: file.file_id })
|
|
7435
|
-
};
|
|
7436
|
-
if (typeof file.file_data === "string") {
|
|
7437
|
-
const m = DATA_URL_RE.exec(file.file_data.slice(0, 50));
|
|
7438
|
-
return {
|
|
7439
|
-
type: "document",
|
|
7440
|
-
source: {
|
|
7441
|
-
type: "base64",
|
|
7442
|
-
media_type: m ? m[1] : "application/octet-stream",
|
|
7443
|
-
data: m ? file.file_data.slice(file.file_data.indexOf(",") + 1) : file.file_data
|
|
7444
|
-
}
|
|
7445
|
-
};
|
|
7446
|
-
}
|
|
7447
|
-
return null;
|
|
7448
|
-
}
|
|
7449
|
-
if (typeof e.data === "string") {
|
|
7450
|
-
const mediaType = firstString(e.media_type, e.mediaType, e.mime_type);
|
|
7451
|
-
if (mediaType === void 0) return null;
|
|
7452
|
-
if (mediaType.startsWith("image/")) return {
|
|
7453
|
-
type: "image",
|
|
7454
|
-
data: e.data,
|
|
7455
|
-
mediaType
|
|
7456
|
-
};
|
|
7457
|
-
return {
|
|
7458
|
-
type: "document",
|
|
7459
|
-
source: {
|
|
7460
|
-
type: "base64",
|
|
7461
|
-
media_type: mediaType,
|
|
7462
|
-
data: e.data
|
|
7463
|
-
}
|
|
7464
|
-
};
|
|
7465
|
-
}
|
|
7466
|
-
return null;
|
|
7467
|
-
};
|
|
7468
|
-
/**
|
|
7469
|
-
* Port of `serde_json::from_value::<InstrumentationChatMessageContentPart>` for a
|
|
7470
|
-
* single part, returning the mapped output `ChatMessageContentPart` object or
|
|
7471
|
-
* `null` when no variant matches (an unrecognized `type`, or a recognized tag
|
|
7472
|
-
* whose inner shape fails). `null` propagates to an all-or-nothing Text fallback.
|
|
7473
|
-
*
|
|
7474
|
-
* Output objects carry serde's serialized field names; `skip_serializing_if` is
|
|
7475
|
-
* reproduced by omitting the field (ChatMessage.tool_call_id only), and `Option`
|
|
7476
|
-
* fields WITHOUT skip (`detail`, tool-call `id`/`arguments`) always emit `null`.
|
|
7477
|
-
*/
|
|
7478
|
-
const parsePart = (el) => {
|
|
7479
|
-
if (!isRecord(el)) return null;
|
|
7480
|
-
switch (el.type) {
|
|
7481
|
-
case "text": {
|
|
7482
|
-
const text = firstString(el.text, el.input_text, el.output_text);
|
|
7483
|
-
return text === void 0 ? null : {
|
|
7484
|
-
type: "text",
|
|
7485
|
-
text
|
|
7486
|
-
};
|
|
7487
|
-
}
|
|
7488
|
-
case "image_url": {
|
|
7489
|
-
if (!isRecord(el.image_url) || typeof el.image_url.url !== "string") return null;
|
|
7490
|
-
const detail = el.image_url.detail;
|
|
7491
|
-
return {
|
|
7492
|
-
type: "image_url",
|
|
7493
|
-
url: el.image_url.url,
|
|
7494
|
-
detail: typeof detail === "string" ? detail : null
|
|
7495
|
-
};
|
|
7496
|
-
}
|
|
7497
|
-
case "image": return parseImage(el);
|
|
7498
|
-
case "document": return parseDocument(el);
|
|
7499
|
-
case "file": return parseFile(el);
|
|
7500
|
-
case "tool-call": {
|
|
7501
|
-
if (typeof el.toolName !== "string") return null;
|
|
7502
|
-
const rawInput = "input" in el ? el.input : el.args;
|
|
7503
|
-
return {
|
|
7504
|
-
type: "tool_call",
|
|
7505
|
-
name: el.toolName,
|
|
7506
|
-
id: typeof el.toolCallId === "string" ? el.toolCallId : null,
|
|
7507
|
-
arguments: rawInput === void 0 || rawInput === null ? null : rawInput
|
|
7508
|
-
};
|
|
7509
|
-
}
|
|
7510
|
-
case "tool-result":
|
|
7511
|
-
if (typeof el.toolCallId !== "string" || typeof el.toolName !== "string") return null;
|
|
7512
|
-
if (!("output" in el || "result" in el)) return null;
|
|
7513
|
-
return {
|
|
7514
|
-
type: "tool-result",
|
|
7515
|
-
toolCallId: el.toolCallId,
|
|
7516
|
-
output: "output" in el ? el.output : el.result,
|
|
7517
|
-
toolName: el.toolName
|
|
7518
|
-
};
|
|
7519
|
-
default: return null;
|
|
7520
|
-
}
|
|
7521
|
-
};
|
|
7522
|
-
/**
|
|
7523
|
-
* Port of `input_chat_messages_from_json`. `input` is the parsed
|
|
7524
|
-
* `ai.prompt.messages` array. Each message's `content` becomes either a
|
|
7525
|
-
* `ContentPartList` (when EVERY element parses as an instrumentation part) or a
|
|
7526
|
-
* Text blob (`json_value_to_string` of the whole content) otherwise. A message
|
|
7527
|
-
* missing `role`/`content` would be a server error; we skip it defensively so the
|
|
7528
|
-
* hash is still computed over the well-formed remainder.
|
|
7529
|
-
*/
|
|
7530
|
-
const inputChatMessagesFromJson = (input) => {
|
|
7531
|
-
if (!Array.isArray(input)) return [];
|
|
7532
|
-
const messages = [];
|
|
7533
|
-
for (const message of input) {
|
|
7534
|
-
if (!isRecord(message)) continue;
|
|
7535
|
-
const role = message.role;
|
|
7536
|
-
if (typeof role !== "string" || !("content" in message)) continue;
|
|
7537
|
-
const otelContent = message.content;
|
|
7538
|
-
let parts = null;
|
|
7539
|
-
if (Array.isArray(otelContent)) {
|
|
7540
|
-
parts = [];
|
|
7541
|
-
for (const el of otelContent) {
|
|
7542
|
-
const parsed = parsePart(el);
|
|
7543
|
-
if (parsed === null) {
|
|
7544
|
-
parts = null;
|
|
7545
|
-
break;
|
|
7546
|
-
}
|
|
7547
|
-
parts.push(parsed);
|
|
7548
|
-
}
|
|
7549
|
-
}
|
|
7550
|
-
const out = {
|
|
7551
|
-
role,
|
|
7552
|
-
content: parts !== null ? parts : jsonValueToString(otelContent)
|
|
7553
|
-
};
|
|
7554
|
-
if (typeof message.tool_call_id === "string") out.tool_call_id = message.tool_call_id;
|
|
7555
|
-
messages.push(out);
|
|
7556
|
-
}
|
|
7557
|
-
return messages;
|
|
7558
|
-
};
|
|
7559
|
-
/**
|
|
7560
|
-
* Reshape an AI SDK call's `options.prompt` into the reconstructed message array
|
|
7561
|
-
* the server hashes. Version-agnostic: `stringifyPromptForTelemetry` already
|
|
7562
|
-
* normalizes V2/V3/V4 prompts (Uint8Array file `data` → base64) into the stored
|
|
7563
|
-
* `ai.prompt.messages` JSON, and the reshape operates on that JSON identically
|
|
7564
|
-
* across versions.
|
|
7565
|
-
*
|
|
7566
|
-
* Returns `null` when the prompt cannot be reshaped at all (a `stringify` /
|
|
7567
|
-
* `JSON.parse` failure). The caller MUST treat `null` as "can't compute a real
|
|
7568
|
-
* key" and degrade to a live call WITHOUT latching: hashing a default/empty
|
|
7569
|
-
* payload here would key the lookup off the wrong bytes, force a spurious MISS,
|
|
7570
|
-
* and wrongly latch live mode for the rest of the replay.
|
|
7571
|
-
*/
|
|
7572
|
-
const extractInputMessages = (options) => {
|
|
7573
|
-
let aiPromptMessages;
|
|
7574
|
-
try {
|
|
7575
|
-
aiPromptMessages = JSON.parse(stringifyPromptForTelemetry(options.prompt));
|
|
7576
|
-
} catch {
|
|
7577
|
-
return null;
|
|
7578
|
-
}
|
|
7579
|
-
return inputChatMessagesFromJson(aiPromptMessages);
|
|
7580
|
-
};
|
|
7581
|
-
//#endregion
|
|
7582
7318
|
//#region src/debug/hash.ts
|
|
7583
7319
|
/**
|
|
7584
7320
|
* Debug-replay input hashing.
|
|
@@ -7586,13 +7322,13 @@ const extractInputMessages = (options) => {
|
|
|
7586
7322
|
* Computes the per-LLM-call `input_hash` the server-side debugger cache (v2) is
|
|
7587
7323
|
* keyed by. The SDK and the Rust app-server MUST produce byte-identical hashes,
|
|
7588
7324
|
* so this is a faithful port of the app-server primitives:
|
|
7589
|
-
* - `canonical_json`
|
|
7590
|
-
* - `
|
|
7591
|
-
* - `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`
|
|
7592
7327
|
*
|
|
7593
|
-
* The whole non-system message array is hashed as ONE blob: strip
|
|
7594
|
-
* `role === "system"` message (
|
|
7595
|
-
* remaining array, then blake3 over its UTF-8 bytes and hex-encode the
|
|
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.
|
|
7596
7332
|
*
|
|
7597
7333
|
* Keep this file line-comparable with the Python `debug/hash.py`.
|
|
7598
7334
|
*/
|
|
@@ -7613,53 +7349,25 @@ const canonicalJson = (value) => {
|
|
|
7613
7349
|
return JSON.stringify(value);
|
|
7614
7350
|
};
|
|
7615
7351
|
/**
|
|
7616
|
-
*
|
|
7352
|
+
* Drop EVERY `role === "system"` message, matching the server.
|
|
7617
7353
|
*
|
|
7618
|
-
*
|
|
7619
|
-
*
|
|
7620
|
-
*
|
|
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.
|
|
7621
7358
|
*/
|
|
7622
|
-
const
|
|
7623
|
-
const content = sysMsg.content;
|
|
7624
|
-
if (typeof content === "string") return content;
|
|
7625
|
-
if (Array.isArray(content)) {
|
|
7626
|
-
const joined = content.map((block) => block !== null && typeof block === "object" ? block.text : void 0).filter((t) => typeof t === "string").join(" ");
|
|
7627
|
-
if (joined.length > 0) return joined;
|
|
7628
|
-
}
|
|
7629
|
-
const parts = sysMsg.parts;
|
|
7630
|
-
if (Array.isArray(parts) && parts.length > 0) {
|
|
7631
|
-
const first = parts[0];
|
|
7632
|
-
if (first !== null && typeof first === "object") {
|
|
7633
|
-
const f = first;
|
|
7634
|
-
const t = typeof f.text === "string" ? f.text : f.content;
|
|
7635
|
-
if (typeof t === "string") return t;
|
|
7636
|
-
}
|
|
7637
|
-
}
|
|
7638
|
-
return "";
|
|
7639
|
-
};
|
|
7359
|
+
const stripSystemMessages = (messages) => messages.filter((m) => !(m !== null && typeof m === "object" && m.role === "system"));
|
|
7640
7360
|
/**
|
|
7641
|
-
*
|
|
7361
|
+
* Compute the hex blake3 `input_hash` for a message array.
|
|
7642
7362
|
*
|
|
7643
|
-
*
|
|
7644
|
-
*
|
|
7645
|
-
*
|
|
7646
|
-
|
|
7647
|
-
|
|
7648
|
-
const sysIdx = messages.findIndex((m) => m !== null && typeof m === "object" && m.role === "system");
|
|
7649
|
-
if (sysIdx === -1) return messages;
|
|
7650
|
-
if (systemMessageText(messages[sysIdx]).length === 0) return messages;
|
|
7651
|
-
return messages.filter((_, i) => i !== sysIdx);
|
|
7652
|
-
};
|
|
7653
|
-
/**
|
|
7654
|
-
* Compute the hex blake3 `input_hash` for a reconstructed message array.
|
|
7655
|
-
*
|
|
7656
|
-
* `messages` must already be the server's reconstructed shape (the SDK reshapes
|
|
7657
|
-
* provider-specific prompts via `aisdk-normalize.ts` so the hashed bytes match
|
|
7658
|
-
* what `spans_v0` stores). The system message is stripped, the remainder
|
|
7659
|
-
* 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).
|
|
7660
7368
|
*/
|
|
7661
7369
|
const debugInputHash = (messages) => {
|
|
7662
|
-
const canonical = canonicalJson(
|
|
7370
|
+
const canonical = canonicalJson(stripSystemMessages(messages));
|
|
7663
7371
|
return (0, _noble_hashes_utils_js.bytesToHex)((0, _noble_hashes_blake3_js.blake3)(new TextEncoder().encode(canonical)));
|
|
7664
7372
|
};
|
|
7665
7373
|
//#endregion
|
|
@@ -7719,6 +7427,107 @@ const removeActiveLlmSpan = (span) => {
|
|
|
7719
7427
|
/** Most recently pushed still-open LLM span, or undefined if none. */
|
|
7720
7428
|
const peekActiveLlmSpan = () => openLlmSpans.length > 0 ? openLlmSpans[openLlmSpans.length - 1] : void 0;
|
|
7721
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
|
|
7722
7531
|
//#region src/opentelemetry-lib/instrumentation/aisdk/base-language-model.ts
|
|
7723
7532
|
/**
|
|
7724
7533
|
* Base class for Laminar language model wrappers.
|
|
@@ -7761,7 +7570,7 @@ var BaseLaminarLanguageModel = class {
|
|
|
7761
7570
|
async doGenerateOrStreamWithCaching(options, originalFn, buildFromCached) {
|
|
7762
7571
|
if (!replayEnabled() || Laminar.debugRunLive) return originalFn(options);
|
|
7763
7572
|
const spanToMark = peekActiveLlmSpan() ?? Laminar.getCurrentSpan();
|
|
7764
|
-
const messages =
|
|
7573
|
+
const messages = verbatimPromptMessages(options.prompt);
|
|
7765
7574
|
if (messages === null || messages.length === 0) return originalFn(options);
|
|
7766
7575
|
const inputHash = debugInputHash(messages);
|
|
7767
7576
|
const outcome = await getRuntime()?.lookupCache(inputHash) ?? { kind: "live" };
|
|
@@ -7809,17 +7618,45 @@ var BaseLaminarLanguageModel = class {
|
|
|
7809
7618
|
}];
|
|
7810
7619
|
if (output === null || typeof output !== "object") return [];
|
|
7811
7620
|
const handleItem = (item) => {
|
|
7812
|
-
if (item.type === "text")
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
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,
|
|
7817
7634
|
type: "tool-call",
|
|
7818
7635
|
toolCallId: item.toolCallId ?? item.id,
|
|
7819
7636
|
toolName: item.toolName ?? item.name,
|
|
7820
|
-
input: JSON.stringify(item.input ?? item.arguments)
|
|
7637
|
+
input: typeof item.input === "string" ? item.input : JSON.stringify(item.input ?? item.arguments)
|
|
7821
7638
|
}];
|
|
7822
|
-
if (
|
|
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 [{
|
|
7823
7660
|
type: "reasoning",
|
|
7824
7661
|
text: item.text ?? item.content ?? ""
|
|
7825
7662
|
}];
|
|
@@ -7885,7 +7722,8 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7885
7722
|
const id = `text-${textIndex++}`;
|
|
7886
7723
|
parts.push({
|
|
7887
7724
|
type: "text-start",
|
|
7888
|
-
id
|
|
7725
|
+
id,
|
|
7726
|
+
providerMetadata: block.providerMetadata
|
|
7889
7727
|
});
|
|
7890
7728
|
parts.push({
|
|
7891
7729
|
type: "text-delta",
|
|
@@ -7913,16 +7751,15 @@ var LaminarLanguageModelV2 = class extends BaseLaminarLanguageModel {
|
|
|
7913
7751
|
id
|
|
7914
7752
|
});
|
|
7915
7753
|
parts.push({
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
toolName: block.toolName,
|
|
7919
|
-
input: block.input
|
|
7754
|
+
...block,
|
|
7755
|
+
type: "tool-call"
|
|
7920
7756
|
});
|
|
7921
7757
|
} else if (block.type === "reasoning") {
|
|
7922
7758
|
const id = `reasoning-${reasoningIndex++}`;
|
|
7923
7759
|
parts.push({
|
|
7924
7760
|
type: "reasoning-start",
|
|
7925
|
-
id
|
|
7761
|
+
id,
|
|
7762
|
+
providerMetadata: block.providerMetadata
|
|
7926
7763
|
});
|
|
7927
7764
|
parts.push({
|
|
7928
7765
|
type: "reasoning-delta",
|
|
@@ -7987,7 +7824,8 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
7987
7824
|
const id = `text-${textIndex++}`;
|
|
7988
7825
|
parts.push({
|
|
7989
7826
|
type: "text-start",
|
|
7990
|
-
id
|
|
7827
|
+
id,
|
|
7828
|
+
providerMetadata: block.providerMetadata
|
|
7991
7829
|
});
|
|
7992
7830
|
parts.push({
|
|
7993
7831
|
type: "text-delta",
|
|
@@ -8015,16 +7853,15 @@ var LaminarLanguageModelV3 = class extends BaseLaminarLanguageModel {
|
|
|
8015
7853
|
id
|
|
8016
7854
|
});
|
|
8017
7855
|
parts.push({
|
|
8018
|
-
|
|
8019
|
-
|
|
8020
|
-
toolName: block.toolName,
|
|
8021
|
-
input: block.input
|
|
7856
|
+
...block,
|
|
7857
|
+
type: "tool-call"
|
|
8022
7858
|
});
|
|
8023
7859
|
} else if (block.type === "reasoning") {
|
|
8024
7860
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8025
7861
|
parts.push({
|
|
8026
7862
|
type: "reasoning-start",
|
|
8027
|
-
id
|
|
7863
|
+
id,
|
|
7864
|
+
providerMetadata: block.providerMetadata
|
|
8028
7865
|
});
|
|
8029
7866
|
parts.push({
|
|
8030
7867
|
type: "reasoning-delta",
|
|
@@ -8089,7 +7926,8 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8089
7926
|
const id = `text-${textIndex++}`;
|
|
8090
7927
|
parts.push({
|
|
8091
7928
|
type: "text-start",
|
|
8092
|
-
id
|
|
7929
|
+
id,
|
|
7930
|
+
providerMetadata: block.providerMetadata
|
|
8093
7931
|
});
|
|
8094
7932
|
parts.push({
|
|
8095
7933
|
type: "text-delta",
|
|
@@ -8117,16 +7955,15 @@ var LaminarLanguageModelV4 = class extends BaseLaminarLanguageModel {
|
|
|
8117
7955
|
id
|
|
8118
7956
|
});
|
|
8119
7957
|
parts.push({
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
-
toolName: block.toolName,
|
|
8123
|
-
input: block.input
|
|
7958
|
+
...block,
|
|
7959
|
+
type: "tool-call"
|
|
8124
7960
|
});
|
|
8125
7961
|
} else if (block.type === "reasoning") {
|
|
8126
7962
|
const id = `reasoning-${reasoningIndex++}`;
|
|
8127
7963
|
parts.push({
|
|
8128
7964
|
type: "reasoning-start",
|
|
8129
|
-
id
|
|
7965
|
+
id,
|
|
7966
|
+
providerMetadata: block.providerMetadata
|
|
8130
7967
|
});
|
|
8131
7968
|
parts.push({
|
|
8132
7969
|
type: "reasoning-delta",
|
|
@@ -8206,136 +8043,94 @@ function wrapLanguageModel(languageModel) {
|
|
|
8206
8043
|
return new LaminarLanguageModelV2(languageModel);
|
|
8207
8044
|
}
|
|
8208
8045
|
//#endregion
|
|
8209
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/utils.ts
|
|
8213
|
-
const serializeJSON$1 = (value) => {
|
|
8214
|
-
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) => {
|
|
8215
8049
|
try {
|
|
8216
|
-
|
|
8050
|
+
const pkg = require(`${specifier}/package.json`);
|
|
8051
|
+
return typeof pkg?.version === "string" ? pkg.version : void 0;
|
|
8217
8052
|
} catch {
|
|
8218
|
-
return
|
|
8053
|
+
return;
|
|
8219
8054
|
}
|
|
8220
8055
|
};
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
|
|
8224
|
-
|
|
8225
|
-
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
else if (sys && typeof sys === "object") messages.push(sys);
|
|
8246
|
-
if (Array.isArray(event.messages)) for (const m of event.messages) messages.push(convertRawStringTextToTextBlock(m));
|
|
8247
|
-
return messages;
|
|
8248
|
-
};
|
|
8249
|
-
const convertRawStringTextToTextBlock = (m) => ({
|
|
8250
|
-
...m,
|
|
8251
|
-
content: typeof m?.content === "string" ? [{
|
|
8252
|
-
type: "text",
|
|
8253
|
-
text: m.content
|
|
8254
|
-
}] : m?.content
|
|
8255
|
-
});
|
|
8256
|
-
const normalizeToolCalls = (toolCalls) => {
|
|
8257
|
-
if (!Array.isArray(toolCalls)) return [];
|
|
8258
|
-
return toolCalls.map((tc) => ({
|
|
8259
|
-
toolCallType: "function",
|
|
8260
|
-
toolCallId: tc?.toolCallId,
|
|
8261
|
-
toolName: tc?.toolName,
|
|
8262
|
-
args: typeof tc?.input === "string" ? tc.input : serializeJSON$1(tc?.input ?? {})
|
|
8263
|
-
}));
|
|
8264
|
-
};
|
|
8265
|
-
const buildGenAiOutputMessages = (args) => {
|
|
8266
|
-
const parts = [];
|
|
8267
|
-
if (args.reasoningText && args.reasoningText.length > 0) parts.push({
|
|
8268
|
-
type: "thinking",
|
|
8269
|
-
content: args.reasoningText
|
|
8270
|
-
});
|
|
8271
|
-
if (args.text && args.text.length > 0) parts.push({
|
|
8272
|
-
type: "text",
|
|
8273
|
-
content: args.text
|
|
8274
|
-
});
|
|
8275
|
-
for (const tc of args.toolCalls ?? []) {
|
|
8276
|
-
let argsObj = tc.args;
|
|
8277
|
-
if (typeof argsObj === "string") try {
|
|
8278
|
-
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;
|
|
8279
8080
|
} catch {}
|
|
8280
|
-
parts.push({
|
|
8281
|
-
type: "tool_call",
|
|
8282
|
-
id: tc.toolCallId,
|
|
8283
|
-
name: tc.toolName,
|
|
8284
|
-
arguments: argsObj
|
|
8285
|
-
});
|
|
8286
8081
|
}
|
|
8287
|
-
|
|
8288
|
-
return JSON.stringify([{
|
|
8289
|
-
role: "assistant",
|
|
8290
|
-
parts
|
|
8291
|
-
}]);
|
|
8082
|
+
return [];
|
|
8292
8083
|
};
|
|
8293
|
-
const
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
};
|
|
8301
|
-
const applyUsageToSpan = (span, usage) => {
|
|
8302
|
-
const attrs = {};
|
|
8303
|
-
applyUsage(attrs, usage);
|
|
8304
|
-
for (const [k, v] of Object.entries(attrs)) if (v !== void 0) span.setAttribute(k, v);
|
|
8305
|
-
};
|
|
8306
|
-
const applyFinishReason = (span, finishReason) => {
|
|
8307
|
-
span.setAttribute("gen_ai.response.finish_reason", finishReason);
|
|
8308
|
-
span.setAttribute("ai.response.finishReason", finishReason);
|
|
8309
|
-
};
|
|
8310
|
-
const applyRequestModelAttributes = (span, event) => {
|
|
8311
|
-
if (typeof event.provider === "string") {
|
|
8312
|
-
const provider = normalizeProvider$1(event.provider);
|
|
8313
|
-
if (provider) span.setAttribute(require_dist.LaminarAttributes.PROVIDER, provider);
|
|
8314
|
-
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 = [];
|
|
8315
8091
|
}
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8092
|
+
for (const entry of findAiSdkScopeEntries(searchPaths)) {
|
|
8093
|
+
const specifier = `@ai-sdk/${entry}`;
|
|
8094
|
+
const version = readPackageVersion(specifier);
|
|
8095
|
+
if (version) packages[specifier] = version;
|
|
8319
8096
|
}
|
|
8097
|
+
return packages;
|
|
8320
8098
|
};
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
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;
|
|
8331
8116
|
};
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
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;
|
|
8337
8129
|
};
|
|
8338
8130
|
//#endregion
|
|
8131
|
+
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/types.ts
|
|
8132
|
+
const stepKey = (callId, stepNumber) => `${callId}:${stepNumber}`;
|
|
8133
|
+
//#endregion
|
|
8339
8134
|
//#region src/opentelemetry-lib/instrumentation/aisdk/v7-integration/index.ts
|
|
8340
8135
|
/**
|
|
8341
8136
|
* The AI SDK v7 diagnostics channel name. Every lifecycle event fires
|
|
@@ -8357,6 +8152,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8357
8152
|
this.llmByKey = /* @__PURE__ */ new Map();
|
|
8358
8153
|
this.toolByCallId = /* @__PURE__ */ new Map();
|
|
8359
8154
|
this.activeStreamStepByCallId = /* @__PURE__ */ new Map();
|
|
8155
|
+
this.promptByCallId = /* @__PURE__ */ new Map();
|
|
8156
|
+
this.logger = require_dist.initializeLogger();
|
|
8360
8157
|
this.onStart = (event) => {
|
|
8361
8158
|
const callId = event?.callId;
|
|
8362
8159
|
if (!callId) return;
|
|
@@ -8368,6 +8165,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8368
8165
|
kind: _opentelemetry_api.SpanKind.CLIENT,
|
|
8369
8166
|
attributes: {
|
|
8370
8167
|
[require_dist.SPAN_TYPE]: "DEFAULT",
|
|
8168
|
+
...buildAiSdkInstrumentationAttributes(),
|
|
8371
8169
|
...typeof event.functionId === "string" ? {
|
|
8372
8170
|
"operation.name": event.functionId,
|
|
8373
8171
|
"ai.prompt": "_"
|
|
@@ -8388,8 +8186,10 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8388
8186
|
if (typeof event.functionId === "string") span.setAttribute("ai.functionId", event.functionId);
|
|
8389
8187
|
if (event.operationId === "ai.embed" || event.operationId === "ai.embedMany" || event.operationId === "ai.rerank") span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8390
8188
|
if (this.recordInputs) {
|
|
8391
|
-
if (Array.isArray(event.messages) || event.system !== void 0 || event.instructions !== void 0)
|
|
8392
|
-
|
|
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));
|
|
8393
8193
|
else if (event.query !== void 0 || event.documents !== void 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1({
|
|
8394
8194
|
query: event.query,
|
|
8395
8195
|
documents: event.documents
|
|
@@ -8404,17 +8204,25 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8404
8204
|
});
|
|
8405
8205
|
};
|
|
8406
8206
|
this.onStepStart = (event) => {
|
|
8407
|
-
if (!this.createStepSpan) return;
|
|
8408
8207
|
const callId = event?.callId;
|
|
8409
8208
|
const stepNumber = event?.stepNumber ?? 0;
|
|
8410
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;
|
|
8411
8215
|
const op = this.operationByCallId.get(callId);
|
|
8412
8216
|
if (!op) return;
|
|
8413
8217
|
const span = getTracer().startSpan(`ai.step`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
|
|
8414
8218
|
const spanCtx = _opentelemetry_api.trace.setSpan(op.ctx, span);
|
|
8415
8219
|
span.setAttribute(require_dist.SPAN_TYPE, "DEFAULT");
|
|
8220
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8416
8221
|
span.setAttribute("ai.step.number", stepNumber);
|
|
8417
|
-
if (this.recordInputs)
|
|
8222
|
+
if (this.recordInputs) {
|
|
8223
|
+
const msgs = verbatimStandardizedMessages(event);
|
|
8224
|
+
if (msgs.length > 0) span.setAttribute(require_dist.SPAN_INPUT, serializeJSON$1(msgs));
|
|
8225
|
+
}
|
|
8418
8226
|
this.stepByKey.set(stepKey(callId, stepNumber), {
|
|
8419
8227
|
span,
|
|
8420
8228
|
ctx: spanCtx,
|
|
@@ -8430,8 +8238,17 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8430
8238
|
if (!parentCtx) return;
|
|
8431
8239
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
|
|
8432
8240
|
span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8241
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8433
8242
|
applyRequestModelAttributes(span, event);
|
|
8434
|
-
if (this.recordInputs)
|
|
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
|
+
}
|
|
8435
8252
|
this.llmByKey.set(stepKey(callId, stepNumber), {
|
|
8436
8253
|
span,
|
|
8437
8254
|
textDeltas: []
|
|
@@ -8454,18 +8271,14 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8454
8271
|
if (typeof event.responseId === "string") span.setAttribute("gen_ai.response.id", event.responseId);
|
|
8455
8272
|
applyUsageToSpan(span, event.usage);
|
|
8456
8273
|
if (this.recordOutputs) {
|
|
8457
|
-
const
|
|
8458
|
-
const outputMessages = buildGenAiOutputMessages({
|
|
8459
|
-
text: extractTextFromContent(content),
|
|
8460
|
-
reasoningText: extractReasoningFromContent(content),
|
|
8461
|
-
toolCalls: normalizeToolCalls(content ? content.filter((p) => p && p.type === "tool-call") : [])
|
|
8462
|
-
});
|
|
8274
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8463
8275
|
if (outputMessages) span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8464
8276
|
}
|
|
8465
8277
|
span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
8466
8278
|
span.end();
|
|
8467
8279
|
removeActiveLlmSpan(span);
|
|
8468
8280
|
this.llmByKey.delete(stepKey(callId, stepNumber));
|
|
8281
|
+
this.promptByCallId.delete(callId);
|
|
8469
8282
|
};
|
|
8470
8283
|
this.onChunk = (event) => {
|
|
8471
8284
|
const chunk = event?.chunk;
|
|
@@ -8494,7 +8307,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8494
8307
|
const llm = this.llmByKey.get(key);
|
|
8495
8308
|
if (llm) {
|
|
8496
8309
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8497
|
-
const outputMessages =
|
|
8310
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8498
8311
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8499
8312
|
}
|
|
8500
8313
|
applyUsageToSpan(llm.span, event.usage);
|
|
@@ -8502,21 +8315,13 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8502
8315
|
removeActiveLlmSpan(llm.span);
|
|
8503
8316
|
this.llmByKey.delete(key);
|
|
8504
8317
|
}
|
|
8318
|
+
this.promptByCallId.delete(callId);
|
|
8505
8319
|
const step = this.stepByKey.get(key);
|
|
8506
8320
|
if (!step) return;
|
|
8507
8321
|
if (typeof event.finishReason === "string") applyFinishReason(step.span, event.finishReason);
|
|
8508
8322
|
if (this.recordOutputs) {
|
|
8509
|
-
const
|
|
8510
|
-
if (
|
|
8511
|
-
outputPayload.text = event.text;
|
|
8512
|
-
step.span.setAttribute("ai.response.text", event.text);
|
|
8513
|
-
}
|
|
8514
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8515
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8516
|
-
outputPayload.toolCalls = normalizedToolCallsList;
|
|
8517
|
-
step.span.setAttribute("ai.response.toolCalls", serializeJSON$1(normalizedToolCallsList));
|
|
8518
|
-
}
|
|
8519
|
-
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);
|
|
8520
8325
|
}
|
|
8521
8326
|
step.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
8522
8327
|
step.span.end();
|
|
@@ -8529,7 +8334,12 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8529
8334
|
if (!op) return;
|
|
8530
8335
|
const span = getTracer().startSpan(`ai.llm ${event.provider ?? ""}:${event.modelId ?? ""}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, op.ctx);
|
|
8531
8336
|
span.setAttribute(require_dist.SPAN_TYPE, "LLM");
|
|
8337
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8532
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
|
+
}
|
|
8533
8343
|
this.llmByKey.set(stepKey(callId, 0), {
|
|
8534
8344
|
span,
|
|
8535
8345
|
textDeltas: []
|
|
@@ -8549,7 +8359,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8549
8359
|
}
|
|
8550
8360
|
applyUsageToSpan(llm.span, event.usage);
|
|
8551
8361
|
if (this.recordOutputs && typeof event.objectText === "string") {
|
|
8552
|
-
const outputMessages =
|
|
8362
|
+
const outputMessages = buildTextOutputMessages(event.objectText);
|
|
8553
8363
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8554
8364
|
}
|
|
8555
8365
|
llm.span.setStatus({ code: _opentelemetry_api.SpanStatusCode.OK });
|
|
@@ -8568,6 +8378,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8568
8378
|
const span = getTracer().startSpan(`ai.tool ${toolName ?? "unknown"}`, { kind: _opentelemetry_api.SpanKind.CLIENT }, parentCtx);
|
|
8569
8379
|
const spanCtx = _opentelemetry_api.trace.setSpan(parentCtx, span);
|
|
8570
8380
|
span.setAttribute(require_dist.SPAN_TYPE, "TOOL");
|
|
8381
|
+
span.setAttributes(buildAiSdkInstrumentationAttributes());
|
|
8571
8382
|
if (toolName) span.setAttribute("ai.toolCall.name", toolName);
|
|
8572
8383
|
span.setAttribute("ai.toolCall.id", toolCallId);
|
|
8573
8384
|
if (this.recordInputs && toolCall?.input !== void 0) {
|
|
@@ -8645,17 +8456,8 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8645
8456
|
if (typeof event.finishReason === "string") applyFinishReason(op.span, event.finishReason);
|
|
8646
8457
|
if (op.operationId === "ai.embed" || op.operationId === "ai.embedMany" || op.operationId === "ai.rerank") applyUsageToSpan(op.span, event.totalUsage ?? event.usage);
|
|
8647
8458
|
if (this.recordOutputs) {
|
|
8648
|
-
const
|
|
8649
|
-
if (
|
|
8650
|
-
op.span.setAttribute("ai.response.text", event.text);
|
|
8651
|
-
op.span.setAttribute(require_dist.SPAN_OUTPUT, event.text);
|
|
8652
|
-
}
|
|
8653
|
-
const normalizedToolCallsList = normalizeToolCalls(event.toolCalls);
|
|
8654
|
-
if (normalizedToolCallsList.length > 0) {
|
|
8655
|
-
const serialized = serializeJSON$1(normalizedToolCallsList);
|
|
8656
|
-
op.span.setAttribute("ai.response.toolCalls", serialized);
|
|
8657
|
-
if (!hasText) op.span.setAttribute(require_dist.SPAN_OUTPUT, serialized);
|
|
8658
|
-
}
|
|
8459
|
+
const outputMessages = buildVerbatimOutputMessages(Array.isArray(event.content) ? event.content : void 0);
|
|
8460
|
+
if (outputMessages) op.span.setAttribute(require_dist.SPAN_OUTPUT, outputMessages);
|
|
8659
8461
|
if (event.embedding !== void 0) {
|
|
8660
8462
|
const emb = event.embedding;
|
|
8661
8463
|
if (Array.isArray(emb) && Array.isArray(emb[0])) op.span.setAttribute("ai.embed.count", emb.length);
|
|
@@ -8759,7 +8561,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8759
8561
|
};
|
|
8760
8562
|
for (const [key, llm] of this.llmByKey) if (key.startsWith(prefix)) {
|
|
8761
8563
|
if (this.recordOutputs && llm.textDeltas.length > 0) {
|
|
8762
|
-
const outputMessages =
|
|
8564
|
+
const outputMessages = buildTextOutputMessages(llm.textDeltas.join(""));
|
|
8763
8565
|
if (outputMessages) llm.span.setAttribute("gen_ai.output.messages", outputMessages);
|
|
8764
8566
|
}
|
|
8765
8567
|
llm.span.end();
|
|
@@ -8777,6 +8579,7 @@ var LaminarAiSdkTelemetry = class {
|
|
|
8777
8579
|
for (const key of this.llmByKey.keys()) if (key.startsWith(prefix)) this.llmByKey.delete(key);
|
|
8778
8580
|
for (const [toolCallId, tool] of this.toolByCallId) if (tool.callId === callId) this.toolByCallId.delete(toolCallId);
|
|
8779
8581
|
for (const key of this.stepByKey.keys()) if (key.startsWith(prefix)) this.stepByKey.delete(key);
|
|
8582
|
+
this.promptByCallId.delete(callId);
|
|
8780
8583
|
return latest;
|
|
8781
8584
|
}
|
|
8782
8585
|
};
|