@tangle-network/agent-eval 0.135.4 → 0.137.0
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/CHANGELOG.md +56 -0
- package/README.md +4 -2
- package/dist/{agent-profile-cell-OhuTee9n.js → agent-profile-cell-CbfBm2g6.js} +2 -2
- package/dist/{agent-profile-cell-OhuTee9n.js.map → agent-profile-cell-CbfBm2g6.js.map} +1 -1
- package/dist/{agent-profile-cell-CCm3l2v2.d.ts → agent-profile-cell-Cw0PVwDr.d.ts} +2 -2
- package/dist/{agent-profile-cell-CCm3l2v2.d.ts.map → agent-profile-cell-Cw0PVwDr.d.ts.map} +1 -1
- package/dist/analyst/index.d.ts +139 -17
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +606 -4
- package/dist/analyst/index.js.map +1 -1
- package/dist/{analyze-runs-jjCmF8pU.js → analyze-runs-BScZvqMV.js} +6 -6
- package/dist/{analyze-runs-jjCmF8pU.js.map → analyze-runs-BScZvqMV.js.map} +1 -1
- package/dist/{analyze-runs-Cda5Xkj1.d.ts → analyze-runs-PVtnfjvA.d.ts} +6 -6
- package/dist/{analyze-runs-Cda5Xkj1.d.ts.map → analyze-runs-PVtnfjvA.d.ts.map} +1 -1
- package/dist/{baseline-BUeFcgrn.js → baseline-C-GocmIW.js} +2 -2
- package/dist/{baseline-BUeFcgrn.js.map → baseline-C-GocmIW.js.map} +1 -1
- package/dist/benchmark-CHX4orG7.d.ts +184 -0
- package/dist/benchmark-CHX4orG7.d.ts.map +1 -0
- package/dist/benchmark-YDrpumqB.js +414 -0
- package/dist/benchmark-YDrpumqB.js.map +1 -0
- package/dist/benchmarks/index.d.ts +1 -1
- package/dist/benchmarks/index.js +1 -1
- package/dist/{benchmarks-Dfgm9ts5.js → benchmarks-DCLkQOmc.js} +3 -3
- package/dist/{benchmarks-Dfgm9ts5.js.map → benchmarks-DCLkQOmc.js.map} +1 -1
- package/dist/builder-eval/index.js +2 -2
- package/dist/campaign/index.d.ts +6 -6
- package/dist/campaign/index.js +3 -3
- package/dist/{campaign-Dz8uQnhC.js → campaign-lgObcHFC.js} +212 -78
- package/dist/campaign-lgObcHFC.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/client-C8L6h6Wf.d.ts +202 -0
- package/dist/client-C8L6h6Wf.d.ts.map +1 -0
- package/dist/completion-verifier-DSyRNVzU.d.ts +240 -0
- package/dist/completion-verifier-DSyRNVzU.d.ts.map +1 -0
- package/dist/contract/index.d.ts +10 -9
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +10 -10
- package/dist/control.d.ts +2 -2
- package/dist/control.js +1 -1
- package/dist/{cost-ledger-DHAjwNj7.js → cost-ledger-D-5_-dhi.js} +2 -2
- package/dist/{cost-ledger-DHAjwNj7.js.map → cost-ledger-D-5_-dhi.js.map} +1 -1
- package/dist/{cost-ledger-fGS_u_O1.d.ts → cost-ledger-D2o6JOrL.d.ts} +2 -2
- package/dist/{cost-ledger-fGS_u_O1.d.ts.map → cost-ledger-D2o6JOrL.d.ts.map} +1 -1
- package/dist/{dataset-BvtnC8Dc.d.ts → dataset-v_Y5902-.d.ts} +2 -2
- package/dist/{dataset-BvtnC8Dc.d.ts.map → dataset-v_Y5902-.d.ts.map} +1 -1
- package/dist/{default-registry-CHmdy2An.js → default-registry-CLXbRt0f.js} +119 -38
- package/dist/default-registry-CLXbRt0f.js.map +1 -0
- package/dist/{default-registry-Brxr728w.d.ts → default-registry-Dc5D_Loc.d.ts} +63 -137
- package/dist/default-registry-Dc5D_Loc.d.ts.map +1 -0
- package/dist/{errors-8YnH8WlF.js → errors-D-LKuDhb.js} +8 -2
- package/dist/errors-D-LKuDhb.js.map +1 -0
- package/dist/{errors-CEk209JS.d.ts → errors-DkfjIDvD.d.ts} +9 -3
- package/dist/errors-DkfjIDvD.d.ts.map +1 -0
- package/dist/{eval-campaign-Cc8WZJ6b.js → eval-campaign-CHqfLnff.js} +6 -6
- package/dist/{eval-campaign-Cc8WZJ6b.js.map → eval-campaign-CHqfLnff.js.map} +1 -1
- package/dist/{extract-usage-DIQpN-ww.js → extract-usage-p-56bh8q.js} +3 -3
- package/dist/{extract-usage-DIQpN-ww.js.map → extract-usage-p-56bh8q.js.map} +1 -1
- package/dist/{feedback-trajectory-CVaeREXV.d.ts → feedback-trajectory-N_F0PwHz.d.ts} +90 -3
- package/dist/feedback-trajectory-N_F0PwHz.d.ts.map +1 -0
- package/dist/fuzz.d.ts +1 -1
- package/dist/fuzz.js +2 -2
- package/dist/hosted/index.d.ts +3 -2
- package/dist/hosted/index.d.ts.map +1 -1
- package/dist/index-BipJlj-C.d.ts +316 -0
- package/dist/index-BipJlj-C.d.ts.map +1 -0
- package/dist/{index-CQsJcqch.d.ts → index-BnP1QJUv.d.ts} +5 -5
- package/dist/{index-CQsJcqch.d.ts.map → index-BnP1QJUv.d.ts.map} +1 -1
- package/dist/{index-B4Fjfo5U.d.ts → index-C-Pr4OWg.d.ts} +88 -317
- package/dist/index-C-Pr4OWg.d.ts.map +1 -0
- package/dist/{index-DuhJaaiH.d.ts → index-DEb46kc6.d.ts} +2 -2
- package/dist/{index-DuhJaaiH.d.ts.map → index-DEb46kc6.d.ts.map} +1 -1
- package/dist/{index-C2fkZhv_.d.ts → index-DRNl6g_N.d.ts} +3 -3
- package/dist/{index-C2fkZhv_.d.ts.map → index-DRNl6g_N.d.ts.map} +1 -1
- package/dist/{index-AbhwHp0V.d.ts → index-U3RHOShi.d.ts} +2 -2
- package/dist/{index-AbhwHp0V.d.ts.map → index-U3RHOShi.d.ts.map} +1 -1
- package/dist/index.d.ts +29 -70
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +507 -33
- package/dist/index.js.map +1 -1
- package/dist/{client-DcvgkaZi.d.ts → insight-report-B9ooYH_g.d.ts} +5 -203
- package/dist/insight-report-B9ooYH_g.d.ts.map +1 -0
- package/dist/integrity-CCXTftiL.js +1360 -0
- package/dist/integrity-CCXTftiL.js.map +1 -0
- package/dist/{integrity-rmVhXWA7.d.ts → integrity-CKxosZ5Z.d.ts} +3 -3
- package/dist/{integrity-rmVhXWA7.d.ts.map → integrity-CKxosZ5Z.d.ts.map} +1 -1
- package/dist/{integrity-BzRbCHzi.js → integrity-fdt8XPAv.js} +2 -2
- package/dist/{integrity-BzRbCHzi.js.map → integrity-fdt8XPAv.js.map} +1 -1
- package/dist/ledger-core/index.d.ts +1 -1
- package/dist/ledger-core/index.js +1 -1
- package/dist/{ledger-core-DAKFKRzi.js → ledger-core-t6sItivm.js} +85 -85
- package/dist/{ledger-core-DAKFKRzi.js.map → ledger-core-t6sItivm.js.map} +1 -1
- package/dist/{llm-client-DHx8pzyJ.js → llm-client-DKB25jV8.js} +3 -3
- package/dist/{llm-client-DHx8pzyJ.js.map → llm-client-DKB25jV8.js.map} +1 -1
- package/dist/meta-eval/index.d.ts +2 -2
- package/dist/meta-eval/index.js +3 -3
- package/dist/{mint-DyRUc9k6.js → mint-Ctwk079K.js} +4 -4
- package/dist/{mint-DyRUc9k6.js.map → mint-Ctwk079K.js.map} +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/{paired-arms-BbFKrAU-.js → paired-arms-iZ08VFMN.js} +3 -3
- package/dist/{paired-arms-BbFKrAU-.js.map → paired-arms-iZ08VFMN.js.map} +1 -1
- package/dist/pipelines/index.js +2 -2
- package/dist/profile-cell.d.ts +1 -1
- package/dist/profile-cell.js +1 -1
- package/dist/{propose-review-control-SQ-n9-We.js → propose-review-control-DLXz4FCX.js} +2 -2
- package/dist/{propose-review-control-SQ-n9-We.js.map → propose-review-control-DLXz4FCX.js.map} +1 -1
- package/dist/registry-BdM7SuTr.d.ts +124 -0
- package/dist/registry-BdM7SuTr.d.ts.map +1 -0
- package/dist/{release-report-DooPguBc.js → release-report-B5XPBvAU.js} +4 -4
- package/dist/{release-report-DooPguBc.js.map → release-report-B5XPBvAU.js.map} +1 -1
- package/dist/{release-report-DpBxGGI1.d.ts → release-report-CofgVNZt.d.ts} +4 -4
- package/dist/{release-report-DpBxGGI1.d.ts.map → release-report-CofgVNZt.d.ts.map} +1 -1
- package/dist/{replay-C6wRg47C.js → replay-Bju0T8Ls.js} +248 -8
- package/dist/replay-Bju0T8Ls.js.map +1 -0
- package/dist/{replay-BRfMIs81.d.ts → replay-K8FaC0CB.d.ts} +227 -52
- package/dist/replay-K8FaC0CB.d.ts.map +1 -0
- package/dist/reporting.d.ts +4 -4
- package/dist/reporting.js +4 -4
- package/dist/{researcher-Doo95b50.d.ts → researcher-Da0Wj-bt.d.ts} +6 -7
- package/dist/researcher-Da0Wj-bt.d.ts.map +1 -0
- package/dist/{reward-hacking-D-QqXvg-.d.ts → reward-hacking-CQ3hTCO3.d.ts} +2 -2
- package/dist/{reward-hacking-D-QqXvg-.d.ts.map → reward-hacking-CQ3hTCO3.d.ts.map} +1 -1
- package/dist/{reward-hacking-a-kYs0-i.js → reward-hacking-GyN0kMd8.js} +3 -3
- package/dist/{reward-hacking-a-kYs0-i.js.map → reward-hacking-GyN0kMd8.js.map} +1 -1
- package/dist/rl.d.ts +6 -6
- package/dist/rl.js +9 -9
- package/dist/rollout/index.d.ts +1 -1
- package/dist/rollout/index.js +3 -3
- package/dist/{rollout-DLSUIWLu.js → rollout-DQFl0UXA.js} +2 -2
- package/dist/{rollout-DLSUIWLu.js.map → rollout-DQFl0UXA.js.map} +1 -1
- package/dist/{rubric-predictive-validity-BJf-8ejY.js → rubric-predictive-validity-BRR632r1.js} +2 -2
- package/dist/{rubric-predictive-validity-BJf-8ejY.js.map → rubric-predictive-validity-BRR632r1.js.map} +1 -1
- package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts → rubric-predictive-validity-C4sztLR3.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts.map → rubric-predictive-validity-C4sztLR3.d.ts.map} +1 -1
- package/dist/{run-evidence-DokQtX0-.d.ts → run-evidence-BDIircdA.d.ts} +3 -3
- package/dist/{run-evidence-DokQtX0-.d.ts.map → run-evidence-BDIircdA.d.ts.map} +1 -1
- package/dist/{run-record-DcObtIGh.d.ts → run-record-BPCa2rQ8.d.ts} +4 -4
- package/dist/{run-record-DcObtIGh.d.ts.map → run-record-BPCa2rQ8.d.ts.map} +1 -1
- package/dist/{run-record-BIwU2wdV.js → run-record-vRgqWmJw.js} +3 -3
- package/dist/{run-record-BIwU2wdV.js.map → run-record-vRgqWmJw.js.map} +1 -1
- package/dist/{semantic-concept-judge-Btozx3Vc.js → semantic-concept-judge-Bz64IckK.js} +4 -4
- package/dist/{semantic-concept-judge-Btozx3Vc.js.map → semantic-concept-judge-Bz64IckK.js.map} +1 -1
- package/dist/{server-Bz3WQJs6.js → server-KjXZZUDX.js} +3 -3
- package/dist/{server-Bz3WQJs6.js.map → server-KjXZZUDX.js.map} +1 -1
- package/dist/{skill-usage-BDQVPIG1.d.ts → skill-usage-CFDLLlhF.d.ts} +25 -47
- package/dist/skill-usage-CFDLLlhF.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-CwSYkv35.d.ts → skillopt-optimization-method-BpbnlvAZ.d.ts} +11 -12
- package/dist/skillopt-optimization-method-BpbnlvAZ.d.ts.map +1 -0
- package/dist/{skillopt-optimization-method-0UmPD6aP.js → skillopt-optimization-method-f4o9sUT4.js} +7 -7
- package/dist/{skillopt-optimization-method-0UmPD6aP.js.map → skillopt-optimization-method-f4o9sUT4.js.map} +1 -1
- package/dist/{statistics-CnGCLLqc.js → statistics-ByxzSiOM.js} +2 -2
- package/dist/{statistics-CnGCLLqc.js.map → statistics-ByxzSiOM.js.map} +1 -1
- package/dist/{statistics-CKOqre5S.d.ts → statistics-_7P642CN.d.ts} +2 -2
- package/dist/{statistics-CKOqre5S.d.ts.map → statistics-_7P642CN.d.ts.map} +1 -1
- package/dist/{summary-report-BEk8OFLs.js → summary-report-9A5y7EsK.js} +4 -4
- package/dist/{summary-report-BEk8OFLs.js.map → summary-report-9A5y7EsK.js.map} +1 -1
- package/dist/{summary-report-CPMINBqs.d.ts → summary-report-DHipz9Kx.d.ts} +3 -3
- package/dist/{summary-report-CPMINBqs.d.ts.map → summary-report-DHipz9Kx.d.ts.map} +1 -1
- package/dist/supervisor-run/index.d.ts +3 -2
- package/dist/supervisor-run/index.js +3 -2
- package/dist/{supervisor-run-B7lUGoyZ.js → supervisor-run-B2EWUmQY.js} +221 -519
- package/dist/supervisor-run-B2EWUmQY.js.map +1 -0
- package/dist/{test-graded-scenario-BsqWLmPt.js → test-graded-scenario-JHcKQNpq.js} +2 -2
- package/dist/{test-graded-scenario-BsqWLmPt.js.map → test-graded-scenario-JHcKQNpq.js.map} +1 -1
- package/dist/tools-DZk2Jn64.js +1876 -0
- package/dist/tools-DZk2Jn64.js.map +1 -0
- package/dist/traces.d.ts +6 -7
- package/dist/traces.js +5 -6
- package/dist/{types-DVjczBM9.d.ts → types-CKswbJGO.d.ts} +260 -6
- package/dist/types-CKswbJGO.d.ts.map +1 -0
- package/dist/{types-DiWLru6Z.d.ts → types-CTGbIm57.d.ts} +5 -5
- package/dist/{types-DiWLru6Z.d.ts.map → types-CTGbIm57.d.ts.map} +1 -1
- package/dist/types-CTvKfr5F.d.ts +804 -0
- package/dist/types-CTvKfr5F.d.ts.map +1 -0
- package/dist/{index-C61Wi7yg.d.ts → types-Dea6tiVI.d.ts} +47 -235
- package/dist/types-Dea6tiVI.d.ts.map +1 -0
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.js +1 -1
- package/docs/feedback-trajectories.md +100 -1
- package/docs/trace-analysis.md +374 -58
- package/package.json +7 -3
- package/dist/analyst-BkTS3C58.d.ts +0 -89
- package/dist/analyst-BkTS3C58.d.ts.map +0 -1
- package/dist/analyst-j5je5J7c.js +0 -152
- package/dist/analyst-j5je5J7c.js.map +0 -1
- package/dist/campaign-Dz8uQnhC.js.map +0 -1
- package/dist/client-DcvgkaZi.d.ts.map +0 -1
- package/dist/default-registry-Brxr728w.d.ts.map +0 -1
- package/dist/default-registry-CHmdy2An.js.map +0 -1
- package/dist/errors-8YnH8WlF.js.map +0 -1
- package/dist/errors-CEk209JS.d.ts.map +0 -1
- package/dist/feedback-trajectory-CVaeREXV.d.ts.map +0 -1
- package/dist/index-B4Fjfo5U.d.ts.map +0 -1
- package/dist/index-C61Wi7yg.d.ts.map +0 -1
- package/dist/llm-client-BiK4HW0u.d.ts +0 -290
- package/dist/llm-client-BiK4HW0u.d.ts.map +0 -1
- package/dist/raw-provider-sink-BU29Sh8h.d.ts +0 -134
- package/dist/raw-provider-sink-BU29Sh8h.d.ts.map +0 -1
- package/dist/replay-BRfMIs81.d.ts.map +0 -1
- package/dist/replay-C6wRg47C.js.map +0 -1
- package/dist/researcher-Doo95b50.d.ts.map +0 -1
- package/dist/skill-usage-BDQVPIG1.d.ts.map +0 -1
- package/dist/skillopt-optimization-method-CwSYkv35.d.ts.map +0 -1
- package/dist/store-CxJry_cs.d.ts +0 -229
- package/dist/store-CxJry_cs.d.ts.map +0 -1
- package/dist/supervisor-run-B7lUGoyZ.js.map +0 -1
- package/dist/tools-D8yTtNSN.js +0 -1190
- package/dist/tools-D8yTtNSN.js.map +0 -1
- package/dist/types-Cc3qbqzj.d.ts +0 -387
- package/dist/types-Cc3qbqzj.d.ts.map +0 -1
- package/dist/types-DVjczBM9.d.ts.map +0 -1
package/dist/tools-D8yTtNSN.js
DELETED
|
@@ -1,1190 +0,0 @@
|
|
|
1
|
-
import { a as NotFoundError, n as CaptureIntegrityError } from "./errors-8YnH8WlF.js";
|
|
2
|
-
import { INPUT_VALUE, LLM_MODEL_ATTR_KEYS, OPENINFERENCE_SPAN_KIND, OUTPUT_VALUE, SPAN_KIND_ATTR_KEYS, TOOL_ARGS_CAPTURED, TOOL_LATENCY_MS, TOOL_NAME, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
|
|
3
|
-
import { AxJSRuntime, agent, f, fn } from "@ax-llm/ax";
|
|
4
|
-
import { readFile, stat } from "node:fs/promises";
|
|
5
|
-
//#region src/trace/otlp-attributes.ts
|
|
6
|
-
/** Canonical OpenInference-over-OTLP attribute vocabulary used at the trace boundary. */
|
|
7
|
-
const TOOL_SPAN_ATTRIBUTE_KEYS = [
|
|
8
|
-
TOOL_NAME,
|
|
9
|
-
TOOL_ARGS_CAPTURED,
|
|
10
|
-
TOOL_LATENCY_MS,
|
|
11
|
-
INPUT_VALUE,
|
|
12
|
-
OUTPUT_VALUE
|
|
13
|
-
];
|
|
14
|
-
const EXPLICIT_SPAN_ROLES = /* @__PURE__ */ new Set([
|
|
15
|
-
"AGENT",
|
|
16
|
-
"CHAIN",
|
|
17
|
-
"EVALUATOR",
|
|
18
|
-
"GUARDRAIL",
|
|
19
|
-
"LLM",
|
|
20
|
-
"SPAN",
|
|
21
|
-
"TOOL"
|
|
22
|
-
]);
|
|
23
|
-
/**
|
|
24
|
-
* Classify a span once for both measurement and error accounting.
|
|
25
|
-
* An explicit OpenInference kind wins; untyped spans use the same tool and
|
|
26
|
-
* model signals in online and offline intake.
|
|
27
|
-
*/
|
|
28
|
-
function classifyOtlpSpanRole(input) {
|
|
29
|
-
const explicitKind = input.kind ?? firstStringAttribute(input.attributes, SPAN_KIND_ATTR_KEYS);
|
|
30
|
-
if (explicitKind) {
|
|
31
|
-
const normalized = explicitKind.toUpperCase();
|
|
32
|
-
if (EXPLICIT_SPAN_ROLES.has(normalized)) return normalized;
|
|
33
|
-
}
|
|
34
|
-
if (firstStringAttribute(input.attributes, TOOL_NAME_ATTR_KEYS) !== void 0 || /^(?:function|tool)[.:/]/i.test(input.name)) return "TOOL";
|
|
35
|
-
const spanType = input.attributes["span.type"];
|
|
36
|
-
if (typeof spanType === "string" && spanType.toLowerCase() === "llm_request" || /(?:^|[.:/_-])(?:chat[._-]?completions?|llm)(?:$|[.:/_-])/i.test(input.name) || firstStringAttribute(input.attributes, LLM_MODEL_ATTR_KEYS) !== void 0 || typeof input.attributes["gen_ai.operation.name"] === "string") return "LLM";
|
|
37
|
-
return "UNKNOWN";
|
|
38
|
-
}
|
|
39
|
-
function isOtlpModelCall(input) {
|
|
40
|
-
return classifyOtlpSpanRole(input) === "LLM";
|
|
41
|
-
}
|
|
42
|
-
function toolSpanOtlpAttributes(span) {
|
|
43
|
-
const argsCaptured = span.argsCaptured !== false;
|
|
44
|
-
const attributes = {
|
|
45
|
-
[TOOL_NAME]: span.toolName,
|
|
46
|
-
[TOOL_ARGS_CAPTURED]: argsCaptured
|
|
47
|
-
};
|
|
48
|
-
if (span.latencyMs !== void 0) attributes[TOOL_LATENCY_MS] = span.latencyMs;
|
|
49
|
-
if (argsCaptured) attributes[INPUT_VALUE] = stringifyTraceValue(span.args);
|
|
50
|
-
if (span.result !== void 0) attributes[OUTPUT_VALUE] = stringifyTraceValue(span.result);
|
|
51
|
-
return attributes;
|
|
52
|
-
}
|
|
53
|
-
function applyToolSpanOtlpAttributes(attributes, span) {
|
|
54
|
-
for (const key of TOOL_SPAN_ATTRIBUTE_KEYS) delete attributes[key];
|
|
55
|
-
Object.assign(attributes, toolSpanOtlpAttributes(span));
|
|
56
|
-
}
|
|
57
|
-
function traceSpanKindToOpenInferenceKind(kind) {
|
|
58
|
-
switch (kind) {
|
|
59
|
-
case "llm": return "LLM";
|
|
60
|
-
case "tool": return "TOOL";
|
|
61
|
-
case "retrieval": return "CHAIN";
|
|
62
|
-
case "judge": return "EVALUATOR";
|
|
63
|
-
case "sandbox": return "CHAIN";
|
|
64
|
-
case "agent": return "AGENT";
|
|
65
|
-
default: return "SPAN";
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function firstStringAttribute(attributes, keys) {
|
|
69
|
-
for (const key of keys) {
|
|
70
|
-
const value = attributes[key];
|
|
71
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function stringifyTraceValue(value) {
|
|
75
|
-
if (value === void 0) return "null";
|
|
76
|
-
if (typeof value === "string") return value;
|
|
77
|
-
try {
|
|
78
|
-
return JSON.stringify(value) ?? String(value);
|
|
79
|
-
} catch {
|
|
80
|
-
return String(value);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region src/trace-analyst/otlp-span.ts
|
|
85
|
-
/**
|
|
86
|
-
* Canonical OTLP-flat-line readers shared by every consumer of the
|
|
87
|
-
* OTLP-JSONL wire shape (one OTLP span per line; the form
|
|
88
|
-
* `flattenOtlpExportToNdjson` produces and the form AppWorld / HALO
|
|
89
|
-
* emit via their OpenInference OTLP exporter).
|
|
90
|
-
*
|
|
91
|
-
* `OtlpFileTraceStore` indexes spans with these; `otlpToRunRecords`
|
|
92
|
-
* aggregates spans into `RunRecord`s with the same readers. One parser,
|
|
93
|
-
* one vocabulary — a divergence between the analyst's view of a trace and
|
|
94
|
-
* the RunRecord projected from it is a class of bug this consolidation
|
|
95
|
-
* removes by construction.
|
|
96
|
-
*
|
|
97
|
-
* Vocabulary. The readers understand BOTH dialects that appear in the
|
|
98
|
-
* wild:
|
|
99
|
-
* - the substrate's own `llm.*` / `tool.*` / `span.kind` attributes
|
|
100
|
-
* (`flattenSpanAttributes` in `trace/otel.ts`), and
|
|
101
|
-
* - the OpenInference / inference-export attributes AppWorld / HALO
|
|
102
|
-
* emit (`openinference.span.kind`, `inference.observation_kind`,
|
|
103
|
-
* `inference.llm.input_tokens`, `llm.token_count.prompt`, …).
|
|
104
|
-
*
|
|
105
|
-
* Pure, no I/O.
|
|
106
|
-
*/
|
|
107
|
-
/**
|
|
108
|
-
* Project one parsed OTLP-JSONL object to `ProjectedOtlpSpan`, or `null`
|
|
109
|
-
* when the line is missing the mandatory `trace_id` + `span_id`.
|
|
110
|
-
*/
|
|
111
|
-
function projectOtlpFlatLine(raw) {
|
|
112
|
-
const trace_id = stringField(raw, "trace_id") ?? stringField(raw, "traceId");
|
|
113
|
-
const span_id = stringField(raw, "span_id") ?? stringField(raw, "spanId");
|
|
114
|
-
if (!trace_id || !span_id) return null;
|
|
115
|
-
const parent_id = normalizeParentSpanId(trace_id, span_id, stringField(raw, "parent_span_id") ?? stringField(raw, "parentSpanId") ?? null);
|
|
116
|
-
const name = stringField(raw, "name") ?? "unknown";
|
|
117
|
-
const start_time = stringField(raw, "start_time") ?? stringField(raw, "startTime") ?? "";
|
|
118
|
-
const end_time = stringField(raw, "end_time") ?? stringField(raw, "endTime") ?? start_time;
|
|
119
|
-
const status = readOtlpStatus(raw);
|
|
120
|
-
const attributes = extractOtlpAttributes(raw);
|
|
121
|
-
const service_name = asString(attributes["service.name"]) ?? asString(attributes["resource.attributes.service.name"]) ?? null;
|
|
122
|
-
const agent_name = asString(attributes["agent.name"]) ?? asString(attributes["inference.agent.name"]) ?? asString(attributes["inference.agent_name"]) ?? null;
|
|
123
|
-
const model_name = firstStringAttr(attributes, LLM_MODEL_ATTR_KEYS);
|
|
124
|
-
const tool_name = firstStringAttr(attributes, TOOL_NAME_ATTR_KEYS);
|
|
125
|
-
const kind = inferOtlpKind(attributes);
|
|
126
|
-
let duration_ms = 0;
|
|
127
|
-
if (start_time && end_time) {
|
|
128
|
-
const a = spanEpochMillis(start_time);
|
|
129
|
-
const b = spanEpochMillis(end_time);
|
|
130
|
-
if (a !== null && b !== null) duration_ms = Math.max(0, b - a);
|
|
131
|
-
}
|
|
132
|
-
return {
|
|
133
|
-
trace_id,
|
|
134
|
-
span_id,
|
|
135
|
-
parent_span_id: parent_id && parent_id.length > 0 ? parent_id : null,
|
|
136
|
-
name,
|
|
137
|
-
kind,
|
|
138
|
-
start_time,
|
|
139
|
-
end_time,
|
|
140
|
-
duration_ms,
|
|
141
|
-
status: status.code,
|
|
142
|
-
status_message: status.message,
|
|
143
|
-
service_name,
|
|
144
|
-
agent_name,
|
|
145
|
-
model_name,
|
|
146
|
-
tool_name,
|
|
147
|
-
attributes
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function normalizeParentSpanId(traceId, spanId, parentId) {
|
|
151
|
-
if (!parentId) return null;
|
|
152
|
-
const prefix = `${traceId}:`;
|
|
153
|
-
return spanId.startsWith(prefix) && !parentId.startsWith(prefix) ? `${prefix}${parentId}` : parentId;
|
|
154
|
-
}
|
|
155
|
-
function readOtlpStatus(raw) {
|
|
156
|
-
const status = raw.status;
|
|
157
|
-
if (status && typeof status === "object" && !Array.isArray(status)) {
|
|
158
|
-
const codeRaw = status.code;
|
|
159
|
-
const code = codeRaw === "STATUS_CODE_OK" || codeRaw === "OK" ? "OK" : codeRaw === "STATUS_CODE_ERROR" || codeRaw === "ERROR" ? "ERROR" : "UNSET";
|
|
160
|
-
const messageRaw = status.message;
|
|
161
|
-
return {
|
|
162
|
-
code,
|
|
163
|
-
message: typeof messageRaw === "string" && messageRaw.length > 0 ? messageRaw : void 0
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
return {
|
|
167
|
-
code: "UNSET",
|
|
168
|
-
message: void 0
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function inferOtlpKind(attrs) {
|
|
172
|
-
const opik = firstStringAttr(attrs, SPAN_KIND_ATTR_KEYS);
|
|
173
|
-
if (opik) {
|
|
174
|
-
const upper = opik.toUpperCase();
|
|
175
|
-
if (upper === "AGENT" || upper === "LLM" || upper === "TOOL" || upper === "CHAIN" || upper === "EVALUATOR" || upper === "GUARDRAIL" || upper === "SPAN") return upper;
|
|
176
|
-
}
|
|
177
|
-
return "UNKNOWN";
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Flatten OTLP `attributes` + `resource.attributes` into a single
|
|
181
|
-
* dotted-key map. Span attributes override resource attributes when keys
|
|
182
|
-
* overlap. Nested objects/arrays are preserved as-is.
|
|
183
|
-
*/
|
|
184
|
-
function extractOtlpAttributes(raw) {
|
|
185
|
-
const out = {};
|
|
186
|
-
const resource = raw.resource;
|
|
187
|
-
if (resource && typeof resource === "object" && !Array.isArray(resource)) {
|
|
188
|
-
const ra = resource.attributes;
|
|
189
|
-
if (ra && typeof ra === "object" && !Array.isArray(ra)) for (const [k, v] of Object.entries(ra)) out[k] = v;
|
|
190
|
-
}
|
|
191
|
-
const spanAttrs = raw.attributes;
|
|
192
|
-
if (spanAttrs && typeof spanAttrs === "object" && !Array.isArray(spanAttrs)) for (const [k, v] of Object.entries(spanAttrs)) out[k] = v;
|
|
193
|
-
return out;
|
|
194
|
-
}
|
|
195
|
-
function stringField(raw, key) {
|
|
196
|
-
const v = raw[key];
|
|
197
|
-
return typeof v === "string" ? v : void 0;
|
|
198
|
-
}
|
|
199
|
-
function asString(v) {
|
|
200
|
-
return typeof v === "string" && v.length > 0 ? v : null;
|
|
201
|
-
}
|
|
202
|
-
/** First non-empty string value across a list of candidate attribute keys. */
|
|
203
|
-
function firstStringAttr(attrs, keys) {
|
|
204
|
-
for (const k of keys) {
|
|
205
|
-
const s = asString(attrs[k]);
|
|
206
|
-
if (s !== null) return s;
|
|
207
|
-
}
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
/**
|
|
211
|
-
* Parse a span timestamp to epoch millis, or null when empty/unparseable. The
|
|
212
|
-
* OTLP readers accept BOTH ISO-8601 and epoch-millis-string dialects, so raw
|
|
213
|
-
* string comparison (`<`, `localeCompare`) mis-orders across dialects and
|
|
214
|
-
* `Date.parse` returns NaN for a bare epoch-millis string.
|
|
215
|
-
*/
|
|
216
|
-
function spanEpochMillis(ts) {
|
|
217
|
-
if (!ts) return null;
|
|
218
|
-
if (/^\d+$/.test(ts)) return Number(ts);
|
|
219
|
-
const n = Date.parse(ts);
|
|
220
|
-
return Number.isNaN(n) ? null : n;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Order comparator for span timestamps across mixed ISO/epoch dialects.
|
|
224
|
-
* Unparseable timestamps sort as epoch 0 (earliest), never NaN (which would
|
|
225
|
-
* make the sort non-deterministic).
|
|
226
|
-
*/
|
|
227
|
-
function compareSpanTime(a, b) {
|
|
228
|
-
return (spanEpochMillis(a) ?? 0) - (spanEpochMillis(b) ?? 0);
|
|
229
|
-
}
|
|
230
|
-
//#endregion
|
|
231
|
-
//#region src/trace/otlp-flat.ts
|
|
232
|
-
function createOtlpFlatLine(input) {
|
|
233
|
-
return {
|
|
234
|
-
trace_id: input.traceId,
|
|
235
|
-
span_id: input.spanId,
|
|
236
|
-
parent_span_id: input.parentSpanId,
|
|
237
|
-
name: input.name,
|
|
238
|
-
kind: input.kind,
|
|
239
|
-
start_time: input.startTime,
|
|
240
|
-
end_time: input.endTime,
|
|
241
|
-
status: {
|
|
242
|
-
code: input.statusCode,
|
|
243
|
-
...input.statusMessage !== void 0 ? { message: input.statusMessage } : {}
|
|
244
|
-
},
|
|
245
|
-
resource: input.resource,
|
|
246
|
-
attributes: input.attributes,
|
|
247
|
-
...input.events && input.events.length > 0 ? { events: input.events } : {}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
/** Map the canonical trace status while letting each caller choose its legacy default. */
|
|
251
|
-
function spanStatusToOtlp(status, error, defaultCode) {
|
|
252
|
-
if (status === "error" || error) return "STATUS_CODE_ERROR";
|
|
253
|
-
if (status === "ok") return "STATUS_CODE_OK";
|
|
254
|
-
return defaultCode;
|
|
255
|
-
}
|
|
256
|
-
/** Convert epoch milliseconds, returning `undefined` for invalid or out-of-range values. */
|
|
257
|
-
function epochMillisToIso(value) {
|
|
258
|
-
if (!Number.isFinite(value)) return void 0;
|
|
259
|
-
try {
|
|
260
|
-
return new Date(value).toISOString();
|
|
261
|
-
} catch {
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
//#endregion
|
|
266
|
-
//#region src/trace-analyst/store.ts
|
|
267
|
-
/** Compile a regex with the same anchoring + flags semantics across
|
|
268
|
-
* implementations. Throws on invalid pattern — callers should surface
|
|
269
|
-
* that to the agent so it can refine instead of looping. */
|
|
270
|
-
function compileSearchRegex(pattern) {
|
|
271
|
-
let source = pattern;
|
|
272
|
-
let flags = "m";
|
|
273
|
-
if (source.startsWith("(?i)")) {
|
|
274
|
-
source = source.slice(4);
|
|
275
|
-
flags += "i";
|
|
276
|
-
}
|
|
277
|
-
return new RegExp(source, flags);
|
|
278
|
-
}
|
|
279
|
-
/** Truncate string payload deterministically for tool responses.
|
|
280
|
-
* Marker is parseable so downstream consumers can detect truncation
|
|
281
|
-
* and decide whether to fetch surgically. */
|
|
282
|
-
function truncateForBudget(value, byteCap) {
|
|
283
|
-
const original = Buffer.byteLength(value, "utf8");
|
|
284
|
-
if (original <= byteCap) return value;
|
|
285
|
-
const ratio = byteCap / original;
|
|
286
|
-
let cut = Math.max(0, Math.floor(value.length * ratio));
|
|
287
|
-
while (cut > 0 && Buffer.byteLength(value.slice(0, cut), "utf8") > byteCap) cut -= 1;
|
|
288
|
-
return `${value.slice(0, cut)}\n[trace-analyst truncated: original ${original} bytes]`;
|
|
289
|
-
}
|
|
290
|
-
//#endregion
|
|
291
|
-
//#region src/trace-analyst/types.ts
|
|
292
|
-
const DEFAULT_TRACE_ANALYST_BUDGETS = {
|
|
293
|
-
perCallByteCeiling: 15e4,
|
|
294
|
-
perAttributeViewBudget: 4096,
|
|
295
|
-
perAttributeSpanBudget: 16384,
|
|
296
|
-
perMatchTextBudget: 1024
|
|
297
|
-
};
|
|
298
|
-
/** Marker substituted in place of truncated string payloads. Callers
|
|
299
|
-
* parsing tool output can detect it deterministically. */
|
|
300
|
-
const TRACE_ANALYST_TRUNCATION_MARKER_PREFIX = "[trace-analyst truncated:";
|
|
301
|
-
//#endregion
|
|
302
|
-
//#region src/trace-analyst/store-otlp.ts
|
|
303
|
-
/**
|
|
304
|
-
* `OtlpFileTraceStore` — read-only OTLP-JSONL trace store for the
|
|
305
|
-
* trace-analyst.
|
|
306
|
-
*
|
|
307
|
-
* Wire shape. Each line of the input file is one OTLP-shaped span. The
|
|
308
|
-
* store understands flattened OTLP JSONL plus the OpenInference vocab.
|
|
309
|
-
* We project upstream's full
|
|
310
|
-
* span shape down to `TraceAnalystSpan` lazily — full materialisation
|
|
311
|
-
* only happens for the spans the agent actually requests.
|
|
312
|
-
*
|
|
313
|
-
* Indexing. On first read the store builds an in-memory index keyed
|
|
314
|
-
* by `trace_id` carrying:
|
|
315
|
-
* - byte offsets + lengths for each span line (for surgical reads
|
|
316
|
-
* without re-parsing the whole file)
|
|
317
|
-
* - a `TraceAnalystTraceSummary` rollup
|
|
318
|
-
* - sets of services / agents / models / tools / has_errors
|
|
319
|
-
* - byte size of the trace's JSONL slab
|
|
320
|
-
*
|
|
321
|
-
* Memory bound. The index keeps span metadata only — names, kinds,
|
|
322
|
-
* offsets, status. Attribute payloads stay on disk until requested.
|
|
323
|
-
* For a 50MB JSONL with 50k spans, the index is ~5MB.
|
|
324
|
-
*
|
|
325
|
-
* Concurrency. The store builds the index once on first read and
|
|
326
|
-
* caches it. Subsequent reads reuse the index. The file is opened on
|
|
327
|
-
* each read; we never hold a long-lived FD.
|
|
328
|
-
*/
|
|
329
|
-
/** Lines indexed between event-loop yields. Bounded synchronous work per
|
|
330
|
-
* tick keeps the index build from starving other tasks on large files
|
|
331
|
-
* while staying coarse enough that the yields are cheap. */
|
|
332
|
-
const INDEX_YIELD_LINES = 5e3;
|
|
333
|
-
/** Hand control back to the event loop without busy-waiting. */
|
|
334
|
-
function yieldToEventLoop() {
|
|
335
|
-
return new Promise((resolve) => setImmediate(resolve));
|
|
336
|
-
}
|
|
337
|
-
var BufferedOtlpTraceStore = class {
|
|
338
|
-
perAttributeViewBudget;
|
|
339
|
-
perAttributeSpanBudget;
|
|
340
|
-
perCallByteCeiling;
|
|
341
|
-
perMatchTextBudget;
|
|
342
|
-
indexPromise;
|
|
343
|
-
/** Cached UTF-8 buffer. Every read needs slice access, so each source is
|
|
344
|
-
* materialised once. */
|
|
345
|
-
bufferPromise;
|
|
346
|
-
constructor(opts) {
|
|
347
|
-
this.perAttributeViewBudget = opts.perAttributeViewBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeViewBudget;
|
|
348
|
-
this.perAttributeSpanBudget = opts.perAttributeSpanBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perAttributeSpanBudget;
|
|
349
|
-
this.perCallByteCeiling = opts.perCallByteCeiling ?? DEFAULT_TRACE_ANALYST_BUDGETS.perCallByteCeiling;
|
|
350
|
-
this.perMatchTextBudget = opts.perMatchTextBudget ?? DEFAULT_TRACE_ANALYST_BUDGETS.perMatchTextBudget;
|
|
351
|
-
}
|
|
352
|
-
async getOverview(filters) {
|
|
353
|
-
const idx = await this.index();
|
|
354
|
-
const matched = await this.matchedTraces(idx, filters);
|
|
355
|
-
const services = /* @__PURE__ */ new Set();
|
|
356
|
-
const agents = /* @__PURE__ */ new Set();
|
|
357
|
-
const models = /* @__PURE__ */ new Set();
|
|
358
|
-
const tools = /* @__PURE__ */ new Set();
|
|
359
|
-
let rawBytes = 0;
|
|
360
|
-
let earliest = null;
|
|
361
|
-
let latest = null;
|
|
362
|
-
let errorTraceCount = 0;
|
|
363
|
-
let errorSpanCount = 0;
|
|
364
|
-
const clusters = /* @__PURE__ */ new Map();
|
|
365
|
-
for (const t of matched) {
|
|
366
|
-
if (t.service_name) services.add(t.service_name);
|
|
367
|
-
if (t.agent_name) agents.add(t.agent_name);
|
|
368
|
-
for (const m of t.models) models.add(m);
|
|
369
|
-
for (const tn of t.tools) tools.add(tn);
|
|
370
|
-
rawBytes += t.raw_jsonl_bytes;
|
|
371
|
-
if (!earliest || compareSpanTime(t.start_time, earliest) < 0) earliest = t.start_time;
|
|
372
|
-
if (!latest || compareSpanTime(t.end_time, latest) > 0) latest = t.end_time;
|
|
373
|
-
if (t.has_errors) {
|
|
374
|
-
errorTraceCount += 1;
|
|
375
|
-
for (const s of t.spans) {
|
|
376
|
-
if (s.status !== "ERROR") continue;
|
|
377
|
-
errorSpanCount += 1;
|
|
378
|
-
accumulateErrorCluster(clusters, t.trace_id, s);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
const sample_trace_ids = matched.slice(0, 20).map((t) => t.trace_id);
|
|
383
|
-
return {
|
|
384
|
-
total_traces: matched.length,
|
|
385
|
-
raw_jsonl_bytes: rawBytes,
|
|
386
|
-
services: [...services].sort(),
|
|
387
|
-
agents: [...agents].sort(),
|
|
388
|
-
models: [...models].sort(),
|
|
389
|
-
tool_names: [...tools].sort(),
|
|
390
|
-
sample_trace_ids,
|
|
391
|
-
errors: {
|
|
392
|
-
trace_count: errorTraceCount,
|
|
393
|
-
span_count: errorSpanCount
|
|
394
|
-
},
|
|
395
|
-
error_clusters: finalizeErrorClusters(clusters, errorTraceCount),
|
|
396
|
-
time_range: earliest && latest ? {
|
|
397
|
-
earliest,
|
|
398
|
-
latest
|
|
399
|
-
} : null
|
|
400
|
-
};
|
|
401
|
-
}
|
|
402
|
-
async queryTraces(opts) {
|
|
403
|
-
if (!Number.isInteger(opts.limit) || opts.limit < 1 || opts.limit > 200) throw new RangeError(`queryTraces.limit must be 1..200, got ${opts.limit}`);
|
|
404
|
-
const offset = opts.offset ?? 0;
|
|
405
|
-
if (!Number.isInteger(offset) || offset < 0) throw new RangeError(`queryTraces.offset must be >=0, got ${offset}`);
|
|
406
|
-
const idx = await this.index();
|
|
407
|
-
const matched = await this.matchedTraces(idx, opts.filters);
|
|
408
|
-
const slice = matched.slice(offset, offset + opts.limit);
|
|
409
|
-
return {
|
|
410
|
-
traces: slice.map((t) => this.toSummary(t)),
|
|
411
|
-
total: matched.length,
|
|
412
|
-
has_more: offset + slice.length < matched.length
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
async countTraces(filters) {
|
|
416
|
-
const idx = await this.index();
|
|
417
|
-
return (await this.matchedTraces(idx, filters)).length;
|
|
418
|
-
}
|
|
419
|
-
async viewTrace(opts) {
|
|
420
|
-
const trace = (await this.index()).byTrace.get(opts.trace_id);
|
|
421
|
-
if (!trace) throw new TraceNotFoundError(opts.trace_id);
|
|
422
|
-
const cap = opts.per_attribute_byte_cap ?? this.perAttributeViewBudget;
|
|
423
|
-
const buf = await this.buffer();
|
|
424
|
-
const spans = [];
|
|
425
|
-
let runningBytes = 0;
|
|
426
|
-
let span_response_bytes_max = 0;
|
|
427
|
-
const counter = { value: 0 };
|
|
428
|
-
for (const s of trace.spans) {
|
|
429
|
-
const projected = this.projectSpan(buf, trace.trace_id, s, cap, counter);
|
|
430
|
-
const bytes = Buffer.byteLength(JSON.stringify(projected), "utf8");
|
|
431
|
-
span_response_bytes_max = Math.max(span_response_bytes_max, bytes);
|
|
432
|
-
runningBytes += bytes;
|
|
433
|
-
if (runningBytes > this.perCallByteCeiling) return {
|
|
434
|
-
trace_id: trace.trace_id,
|
|
435
|
-
oversized: this.buildOversizedSummary(trace, span_response_bytes_max)
|
|
436
|
-
};
|
|
437
|
-
spans.push(projected);
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
trace_id: trace.trace_id,
|
|
441
|
-
spans
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
async viewSpans(opts) {
|
|
445
|
-
const trace = (await this.index()).byTrace.get(opts.trace_id);
|
|
446
|
-
if (!trace) throw new TraceNotFoundError(opts.trace_id);
|
|
447
|
-
if (opts.span_ids.length === 0) return {
|
|
448
|
-
trace_id: trace.trace_id,
|
|
449
|
-
spans: [],
|
|
450
|
-
missing_span_ids: [],
|
|
451
|
-
truncated_attribute_count: 0
|
|
452
|
-
};
|
|
453
|
-
if (opts.span_ids.length > 100) throw new RangeError(`viewSpans.span_ids cap is 100, got ${opts.span_ids.length}`);
|
|
454
|
-
const cap = opts.per_attribute_byte_cap ?? this.perAttributeSpanBudget;
|
|
455
|
-
const wantSet = new Set(opts.span_ids);
|
|
456
|
-
const found = trace.spans.filter((s) => wantSet.has(s.span_id));
|
|
457
|
-
const missing = opts.span_ids.filter((id) => !found.some((f) => f.span_id === id));
|
|
458
|
-
const buf = await this.buffer();
|
|
459
|
-
const spans = [];
|
|
460
|
-
const counter = { value: 0 };
|
|
461
|
-
let runningBytes = 0;
|
|
462
|
-
for (const s of found) {
|
|
463
|
-
const projected = this.projectSpan(buf, trace.trace_id, s, cap, counter);
|
|
464
|
-
const bytes = Buffer.byteLength(JSON.stringify(projected), "utf8");
|
|
465
|
-
runningBytes += bytes;
|
|
466
|
-
if (runningBytes > this.perCallByteCeiling) break;
|
|
467
|
-
spans.push(projected);
|
|
468
|
-
}
|
|
469
|
-
return {
|
|
470
|
-
trace_id: trace.trace_id,
|
|
471
|
-
spans,
|
|
472
|
-
missing_span_ids: missing,
|
|
473
|
-
truncated_attribute_count: counter.value
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
async searchTrace(opts) {
|
|
477
|
-
const max_matches = opts.max_matches ?? 50;
|
|
478
|
-
if (!Number.isInteger(max_matches) || max_matches < 1 || max_matches > 500) throw new RangeError(`searchTrace.max_matches must be 1..500, got ${max_matches}`);
|
|
479
|
-
const trace = (await this.index()).byTrace.get(opts.trace_id);
|
|
480
|
-
if (!trace) throw new TraceNotFoundError(opts.trace_id);
|
|
481
|
-
const re = compileSearchRegex(opts.regex_pattern);
|
|
482
|
-
const buf = await this.buffer();
|
|
483
|
-
const hits = [];
|
|
484
|
-
let total = 0;
|
|
485
|
-
let capped = false;
|
|
486
|
-
for (const s of trace.spans) {
|
|
487
|
-
const remaining = max_matches - hits.length;
|
|
488
|
-
const localHits = await this.scanSpanForMatches(buf, trace.trace_id, s, re, this.perMatchTextBudget, remaining);
|
|
489
|
-
total += localHits.total;
|
|
490
|
-
for (const h of localHits.records) {
|
|
491
|
-
if (hits.length >= max_matches) break;
|
|
492
|
-
hits.push(h);
|
|
493
|
-
}
|
|
494
|
-
if (hits.length >= max_matches) {
|
|
495
|
-
capped = true;
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
return {
|
|
500
|
-
trace_id: trace.trace_id,
|
|
501
|
-
hits,
|
|
502
|
-
total_matches: capped ? hits.length : total,
|
|
503
|
-
has_more: capped || total > hits.length
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
async searchSpan(opts) {
|
|
507
|
-
const max_matches = opts.max_matches ?? 50;
|
|
508
|
-
if (!Number.isInteger(max_matches) || max_matches < 1 || max_matches > 500) throw new RangeError(`searchSpan.max_matches must be 1..500, got ${max_matches}`);
|
|
509
|
-
const trace = (await this.index()).byTrace.get(opts.trace_id);
|
|
510
|
-
if (!trace) throw new TraceNotFoundError(opts.trace_id);
|
|
511
|
-
const span = trace.spans.find((s) => s.span_id === opts.span_id);
|
|
512
|
-
if (!span) throw new SpanNotFoundError(opts.trace_id, opts.span_id);
|
|
513
|
-
const re = compileSearchRegex(opts.regex_pattern);
|
|
514
|
-
const buf = await this.buffer();
|
|
515
|
-
const localHits = await this.scanSpanForMatches(buf, trace.trace_id, span, re, this.perMatchTextBudget, max_matches);
|
|
516
|
-
return {
|
|
517
|
-
trace_id: trace.trace_id,
|
|
518
|
-
span_id: span.span_id,
|
|
519
|
-
hits: localHits.records,
|
|
520
|
-
total_matches: localHits.total,
|
|
521
|
-
has_more: localHits.total > localHits.records.length
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
/** Force the index to materialise. Useful to amortise startup cost
|
|
525
|
-
* before the first agent call. */
|
|
526
|
-
async ensureIndexed() {
|
|
527
|
-
await this.index();
|
|
528
|
-
}
|
|
529
|
-
async buffer() {
|
|
530
|
-
if (!this.bufferPromise) this.bufferPromise = this.readBuffer();
|
|
531
|
-
return this.bufferPromise;
|
|
532
|
-
}
|
|
533
|
-
async index() {
|
|
534
|
-
if (!this.indexPromise) this.indexPromise = this.buildIndex();
|
|
535
|
-
return this.indexPromise;
|
|
536
|
-
}
|
|
537
|
-
async buildIndex() {
|
|
538
|
-
const buf = await this.buffer();
|
|
539
|
-
const byTrace = /* @__PURE__ */ new Map();
|
|
540
|
-
let cursor = 0;
|
|
541
|
-
let sinceYield = 0;
|
|
542
|
-
while (cursor < buf.length) {
|
|
543
|
-
if (++sinceYield >= INDEX_YIELD_LINES) {
|
|
544
|
-
sinceYield = 0;
|
|
545
|
-
await yieldToEventLoop();
|
|
546
|
-
}
|
|
547
|
-
const newlineIndex = buf.indexOf(10, cursor);
|
|
548
|
-
const lineEnd = newlineIndex === -1 ? buf.length : newlineIndex;
|
|
549
|
-
const lineLength = lineEnd - cursor;
|
|
550
|
-
if (lineLength === 0) {
|
|
551
|
-
cursor = lineEnd + 1;
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
const lineSlice = buf.subarray(cursor, lineEnd).toString("utf8");
|
|
555
|
-
const lineOffset = cursor;
|
|
556
|
-
cursor = lineEnd + 1;
|
|
557
|
-
let parsed;
|
|
558
|
-
try {
|
|
559
|
-
parsed = JSON.parse(lineSlice);
|
|
560
|
-
} catch {
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
if (!parsed || typeof parsed !== "object") continue;
|
|
564
|
-
const span = projectOtlpFlatLine(parsed);
|
|
565
|
-
if (!span) continue;
|
|
566
|
-
let entry = byTrace.get(span.trace_id);
|
|
567
|
-
if (!entry) {
|
|
568
|
-
entry = {
|
|
569
|
-
trace_id: span.trace_id,
|
|
570
|
-
service_name: span.service_name,
|
|
571
|
-
agent_name: span.agent_name,
|
|
572
|
-
span_count: 0,
|
|
573
|
-
has_errors: false,
|
|
574
|
-
start_time: span.start_time,
|
|
575
|
-
end_time: span.end_time,
|
|
576
|
-
duration_ms: 0,
|
|
577
|
-
raw_jsonl_bytes: 0,
|
|
578
|
-
models: /* @__PURE__ */ new Set(),
|
|
579
|
-
tools: /* @__PURE__ */ new Set(),
|
|
580
|
-
spans: []
|
|
581
|
-
};
|
|
582
|
-
byTrace.set(span.trace_id, entry);
|
|
583
|
-
} else {
|
|
584
|
-
if (!entry.service_name && span.service_name) entry.service_name = span.service_name;
|
|
585
|
-
if (!entry.agent_name && span.agent_name) entry.agent_name = span.agent_name;
|
|
586
|
-
}
|
|
587
|
-
const indexEntry = {
|
|
588
|
-
span_id: span.span_id,
|
|
589
|
-
parent_span_id: span.parent_span_id,
|
|
590
|
-
name: span.name,
|
|
591
|
-
kind: span.kind,
|
|
592
|
-
start_time: span.start_time,
|
|
593
|
-
end_time: span.end_time,
|
|
594
|
-
duration_ms: span.duration_ms,
|
|
595
|
-
status: span.status,
|
|
596
|
-
status_message: span.status_message,
|
|
597
|
-
service_name: span.service_name,
|
|
598
|
-
agent_name: span.agent_name,
|
|
599
|
-
model_name: span.model_name,
|
|
600
|
-
tool_name: span.tool_name,
|
|
601
|
-
line_byte_offset: lineOffset,
|
|
602
|
-
line_byte_length: lineLength
|
|
603
|
-
};
|
|
604
|
-
entry.spans.push(indexEntry);
|
|
605
|
-
entry.span_count += 1;
|
|
606
|
-
entry.raw_jsonl_bytes += lineLength + 1;
|
|
607
|
-
if (span.status === "ERROR") entry.has_errors = true;
|
|
608
|
-
if (compareSpanTime(span.start_time, entry.start_time) < 0) entry.start_time = span.start_time;
|
|
609
|
-
if (compareSpanTime(span.end_time, entry.end_time) > 0) entry.end_time = span.end_time;
|
|
610
|
-
if (span.model_name) entry.models.add(span.model_name);
|
|
611
|
-
if (span.tool_name) entry.tools.add(span.tool_name);
|
|
612
|
-
}
|
|
613
|
-
let totalRawBytes = 0;
|
|
614
|
-
for (const t of byTrace.values()) {
|
|
615
|
-
totalRawBytes += t.raw_jsonl_bytes;
|
|
616
|
-
t.spans.sort((a, b) => compareSpanTime(a.start_time, b.start_time) || a.line_byte_offset - b.line_byte_offset);
|
|
617
|
-
const startMs = spanEpochMillis(t.start_time);
|
|
618
|
-
const endMs = spanEpochMillis(t.end_time);
|
|
619
|
-
t.duration_ms = startMs === null || endMs === null ? 0 : Math.max(0, endMs - startMs);
|
|
620
|
-
}
|
|
621
|
-
const sortedTraceIds = [...byTrace.keys()].sort();
|
|
622
|
-
return {
|
|
623
|
-
byTrace,
|
|
624
|
-
totalRawBytes,
|
|
625
|
-
sortedTraceIds
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
async matchedTraces(idx, filters) {
|
|
629
|
-
const traces = idx.sortedTraceIds.map((id) => idx.byTrace.get(id)).filter(isPresent);
|
|
630
|
-
if (!filters) return traces;
|
|
631
|
-
const indexedFiltered = traces.filter((t) => {
|
|
632
|
-
if (filters.has_errors !== void 0 && t.has_errors !== filters.has_errors) return false;
|
|
633
|
-
if (filters.service_names && filters.service_names.length > 0) {
|
|
634
|
-
if (!t.service_name || !filters.service_names.includes(t.service_name)) return false;
|
|
635
|
-
}
|
|
636
|
-
if (filters.agent_names && filters.agent_names.length > 0) {
|
|
637
|
-
if (!t.agent_name || !filters.agent_names.includes(t.agent_name)) return false;
|
|
638
|
-
}
|
|
639
|
-
if (filters.model_names && filters.model_names.length > 0) {
|
|
640
|
-
if (![...t.models].some((m) => filters.model_names.includes(m))) return false;
|
|
641
|
-
}
|
|
642
|
-
if (filters.tool_names && filters.tool_names.length > 0) {
|
|
643
|
-
if (![...t.tools].some((tn) => filters.tool_names.includes(tn))) return false;
|
|
644
|
-
}
|
|
645
|
-
if (filters.start_time_after && t.start_time < filters.start_time_after) return false;
|
|
646
|
-
if (filters.start_time_before && t.start_time > filters.start_time_before) return false;
|
|
647
|
-
return true;
|
|
648
|
-
});
|
|
649
|
-
if (!filters.regex_pattern) return indexedFiltered;
|
|
650
|
-
const re = compileSearchRegex(filters.regex_pattern);
|
|
651
|
-
const buf = await this.buffer();
|
|
652
|
-
const out = [];
|
|
653
|
-
for (const t of indexedFiltered) {
|
|
654
|
-
let matched = false;
|
|
655
|
-
for (const s of t.spans) {
|
|
656
|
-
const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length);
|
|
657
|
-
if (re.test(slice.toString("utf8"))) {
|
|
658
|
-
matched = true;
|
|
659
|
-
break;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
if (matched) out.push(t);
|
|
663
|
-
}
|
|
664
|
-
return out;
|
|
665
|
-
}
|
|
666
|
-
toSummary(t) {
|
|
667
|
-
return {
|
|
668
|
-
trace_id: t.trace_id,
|
|
669
|
-
service_name: t.service_name,
|
|
670
|
-
agent_name: t.agent_name,
|
|
671
|
-
span_count: t.span_count,
|
|
672
|
-
has_errors: t.has_errors,
|
|
673
|
-
start_time: t.start_time,
|
|
674
|
-
end_time: t.end_time,
|
|
675
|
-
duration_ms: t.duration_ms,
|
|
676
|
-
raw_jsonl_bytes: t.raw_jsonl_bytes,
|
|
677
|
-
models: [...t.models].sort(),
|
|
678
|
-
tools: [...t.tools].sort()
|
|
679
|
-
};
|
|
680
|
-
}
|
|
681
|
-
projectSpan(buf, trace_id, s, perAttrCap, counter) {
|
|
682
|
-
const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length).toString("utf8");
|
|
683
|
-
let raw = {};
|
|
684
|
-
try {
|
|
685
|
-
const parsed = JSON.parse(slice);
|
|
686
|
-
if (parsed && typeof parsed === "object") raw = parsed;
|
|
687
|
-
} catch {}
|
|
688
|
-
const attrs = extractOtlpAttributes(raw);
|
|
689
|
-
const projected = {};
|
|
690
|
-
for (const [k, v] of Object.entries(attrs)) if (typeof v === "string") {
|
|
691
|
-
const trunc = truncateForBudget(v, perAttrCap);
|
|
692
|
-
if (trunc !== v) counter.value += 1;
|
|
693
|
-
projected[k] = trunc;
|
|
694
|
-
} else if (Array.isArray(v) || v && typeof v === "object") {
|
|
695
|
-
const json = JSON.stringify(v);
|
|
696
|
-
const trunc = truncateForBudget(json, perAttrCap);
|
|
697
|
-
if (trunc !== json) {
|
|
698
|
-
counter.value += 1;
|
|
699
|
-
projected[k] = trunc;
|
|
700
|
-
} else projected[k] = v;
|
|
701
|
-
} else projected[k] = v;
|
|
702
|
-
return {
|
|
703
|
-
trace_id,
|
|
704
|
-
span_id: s.span_id,
|
|
705
|
-
parent_span_id: s.parent_span_id,
|
|
706
|
-
name: s.name,
|
|
707
|
-
kind: s.kind,
|
|
708
|
-
start_time: s.start_time,
|
|
709
|
-
end_time: s.end_time,
|
|
710
|
-
duration_ms: s.duration_ms,
|
|
711
|
-
status: s.status,
|
|
712
|
-
status_message: s.status_message,
|
|
713
|
-
service_name: s.service_name,
|
|
714
|
-
agent_name: s.agent_name,
|
|
715
|
-
model_name: s.model_name,
|
|
716
|
-
tool_name: s.tool_name,
|
|
717
|
-
attributes: projected
|
|
718
|
-
};
|
|
719
|
-
}
|
|
720
|
-
buildOversizedSummary(t, span_response_bytes_max) {
|
|
721
|
-
const counts = /* @__PURE__ */ new Map();
|
|
722
|
-
let errorCount = 0;
|
|
723
|
-
for (const s of t.spans) {
|
|
724
|
-
counts.set(s.name, (counts.get(s.name) ?? 0) + 1);
|
|
725
|
-
if (s.status === "ERROR") errorCount += 1;
|
|
726
|
-
}
|
|
727
|
-
const top = [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20);
|
|
728
|
-
return {
|
|
729
|
-
span_count: t.span_count,
|
|
730
|
-
top_span_names: top,
|
|
731
|
-
span_response_bytes_max,
|
|
732
|
-
error_span_count: errorCount
|
|
733
|
-
};
|
|
734
|
-
}
|
|
735
|
-
async scanSpanForMatches(buf, trace_id, s, re, textBudget, recordCap) {
|
|
736
|
-
const slice = buf.subarray(s.line_byte_offset, s.line_byte_offset + s.line_byte_length).toString("utf8");
|
|
737
|
-
const records = [];
|
|
738
|
-
const globalRe = new RegExp(re.source, re.flags.includes("g") ? re.flags : `${re.flags}g`);
|
|
739
|
-
let total = 0;
|
|
740
|
-
let hasMore = false;
|
|
741
|
-
let m = globalRe.exec(slice);
|
|
742
|
-
while (m !== null) {
|
|
743
|
-
total += 1;
|
|
744
|
-
if (m.index === globalRe.lastIndex) globalRe.lastIndex += 1;
|
|
745
|
-
if (records.length >= recordCap) {
|
|
746
|
-
hasMore = true;
|
|
747
|
-
break;
|
|
748
|
-
}
|
|
749
|
-
const before = slice.slice(Math.max(0, m.index - textBudget / 2), m.index);
|
|
750
|
-
const after = slice.slice(m.index + m[0].length, m.index + m[0].length + Math.floor(textBudget / 2));
|
|
751
|
-
records.push({
|
|
752
|
-
trace_id,
|
|
753
|
-
span_id: s.span_id,
|
|
754
|
-
span_name: s.name,
|
|
755
|
-
span_kind: s.kind,
|
|
756
|
-
attribute_path: bestAttributePathForOffset(slice, m.index) ?? "span.raw",
|
|
757
|
-
matched_text: truncateForBudget(m[0], textBudget),
|
|
758
|
-
context_before: truncateForBudget(before, textBudget),
|
|
759
|
-
context_after: truncateForBudget(after, textBudget),
|
|
760
|
-
match_offset: m.index
|
|
761
|
-
});
|
|
762
|
-
m = globalRe.exec(slice);
|
|
763
|
-
}
|
|
764
|
-
return {
|
|
765
|
-
records,
|
|
766
|
-
total,
|
|
767
|
-
hasMore
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
};
|
|
771
|
-
var OtlpFileTraceStore = class extends BufferedOtlpTraceStore {
|
|
772
|
-
path;
|
|
773
|
-
maxFileBytes;
|
|
774
|
-
constructor(opts) {
|
|
775
|
-
super(opts);
|
|
776
|
-
this.path = opts.path;
|
|
777
|
-
this.maxFileBytes = opts.maxFileBytes ?? 268435456;
|
|
778
|
-
}
|
|
779
|
-
/** Stat-then-read so an oversized file fails loud before allocating the
|
|
780
|
-
* source buffer. Missing files remain distinct from malformed traces. */
|
|
781
|
-
async readBuffer() {
|
|
782
|
-
let stats;
|
|
783
|
-
try {
|
|
784
|
-
stats = await stat(this.path);
|
|
785
|
-
} catch (err) {
|
|
786
|
-
if (err?.code === "ENOENT") throw new TraceFileMissingError(this.path);
|
|
787
|
-
throw err;
|
|
788
|
-
}
|
|
789
|
-
if (stats.size > this.maxFileBytes) throw new TraceFileTooLargeError(this.path, stats.size, this.maxFileBytes);
|
|
790
|
-
return readFile(this.path);
|
|
791
|
-
}
|
|
792
|
-
};
|
|
793
|
-
var OtlpBufferTraceStore = class extends BufferedOtlpTraceStore {
|
|
794
|
-
source;
|
|
795
|
-
constructor(source, opts) {
|
|
796
|
-
super(opts);
|
|
797
|
-
this.source = source;
|
|
798
|
-
}
|
|
799
|
-
async readBuffer() {
|
|
800
|
-
return this.source;
|
|
801
|
-
}
|
|
802
|
-
};
|
|
803
|
-
/** Missing tool spans cannot distinguish a tool-free run from broken capture. */
|
|
804
|
-
var ToolTraceMissingError = class extends CaptureIntegrityError {
|
|
805
|
-
constructor() {
|
|
806
|
-
super("toolSpansToTraceAnalysisStore: no tool spans supplied; trace evidence is missing");
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
/**
|
|
810
|
-
* Snapshot canonical tool spans into the bounded read interface used by trace analysts.
|
|
811
|
-
* One `runId` becomes one trace; arguments, results, source attributes, errors, and timing
|
|
812
|
-
* remain queryable through the same OTLP projection as file-backed traces.
|
|
813
|
-
*/
|
|
814
|
-
function toolSpansToTraceAnalysisStore(spans, opts = {}) {
|
|
815
|
-
if (!spans || spans.length === 0) throw new ToolTraceMissingError();
|
|
816
|
-
const seen = /* @__PURE__ */ new Set();
|
|
817
|
-
const lines = spans.map((span, index) => {
|
|
818
|
-
assertToolSpanIdentity(span, index);
|
|
819
|
-
const identity = `${span.runId}\u0000${span.spanId}`;
|
|
820
|
-
if (seen.has(identity)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: duplicate span '${span.spanId}' in run '${span.runId}'`);
|
|
821
|
-
seen.add(identity);
|
|
822
|
-
const attributes = { ...span.attributes ?? {} };
|
|
823
|
-
applyToolSpanOtlpAttributes(attributes, span);
|
|
824
|
-
attributes[OPENINFERENCE_SPAN_KIND] = "TOOL";
|
|
825
|
-
const endedAt = span.endedAt ?? span.startedAt + (span.latencyMs ?? 0);
|
|
826
|
-
const line = createOtlpFlatLine({
|
|
827
|
-
traceId: span.runId,
|
|
828
|
-
spanId: span.spanId,
|
|
829
|
-
parentSpanId: span.parentSpanId ?? null,
|
|
830
|
-
name: span.name,
|
|
831
|
-
kind: "SPAN_KIND_INTERNAL",
|
|
832
|
-
startTime: toolSpanTimeIso(span.startedAt, span.spanId, "startedAt"),
|
|
833
|
-
endTime: toolSpanTimeIso(endedAt, span.spanId, "endedAt"),
|
|
834
|
-
statusCode: spanStatusToOtlp(span.status, span.error, "STATUS_CODE_UNSET"),
|
|
835
|
-
statusMessage: span.error,
|
|
836
|
-
resource: { attributes: {} },
|
|
837
|
-
attributes
|
|
838
|
-
});
|
|
839
|
-
try {
|
|
840
|
-
return JSON.stringify(line);
|
|
841
|
-
} catch (cause) {
|
|
842
|
-
throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' in run '${span.runId}' is not JSON-serializable`, { cause });
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
return new OtlpBufferTraceStore(Buffer.from(`${lines.join("\n")}\n`, "utf8"), opts);
|
|
846
|
-
}
|
|
847
|
-
function assertToolSpanIdentity(span, index) {
|
|
848
|
-
if (span.kind !== "tool") throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span at index ${index} has kind '${String(span.kind)}', not 'tool'`);
|
|
849
|
-
if (!span.runId || !span.spanId || !span.name || !span.toolName) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span at index ${index} is missing runId, spanId, name, or toolName`);
|
|
850
|
-
if (!Number.isFinite(span.startedAt)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid startedAt`);
|
|
851
|
-
if (span.endedAt !== void 0 && !Number.isFinite(span.endedAt)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid endedAt`);
|
|
852
|
-
if (span.endedAt !== void 0 && span.endedAt < span.startedAt) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' ends before it starts`);
|
|
853
|
-
if (span.latencyMs !== void 0 && (!Number.isFinite(span.latencyMs) || span.latencyMs < 0)) throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${span.spanId}' has invalid latencyMs`);
|
|
854
|
-
}
|
|
855
|
-
function toolSpanTimeIso(value, spanId, field) {
|
|
856
|
-
const iso = epochMillisToIso(value);
|
|
857
|
-
if (iso) return iso;
|
|
858
|
-
throw new CaptureIntegrityError(`toolSpansToTraceAnalysisStore: span '${spanId}' has invalid ${field}`);
|
|
859
|
-
}
|
|
860
|
-
var TraceFileMissingError = class extends NotFoundError {
|
|
861
|
-
constructor(path) {
|
|
862
|
-
super(`trace file not found: ${path}`);
|
|
863
|
-
}
|
|
864
|
-
};
|
|
865
|
-
var TraceFileTooLargeError = class extends Error {
|
|
866
|
-
path;
|
|
867
|
-
size_bytes;
|
|
868
|
-
max_bytes;
|
|
869
|
-
constructor(path, size_bytes, max_bytes) {
|
|
870
|
-
super(`trace file ${path} is ${size_bytes} bytes, over the ${max_bytes}-byte limit; raise OtlpFileTraceStoreOptions.maxFileBytes or pre-split the file`);
|
|
871
|
-
this.path = path;
|
|
872
|
-
this.size_bytes = size_bytes;
|
|
873
|
-
this.max_bytes = max_bytes;
|
|
874
|
-
}
|
|
875
|
-
};
|
|
876
|
-
var TraceNotFoundError = class extends NotFoundError {
|
|
877
|
-
trace_id;
|
|
878
|
-
constructor(trace_id) {
|
|
879
|
-
super(`trace not found: ${trace_id}`);
|
|
880
|
-
this.trace_id = trace_id;
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
var SpanNotFoundError = class extends NotFoundError {
|
|
884
|
-
trace_id;
|
|
885
|
-
span_id;
|
|
886
|
-
constructor(trace_id, span_id) {
|
|
887
|
-
super(`span ${span_id} not found in trace ${trace_id}`);
|
|
888
|
-
this.trace_id = trace_id;
|
|
889
|
-
this.span_id = span_id;
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
function isPresent(v) {
|
|
893
|
-
return v !== void 0;
|
|
894
|
-
}
|
|
895
|
-
/** A `"` at `idx` is a real JSON delimiter only when the run of `\`
|
|
896
|
-
* immediately preceding it is even-length; an odd run means the quote
|
|
897
|
-
* is escaped (`\"`) and is part of a string value, not a boundary. */
|
|
898
|
-
function isUnescapedQuote(slice, idx) {
|
|
899
|
-
if (slice[idx] !== "\"") return false;
|
|
900
|
-
let backslashes = 0;
|
|
901
|
-
let b = idx - 1;
|
|
902
|
-
while (b >= 0 && slice[b] === "\\") {
|
|
903
|
-
backslashes += 1;
|
|
904
|
-
b -= 1;
|
|
905
|
-
}
|
|
906
|
-
return backslashes % 2 === 0;
|
|
907
|
-
}
|
|
908
|
-
/** Scan backwards from `from` (inclusive) for the nearest UNescaped `"`.
|
|
909
|
-
* Returns its index, or -1 when none is found. */
|
|
910
|
-
function prevUnescapedQuote(slice, from) {
|
|
911
|
-
for (let i = from; i >= 0; i -= 1) if (slice[i] === "\"" && isUnescapedQuote(slice, i)) return i;
|
|
912
|
-
return -1;
|
|
913
|
-
}
|
|
914
|
-
/**
|
|
915
|
-
* Best-effort: locate the JSON path for the substring at `offset` in
|
|
916
|
-
* a single span's JSONL slice. The slice is '...,"key":"value..."' — we
|
|
917
|
-
* walk back from `offset` to the value-opening quote, past the `:`, to
|
|
918
|
-
* the key's closing then opening quote, skipping `\"`-escaped quotes that
|
|
919
|
-
* live inside string values. Returns `null` when the offset doesn't fall
|
|
920
|
-
* inside a recognisable string field.
|
|
921
|
-
*/
|
|
922
|
-
function bestAttributePathForOffset(slice, offset) {
|
|
923
|
-
const valueQuote = prevUnescapedQuote(slice, Math.min(offset, slice.length - 1));
|
|
924
|
-
if (valueQuote < 1) return null;
|
|
925
|
-
let j = valueQuote - 1;
|
|
926
|
-
while (j >= 0 && slice[j] !== ":") j -= 1;
|
|
927
|
-
if (j < 1) return null;
|
|
928
|
-
const keyClose = prevUnescapedQuote(slice, j - 1);
|
|
929
|
-
if (keyClose < 1) return null;
|
|
930
|
-
const keyOpen = prevUnescapedQuote(slice, keyClose - 1);
|
|
931
|
-
if (keyOpen < 0) return null;
|
|
932
|
-
return slice.slice(keyOpen + 1, keyClose);
|
|
933
|
-
}
|
|
934
|
-
const ERROR_CLUSTER_MAX = 50;
|
|
935
|
-
const ERROR_CLUSTER_EXEMPLARS = 5;
|
|
936
|
-
const SIGNATURE_MAX_CHARS = 160;
|
|
937
|
-
/** Collapse volatile tokens so semantically identical failures share a key:
|
|
938
|
-
* hex/uuid ids → <id>, numbers → #, quoted/abs paths → <path>, durations →
|
|
939
|
-
* <dur>, whitespace collapsed. Empty/absent messages fall back to the span
|
|
940
|
-
* name so a no-message error still forms a real cluster. */
|
|
941
|
-
function normalizeErrorSignature(message, spanName) {
|
|
942
|
-
const raw = (message ?? "").trim();
|
|
943
|
-
const norm = (raw.length > 0 ? raw : `(${spanName || "error"} — no message)`).replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<id>").replace(/\b[0-9a-f]{12,}\b/gi, "<id>").replace(/(?:\/[\w.\-@]+){2,}/g, "<path>").replace(/\b\d+(?:\.\d+)?(ms|s|m|h|kb|mb|gb)?\b/gi, (_m, u) => u ? `#${u}` : "#").replace(/\s+/g, " ").trim();
|
|
944
|
-
return norm.length > SIGNATURE_MAX_CHARS ? `${norm.slice(0, SIGNATURE_MAX_CHARS)}…` : norm;
|
|
945
|
-
}
|
|
946
|
-
function bump(map, key) {
|
|
947
|
-
if (!key) return;
|
|
948
|
-
map.set(key, (map.get(key) ?? 0) + 1);
|
|
949
|
-
}
|
|
950
|
-
function topKey(map) {
|
|
951
|
-
let best = null;
|
|
952
|
-
let bestN = 0;
|
|
953
|
-
for (const [k, n] of map) if (n > bestN) {
|
|
954
|
-
best = k;
|
|
955
|
-
bestN = n;
|
|
956
|
-
}
|
|
957
|
-
return best;
|
|
958
|
-
}
|
|
959
|
-
function accumulateErrorCluster(clusters, traceId, span) {
|
|
960
|
-
const signature = normalizeErrorSignature(span.status_message, span.name);
|
|
961
|
-
let acc = clusters.get(signature);
|
|
962
|
-
if (!acc) {
|
|
963
|
-
acc = {
|
|
964
|
-
signature,
|
|
965
|
-
sample: (span.status_message ?? span.name ?? "").slice(0, 500),
|
|
966
|
-
traceIds: /* @__PURE__ */ new Set(),
|
|
967
|
-
spanIds: [],
|
|
968
|
-
spanCount: 0,
|
|
969
|
-
spanNames: /* @__PURE__ */ new Map(),
|
|
970
|
-
toolNames: /* @__PURE__ */ new Map()
|
|
971
|
-
};
|
|
972
|
-
clusters.set(signature, acc);
|
|
973
|
-
}
|
|
974
|
-
acc.traceIds.add(traceId);
|
|
975
|
-
acc.spanCount += 1;
|
|
976
|
-
if (acc.spanIds.length < ERROR_CLUSTER_EXEMPLARS && !acc.spanIds.includes(span.span_id)) acc.spanIds.push(span.span_id);
|
|
977
|
-
bump(acc.spanNames, span.name);
|
|
978
|
-
bump(acc.toolNames, span.tool_name);
|
|
979
|
-
}
|
|
980
|
-
function finalizeErrorClusters(clusters, errorTraceCount) {
|
|
981
|
-
const out = [...clusters.values()].map((acc) => ({
|
|
982
|
-
signature: acc.signature,
|
|
983
|
-
status_message_sample: acc.sample,
|
|
984
|
-
span_name: topKey(acc.spanNames),
|
|
985
|
-
tool_name: topKey(acc.toolNames),
|
|
986
|
-
trace_count: acc.traceIds.size,
|
|
987
|
-
span_count: acc.spanCount,
|
|
988
|
-
prevalence: errorTraceCount > 0 ? acc.traceIds.size / errorTraceCount : 0,
|
|
989
|
-
exemplar_trace_ids: [...acc.traceIds].slice(0, ERROR_CLUSTER_EXEMPLARS),
|
|
990
|
-
exemplar_span_ids: acc.spanIds.slice(0, ERROR_CLUSTER_EXEMPLARS)
|
|
991
|
-
}));
|
|
992
|
-
out.sort((a, b) => b.trace_count - a.trace_count || b.span_count - a.span_count);
|
|
993
|
-
return out.slice(0, ERROR_CLUSTER_MAX);
|
|
994
|
-
}
|
|
995
|
-
const TRACE_ANALYSIS_COMPLETION_INSTRUCTION = `This host consumes your executor result directly; there is no downstream responder for this run. Ignore generic executor guidance that says a responder will format the answer. You must produce the final report and findings yourself.
|
|
996
|
-
|
|
997
|
-
Return exactly one executable JavaScript program per turn. Never emit multiple JavaScript or code fences; put every tool call and the final call in that one program.
|
|
998
|
-
|
|
999
|
-
When the analysis is complete, call \`await final(${JSON.stringify("Submit the completed trace analysis.")}, { report, findings })\` exactly once. Do not return a one-argument string from \`final(...)\`.`;
|
|
1000
|
-
var TraceAnalysisTurnLimitError = class extends Error {
|
|
1001
|
-
analystId;
|
|
1002
|
-
maxTurns;
|
|
1003
|
-
constructor(analystId, maxTurns, cause) {
|
|
1004
|
-
super(`Trace analyst '${analystId}' reached maxTurns=${maxTurns} without a structured final result`, { cause });
|
|
1005
|
-
this.name = "TraceAnalysisTurnLimitError";
|
|
1006
|
-
this.analystId = analystId;
|
|
1007
|
-
this.maxTurns = maxTurns;
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
async function runTraceAnalysisLoop(options) {
|
|
1011
|
-
validateLoopLimits(options);
|
|
1012
|
-
const config = {
|
|
1013
|
-
agentIdentity: {
|
|
1014
|
-
name: options.id,
|
|
1015
|
-
description: options.description
|
|
1016
|
-
},
|
|
1017
|
-
contextFields: [],
|
|
1018
|
-
runtime: new AxJSRuntime({
|
|
1019
|
-
permissions: [],
|
|
1020
|
-
blockDynamicImport: true,
|
|
1021
|
-
allowedModules: [],
|
|
1022
|
-
freezeIntrinsics: true,
|
|
1023
|
-
blockShadowRealm: true,
|
|
1024
|
-
preventGlobalThisExtensions: false
|
|
1025
|
-
}),
|
|
1026
|
-
maxSubAgentCalls: options.maxSubqueries,
|
|
1027
|
-
maxTurns: options.maxTurns,
|
|
1028
|
-
maxRuntimeChars: options.maxRuntimeChars,
|
|
1029
|
-
maxBatchedLlmQueryConcurrency: options.maxParallelSubqueries,
|
|
1030
|
-
promptLevel: "detailed",
|
|
1031
|
-
contextPolicy: {
|
|
1032
|
-
preset: "full",
|
|
1033
|
-
budget: "balanced"
|
|
1034
|
-
},
|
|
1035
|
-
functions: options.tools,
|
|
1036
|
-
executorOptions: {
|
|
1037
|
-
description: `${options.prompt.trim()}\n\n${TRACE_ANALYSIS_COMPLETION_INSTRUCTION}`,
|
|
1038
|
-
...options.model ? { model: options.model } : {},
|
|
1039
|
-
showThoughts: false,
|
|
1040
|
-
thinkingTokenBudget: "none"
|
|
1041
|
-
},
|
|
1042
|
-
...options.onTurn ? { actorTurnCallback: options.onTurn } : {},
|
|
1043
|
-
bubbleErrors: [TraceFileMissingError]
|
|
1044
|
-
};
|
|
1045
|
-
const analyst = options.findingType === "string" ? agent("question:string -> report:string, findings:string[]", config) : agent("question:string -> report:string, findings:json[]", config);
|
|
1046
|
-
const state = await analyst.executor.run(options.ai, { question: options.question }, options.signal ? { abortSignal: options.signal } : void 0);
|
|
1047
|
-
let completed;
|
|
1048
|
-
try {
|
|
1049
|
-
completed = options.findingType === "string" ? readTraceAnalysisCompletion(state.executorResult, "string") : readTraceAnalysisCompletion(state.executorResult, "object");
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
if (state.turnCount >= options.maxTurns) throw new TraceAnalysisTurnLimitError(options.id, options.maxTurns, error);
|
|
1052
|
-
throw new Error(`Trace analyst '${options.id}' stopped without a structured final result`, { cause: error });
|
|
1053
|
-
}
|
|
1054
|
-
return {
|
|
1055
|
-
...completed,
|
|
1056
|
-
usage: analyst.executor.getUsage(),
|
|
1057
|
-
chatLog: analyst.executor.getChatLog(),
|
|
1058
|
-
turnCount: state.turnCount
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
function readTraceAnalysisCompletion(value, findingType) {
|
|
1062
|
-
if (!value || typeof value !== "object") throw new Error("Trace analyst did not return a structured final result");
|
|
1063
|
-
const completion = value;
|
|
1064
|
-
if (completion.type !== "final" || !Array.isArray(completion.args) || completion.args.length !== 2 || completion.args[0] !== "Submit the completed trace analysis.") throw new Error("Trace analyst did not return a structured final result");
|
|
1065
|
-
const payload = completion.args[1];
|
|
1066
|
-
if (!payload || typeof payload !== "object") throw new Error("Trace analyst final result must contain report and findings");
|
|
1067
|
-
const { report, findings } = payload;
|
|
1068
|
-
if (typeof report !== "string" || !Array.isArray(findings)) throw new Error("Trace analyst final result must contain report and findings");
|
|
1069
|
-
if (findingType === "string") {
|
|
1070
|
-
if (findings.some((finding) => typeof finding !== "string")) throw new Error("Trace analyst final result must contain string findings");
|
|
1071
|
-
return {
|
|
1072
|
-
report,
|
|
1073
|
-
findings
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
if (findings.some((finding) => !finding || typeof finding !== "object" || Array.isArray(finding))) throw new Error("Trace analyst final result must contain object findings");
|
|
1077
|
-
return {
|
|
1078
|
-
report,
|
|
1079
|
-
findings
|
|
1080
|
-
};
|
|
1081
|
-
}
|
|
1082
|
-
function validateLoopLimits(options) {
|
|
1083
|
-
if (!Number.isSafeInteger(options.maxSubqueries) || options.maxSubqueries < 0) throw new TypeError("maxSubqueries must be a non-negative integer");
|
|
1084
|
-
if (!Number.isSafeInteger(options.maxParallelSubqueries) || options.maxParallelSubqueries < 1) throw new TypeError("maxParallelSubqueries must be a positive integer");
|
|
1085
|
-
}
|
|
1086
|
-
//#endregion
|
|
1087
|
-
//#region src/trace-analyst/tools.ts
|
|
1088
|
-
const NAMESPACE = "traces";
|
|
1089
|
-
const filtersField = f.json("Filter set. ALL fields are AND-composed. Leave empty to scan everything.").optional();
|
|
1090
|
-
/**
|
|
1091
|
-
* Build the trace-analyst function set. Pass the result into
|
|
1092
|
-
* `agent(...).functions.local`.
|
|
1093
|
-
*/
|
|
1094
|
-
function buildTraceAnalystTools(opts) {
|
|
1095
|
-
const { store } = opts;
|
|
1096
|
-
return [
|
|
1097
|
-
fn("getDatasetOverview").description("Dataset rollup: total traces, raw_jsonl_bytes, services, agents, models, tools, and sample_trace_ids (real ids passable to view/search). Always call this FIRST without a regex_pattern.").namespace(NAMESPACE).arg("filters", filtersField).returns(f.json("DatasetOverview")).handler(async ({ filters }) => store.getOverview(parseFilters(filters))).build(),
|
|
1098
|
-
fn("queryTraces").description("Paginated trace summaries. Each summary carries raw_jsonl_bytes — use it to size traces BEFORE calling viewTrace. Narrow with indexed filters before adding regex_pattern.").namespace(NAMESPACE).arg("filters", filtersField).arg("limit", f.number("Page size, 1..200")).arg("offset", f.number("Page offset; default 0").optional()).returns(f.json("QueryTracesPage")).handler(async ({ filters, limit, offset }) => store.queryTraces({
|
|
1099
|
-
filters: parseFilters(filters),
|
|
1100
|
-
limit: assertPageLimit(limit),
|
|
1101
|
-
offset: assertOffset(offset)
|
|
1102
|
-
})).build(),
|
|
1103
|
-
fn("countTraces").description("Count traces matching `filters`. Use as a cheap pre-flight before opting into a regex_pattern scan.").namespace(NAMESPACE).arg("filters", filtersField).returns(f.number("count")).handler(async ({ filters }) => store.countTraces(parseFilters(filters))).build(),
|
|
1104
|
-
fn("viewTrace").description("Return ALL spans for a single trace, with each attribute capped at ~4KB. If the response would exceed the per-call ceiling the result carries `oversized` instead of `spans` — DO NOT retry with the same trace_id; switch to searchTrace / viewSpans.").namespace(NAMESPACE).arg("trace_id", f.string("Real trace id from a prior overview/query")).returns(f.json("ViewTraceResult")).handler(async ({ trace_id }) => store.viewTrace({ trace_id: assertString(trace_id, "trace_id") })).build(),
|
|
1105
|
-
fn("viewSpans").description("Surgical read of specific spans within a trace, with each attribute capped at ~16KB (4× the discovery cap). Use after searchTrace narrows to specific span_ids.").namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("span_ids", f.string("Span ids to fetch").array()).returns(f.json("ViewSpansResult")).handler(async ({ trace_id, span_ids }) => store.viewSpans({
|
|
1106
|
-
trace_id: assertString(trace_id, "trace_id"),
|
|
1107
|
-
span_ids: assertStringArray(span_ids, "span_ids")
|
|
1108
|
-
})).build(),
|
|
1109
|
-
fn("searchTrace").description("Regex search across all spans of one trace. Returns `{trace_id, hits: SpanMatchRecord[], total_matches, has_more}`. **Iterate `result.hits`, NOT `result.matches`** — the field is `hits`. Each hit has `{span_id, span_name, span_kind, attribute_path, matched_text, context_before, context_after, match_offset}`. Bounded regardless of trace size by max_matches (1..500, default 50). If has_more=true, REFINE the regex rather than blindly raising max_matches.").namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("regex_pattern", f.string("JS-compatible regex, multiline")).arg("max_matches", f.number("Max records returned, 1..500; default 50").optional()).returns(f.json("SearchTraceResult")).handler(async ({ trace_id, regex_pattern, max_matches }) => store.searchTrace({
|
|
1110
|
-
trace_id: assertString(trace_id, "trace_id"),
|
|
1111
|
-
regex_pattern: assertRegex(regex_pattern),
|
|
1112
|
-
max_matches: assertMaxMatches(max_matches)
|
|
1113
|
-
})).build(),
|
|
1114
|
-
fn("searchSpan").description("Regex search inside a single span. Use when viewSpans returned a 16KB-truncated payload and you need to narrow further. Returns `{trace_id, span_id, hits: SpanMatchRecord[], total_matches, has_more}` — iterate `result.hits`, NOT `result.matches`.").namespace(NAMESPACE).arg("trace_id", f.string("Real trace id")).arg("span_id", f.string("Real span id within trace")).arg("regex_pattern", f.string("JS-compatible regex, multiline")).arg("max_matches", f.number("Max records, 1..500; default 50").optional()).returns(f.json("SearchSpanResult")).handler(async ({ trace_id, span_id, regex_pattern, max_matches }) => store.searchSpan({
|
|
1115
|
-
trace_id: assertString(trace_id, "trace_id"),
|
|
1116
|
-
span_id: assertString(span_id, "span_id"),
|
|
1117
|
-
regex_pattern: assertRegex(regex_pattern),
|
|
1118
|
-
max_matches: assertMaxMatches(max_matches)
|
|
1119
|
-
})).build()
|
|
1120
|
-
];
|
|
1121
|
-
}
|
|
1122
|
-
/**
|
|
1123
|
-
* Convenience: same shape as `buildTraceAnalystTools` but returns the
|
|
1124
|
-
* grouped form expected when registering trace tools alongside other
|
|
1125
|
-
* agent function modules. */
|
|
1126
|
-
function traceAnalystFunctionGroup(opts) {
|
|
1127
|
-
return {
|
|
1128
|
-
namespace: NAMESPACE,
|
|
1129
|
-
title: "Trace Analysis",
|
|
1130
|
-
selectionCriteria: "Use for any inspection of OTLP-shaped trace data.",
|
|
1131
|
-
description: "Discovery → narrow → deep-read tools over a JSONL trace dataset. Always call getDatasetOverview first.",
|
|
1132
|
-
functions: buildTraceAnalystTools(opts)
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
function parseFilters(input) {
|
|
1136
|
-
if (input == null) return void 0;
|
|
1137
|
-
if (typeof input !== "object" || Array.isArray(input)) throw new TypeError(`filters must be an object, got ${typeof input}`);
|
|
1138
|
-
const f = input;
|
|
1139
|
-
const out = {};
|
|
1140
|
-
if (typeof f.has_errors === "boolean") out.has_errors = f.has_errors;
|
|
1141
|
-
out.service_names = stringArrayOrUndefined(f.service_names, "service_names");
|
|
1142
|
-
out.agent_names = stringArrayOrUndefined(f.agent_names, "agent_names");
|
|
1143
|
-
out.model_names = stringArrayOrUndefined(f.model_names, "model_names");
|
|
1144
|
-
out.tool_names = stringArrayOrUndefined(f.tool_names, "tool_names");
|
|
1145
|
-
if (typeof f.start_time_after === "string") out.start_time_after = f.start_time_after;
|
|
1146
|
-
if (typeof f.start_time_before === "string") out.start_time_before = f.start_time_before;
|
|
1147
|
-
if (typeof f.regex_pattern === "string") {
|
|
1148
|
-
if (f.regex_pattern.length === 0) throw new TypeError("filters.regex_pattern cannot be empty");
|
|
1149
|
-
out.regex_pattern = f.regex_pattern;
|
|
1150
|
-
}
|
|
1151
|
-
return out;
|
|
1152
|
-
}
|
|
1153
|
-
function stringArrayOrUndefined(v, label) {
|
|
1154
|
-
if (v === void 0 || v === null) return void 0;
|
|
1155
|
-
if (!Array.isArray(v)) throw new TypeError(`${label} must be an array of strings`);
|
|
1156
|
-
if (v.some((x) => typeof x !== "string")) throw new TypeError(`${label} entries must be strings`);
|
|
1157
|
-
return v;
|
|
1158
|
-
}
|
|
1159
|
-
function assertPageLimit(limit) {
|
|
1160
|
-
if (typeof limit !== "number" || !Number.isInteger(limit) || limit < 1 || limit > 200) throw new RangeError(`limit must be an integer 1..200`);
|
|
1161
|
-
return limit;
|
|
1162
|
-
}
|
|
1163
|
-
function assertOffset(offset) {
|
|
1164
|
-
if (offset === void 0) return void 0;
|
|
1165
|
-
if (typeof offset !== "number" || !Number.isInteger(offset) || offset < 0) throw new RangeError(`offset must be a non-negative integer`);
|
|
1166
|
-
return offset;
|
|
1167
|
-
}
|
|
1168
|
-
function assertRegex(pattern) {
|
|
1169
|
-
if (typeof pattern !== "string" || pattern.length === 0) throw new TypeError(`regex_pattern must be a non-empty string`);
|
|
1170
|
-
new RegExp(pattern, "m");
|
|
1171
|
-
return pattern;
|
|
1172
|
-
}
|
|
1173
|
-
function assertMaxMatches(n) {
|
|
1174
|
-
if (n === void 0) return void 0;
|
|
1175
|
-
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 500) throw new RangeError(`max_matches must be an integer 1..500`);
|
|
1176
|
-
return n;
|
|
1177
|
-
}
|
|
1178
|
-
function assertString(v, label) {
|
|
1179
|
-
if (typeof v !== "string" || v.length === 0) throw new TypeError(`${label} must be a non-empty string`);
|
|
1180
|
-
return v;
|
|
1181
|
-
}
|
|
1182
|
-
function assertStringArray(v, label) {
|
|
1183
|
-
if (!Array.isArray(v)) throw new TypeError(`${label} must be an array of strings`);
|
|
1184
|
-
if (v.some((x) => typeof x !== "string")) throw new TypeError(`${label} entries must be strings`);
|
|
1185
|
-
return v;
|
|
1186
|
-
}
|
|
1187
|
-
//#endregion
|
|
1188
|
-
export { stringField as C, traceSpanKindToOpenInferenceKind as D, isOtlpModelCall as E, spanEpochMillis as S, classifyOtlpSpanRole as T, extractOtlpAttributes as _, SpanNotFoundError as a, projectOtlpFlatLine as b, TraceNotFoundError as c, TRACE_ANALYST_TRUNCATION_MARKER_PREFIX as d, createOtlpFlatLine as f, compareSpanTime as g, asString as h, OtlpFileTraceStore as i, toolSpansToTraceAnalysisStore as l, spanStatusToOtlp as m, traceAnalystFunctionGroup as n, ToolTraceMissingError as o, epochMillisToIso as p, runTraceAnalysisLoop as r, TraceFileMissingError as s, buildTraceAnalystTools as t, DEFAULT_TRACE_ANALYST_BUDGETS as u, firstStringAttr as v, applyToolSpanOtlpAttributes as w, readOtlpStatus as x, inferOtlpKind as y };
|
|
1189
|
-
|
|
1190
|
-
//# sourceMappingURL=tools-D8yTtNSN.js.map
|