@tangle-network/agent-eval 0.118.2 → 0.119.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 +21 -0
- package/README.md +26 -0
- package/dist/analyst/index.d.ts +143 -21
- package/dist/analyst/index.js +46 -12
- package/dist/analyst/index.js.map +1 -1
- package/dist/benchmarks/index.d.ts +50 -40
- package/dist/benchmarks/index.js +9 -9
- package/dist/campaign/index.d.ts +231 -156
- package/dist/campaign/index.js +8 -8
- package/dist/{chunk-JTMFT5QZ.js → chunk-4I2E3LLO.js} +2 -2
- package/dist/{chunk-BUBU3YHT.js → chunk-6QIM2EAP.js} +6 -116
- package/dist/chunk-6QIM2EAP.js.map +1 -0
- package/dist/{chunk-DZBCDDIE.js → chunk-7A4LIMMY.js} +846 -253
- package/dist/chunk-7A4LIMMY.js.map +1 -0
- package/dist/{chunk-KK2O4VWA.js → chunk-D7AEXSM5.js} +2 -2
- package/dist/{chunk-GCT3DQWU.js → chunk-F6YUH3L4.js} +41 -30
- package/dist/{chunk-GCT3DQWU.js.map → chunk-F6YUH3L4.js.map} +1 -1
- package/dist/{chunk-63KG3ASG.js → chunk-FIUFRXP3.js} +2 -2
- package/dist/{chunk-J23QK2U5.js → chunk-GERDAIAL.js} +8 -8
- package/dist/{chunk-J23QK2U5.js.map → chunk-GERDAIAL.js.map} +1 -1
- package/dist/{chunk-TVXPJJ7H.js → chunk-H5UD2323.js} +2 -2
- package/dist/{chunk-MLHRFWQK.js → chunk-JHOJHHU7.js} +85 -16
- package/dist/chunk-JHOJHHU7.js.map +1 -0
- package/dist/chunk-K4DBDHLK.js +158 -0
- package/dist/chunk-K4DBDHLK.js.map +1 -0
- package/dist/{chunk-DRSFTBKU.js → chunk-LMJ2TGWJ.js} +3 -15
- package/dist/chunk-LMJ2TGWJ.js.map +1 -0
- package/dist/{chunk-EGNRE7VA.js → chunk-PAHNGS65.js} +2 -2
- package/dist/{chunk-I42AAGHL.js → chunk-RLCJQ6VZ.js} +5 -5
- package/dist/{chunk-7V3QDWNL.js → chunk-XJ7JVCHB.js} +2 -2
- package/dist/{chunk-6JQE3YVE.js → chunk-ZYHJNKI3.js} +6 -6
- package/dist/cli.js +2 -2
- package/dist/contract/index.d.ts +257 -160
- package/dist/contract/index.js +11 -11
- package/dist/fuzz.d.ts +19 -1
- package/dist/fuzz.js +1 -1
- package/dist/index.d.ts +170 -64
- package/dist/index.js +30 -28
- package/dist/index.js.map +1 -1
- package/dist/openapi.json +1 -1
- package/dist/primeintellect/index.d.ts +311 -0
- package/dist/primeintellect/index.js +348 -0
- package/dist/primeintellect/index.js.map +1 -0
- package/dist/rl.d.ts +4 -0
- package/dist/{run-campaign-DWC67KJP.js → run-campaign-OBXSN5WK.js} +3 -3
- package/dist/trace-attributes.d.ts +17 -1
- package/dist/trace-attributes.js +27 -3
- package/dist/traces.d.ts +16 -16
- package/dist/traces.js +18 -18
- package/dist/wire/index.d.ts +19 -1
- package/dist/wire/index.js +2 -2
- package/package.json +6 -1
- package/dist/chunk-BUBU3YHT.js.map +0 -1
- package/dist/chunk-DRSFTBKU.js.map +0 -1
- package/dist/chunk-DZBCDDIE.js.map +0 -1
- package/dist/chunk-MLHRFWQK.js.map +0 -1
- package/dist/chunk-NW4AQBXD.js +0 -53
- package/dist/chunk-NW4AQBXD.js.map +0 -1
- /package/dist/{chunk-JTMFT5QZ.js.map → chunk-4I2E3LLO.js.map} +0 -0
- /package/dist/{chunk-KK2O4VWA.js.map → chunk-D7AEXSM5.js.map} +0 -0
- /package/dist/{chunk-63KG3ASG.js.map → chunk-FIUFRXP3.js.map} +0 -0
- /package/dist/{chunk-TVXPJJ7H.js.map → chunk-H5UD2323.js.map} +0 -0
- /package/dist/{chunk-EGNRE7VA.js.map → chunk-PAHNGS65.js.map} +0 -0
- /package/dist/{chunk-I42AAGHL.js.map → chunk-RLCJQ6VZ.js.map} +0 -0
- /package/dist/{chunk-7V3QDWNL.js.map → chunk-XJ7JVCHB.js.map} +0 -0
- /package/dist/{chunk-6JQE3YVE.js.map → chunk-ZYHJNKI3.js.map} +0 -0
- /package/dist/{run-campaign-DWC67KJP.js.map → run-campaign-OBXSN5WK.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
OtlpFileTraceStore,
|
|
3
3
|
TraceFileMissingError,
|
|
4
4
|
buildTraceAnalystTools
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-6QIM2EAP.js";
|
|
6
6
|
|
|
7
7
|
// src/trace-analyst/prompts.ts
|
|
8
8
|
var TRACE_ANALYST_ACTOR_DESCRIPTION = `You answer questions about an OTLP-shaped JSONL trace dataset using the trace tools provided in the \`traces\` namespace.
|
|
@@ -198,4 +198,4 @@ export {
|
|
|
198
198
|
TRACE_ANALYST_SUBAGENT_DESCRIPTION,
|
|
199
199
|
analyzeTraces
|
|
200
200
|
};
|
|
201
|
-
//# sourceMappingURL=chunk-
|
|
201
|
+
//# sourceMappingURL=chunk-FIUFRXP3.js.map
|
|
@@ -6,19 +6,19 @@ import {
|
|
|
6
6
|
DEFAULT_TRACE_ANALYST_KINDS,
|
|
7
7
|
createTraceAnalystKind,
|
|
8
8
|
makeFinding
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-7A4LIMMY.js";
|
|
10
10
|
import {
|
|
11
11
|
LlmClient
|
|
12
12
|
} from "./chunk-NJC7U437.js";
|
|
13
13
|
import {
|
|
14
|
-
LLM_INPUT_TOKEN_ATTR_KEYS,
|
|
15
|
-
LLM_OUTPUT_TOKEN_ATTR_KEYS,
|
|
16
|
-
TOOL_NAME_ATTR_KEYS,
|
|
17
14
|
spanEpochMillis
|
|
18
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-6QIM2EAP.js";
|
|
19
16
|
import {
|
|
20
|
-
LLM_CONTEXT_TOKENS
|
|
21
|
-
|
|
17
|
+
LLM_CONTEXT_TOKENS,
|
|
18
|
+
LLM_INPUT_TOKEN_ATTR_KEYS,
|
|
19
|
+
LLM_OUTPUT_TOKEN_ATTR_KEYS,
|
|
20
|
+
TOOL_NAME_ATTR_KEYS
|
|
21
|
+
} from "./chunk-K4DBDHLK.js";
|
|
22
22
|
|
|
23
23
|
// src/analyst/chat-client.ts
|
|
24
24
|
function createChatClient(opts) {
|
|
@@ -547,4 +547,4 @@ export {
|
|
|
547
547
|
behavioralAnalyst,
|
|
548
548
|
buildDefaultAnalystRegistry
|
|
549
549
|
};
|
|
550
|
-
//# sourceMappingURL=chunk-
|
|
550
|
+
//# sourceMappingURL=chunk-GERDAIAL.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/analyst/chat-client.ts","../src/trace-analyst/behavioral-metrics.ts","../src/analyst/behavioral-analyst.ts","../src/analyst/default-registry.ts"],"sourcesContent":["/**\n * ChatClient — the single LLM abstraction analysts call.\n *\n * agent-eval already ships an `LlmClient` (OpenAI-compatible, retry,\n * graceful JSON-schema degrade) and judges that talk to `TCloud`. Two\n * mixed patterns force every analyst author to pick a transport, which\n * couples analyst code to runtime concerns (cli-bridge vs router vs\n * sandbox-sdk) it shouldn't know about.\n *\n * `ChatClient` is one interface every analyst takes via `AnalystContext.chat`.\n * The operator decides at the registry boundary which transport binds\n * to it. Analyst code stays transport-agnostic; swapping production\n * (sandbox-sdk) for local dev (cli-bridge) or tests (mock) is a one-\n * line factory call.\n *\n * Designed to coexist: existing `LlmClient` callers and existing\n * `TCloud`-based judges keep working untouched. New analyst code uses\n * `ChatClient`. When old call sites migrate, they pick up budgeting,\n * cancellation, and unified telemetry for free.\n */\n\nimport {\n type LlmCallRequest,\n type LlmCallResult,\n LlmClient,\n type LlmClientOptions,\n} from '../llm-client'\n\n/**\n * Unified chat interface. Mirrors LlmCallRequest/Result so the OpenAI-\n * compatible mental model stays. Two methods: a one-shot `chat()` and\n * an `streamChat()` for future agentic loops (not yet exposed).\n */\nexport interface ChatClient {\n /** Display name of the bound transport — included in telemetry. */\n readonly transport: ChatTransport\n /** Default model when caller omits — operators bind this per environment. */\n readonly defaultModel?: string\n /** Total provider attempts this transport can make for one chat call. */\n readonly maximumAttempts?: number\n\n /** Implementations must enforce `req.maxTokens` when it is present. */\n chat(req: ChatRequest, opts?: ChatCallOpts): Promise<ChatResponse>\n}\n\nexport type ChatTransport =\n | 'router' // router.tangle.tools — production paid models\n | 'sandbox-sdk' // box.streamPrompt() — chat completion via sandbox SDK\n | 'cli-bridge' // local cli-bridge for dev / local-only runs\n | 'direct-provider' // direct OpenAI / Anthropic / etc. — bypass router\n | 'mock' // test-time injection\n\nexport interface ChatRequest extends Omit<LlmCallRequest, 'model'> {\n /** Optional — falls back to ChatClient.defaultModel. */\n model?: string\n}\n\nexport type ChatResponse = LlmCallResult\n\nexport interface ChatCallOpts {\n /** Cancel the in-flight request. */\n signal?: AbortSignal\n /** Hard USD ceiling for this single call (informational; the underlying transport may not enforce). */\n maxCostUsd?: number\n /** Correlation tag carried into request headers when the transport allows. */\n correlationId?: string\n /** Stable provider idempotency key for retries/redrives of one paid call. */\n idempotencyKey?: string\n}\n\n// ── Factory ─────────────────────────────────────────────────────────\n\nexport type CreateChatClientOpts =\n | RouterTransportOpts\n | CliBridgeTransportOpts\n | DirectProviderTransportOpts\n | SandboxSdkTransportOpts\n | MockTransportOpts\n\ninterface BaseTransportOpts {\n defaultModel?: string\n /** Total provider attempts. Required for opaque transports used in capped runs. */\n maximumAttempts?: number\n}\n\nexport interface RouterTransportOpts extends BaseTransportOpts {\n transport: 'router'\n baseUrl?: string\n apiKey: string\n}\n\nexport interface CliBridgeTransportOpts extends BaseTransportOpts {\n transport: 'cli-bridge'\n baseUrl?: string\n bearer?: string\n}\n\nexport interface DirectProviderTransportOpts extends BaseTransportOpts {\n transport: 'direct-provider'\n baseUrl: string\n apiKey: string\n}\n\n/**\n * Sandbox-SDK transport. Provided as a thin pass-through: the caller\n * supplies a callable that mimics LlmClient.chat() against an already-\n * configured Sandbox handle. We don't import the SDK here to keep\n * agent-eval dep-free of @tangle-network/sandbox.\n */\nexport interface SandboxSdkTransportOpts extends BaseTransportOpts {\n transport: 'sandbox-sdk'\n chat: (req: ChatRequest, opts?: ChatCallOpts) => Promise<ChatResponse>\n}\n\n/**\n * Mock transport for tests. The handler receives the request and returns\n * whatever the test wants. No retries, no JSON-schema degrade.\n */\nexport interface MockTransportOpts extends BaseTransportOpts {\n transport: 'mock'\n handler: (req: ChatRequest, opts?: ChatCallOpts) => Promise<ChatResponse>\n}\n\n/**\n * Build a ChatClient bound to a specific transport. The returned client\n * is safe to share across analysts in a single registry run.\n */\nexport function createChatClient(opts: CreateChatClientOpts): ChatClient {\n switch (opts.transport) {\n case 'router':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl ?? 'https://router.tangle.tools/v1',\n apiKey: opts.apiKey,\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'cli-bridge':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl ?? 'http://127.0.0.1:3344/v1',\n apiKey: opts.bearer ?? '',\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'direct-provider':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl,\n apiKey: opts.apiKey,\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'sandbox-sdk':\n return {\n transport: 'sandbox-sdk',\n defaultModel: opts.defaultModel,\n maximumAttempts: opts.maximumAttempts,\n chat: async (req, callOpts) => opts.chat(resolveModel(req, opts.defaultModel), callOpts),\n }\n case 'mock':\n return {\n transport: 'mock',\n defaultModel: opts.defaultModel,\n maximumAttempts: 1,\n chat: async (req, callOpts) => opts.handler(resolveModel(req, opts.defaultModel), callOpts),\n }\n }\n}\n\nfunction wrapLlmClient(\n transport: ChatTransport,\n defaultModel: string | undefined,\n inner: LlmClient,\n): ChatClient {\n return {\n transport,\n defaultModel,\n maximumAttempts: inner.maximumAttempts,\n chat: (req, callOpts) => {\n const resolved = resolveModel(req, defaultModel)\n const request: LlmCallRequest = {\n model: resolved.model!,\n messages: req.messages,\n jsonMode: req.jsonMode,\n jsonSchema: req.jsonSchema,\n temperature: req.temperature,\n maxTokens: req.maxTokens,\n timeoutMs: req.timeoutMs,\n }\n return inner.call(request, {\n signal: callOpts?.signal,\n idempotencyKey: callOpts?.idempotencyKey,\n })\n },\n }\n}\n\nfunction resolveModel(req: ChatRequest, defaultModel: string | undefined): ChatRequest {\n if (req.model) return req\n if (!defaultModel) {\n throw new Error(\n 'ChatClient.chat: no model on request and no defaultModel on the client. ' +\n 'Either pass req.model or bind defaultModel at createChatClient().',\n )\n }\n return { ...req, model: defaultModel }\n}\n","/**\n * Deterministic behavioral metrics over OTLP spans — pure arithmetic, no LLM.\n *\n * These are the model-independent multiplier: the four trace-quality signals a\n * tolerant analyzer (e.g. HALO) re-derives per run inside the model — token\n * growth, output decay, tool monoculture, missing self-verification — computed\n * here once, in TypeScript, with zero model judgment. A finding that falls out\n * of arithmetic is trivially model-agnostic and cannot hallucinate the trend.\n *\n * General, not trace-specific: the detectors key off token trajectories and\n * tool usage present in any agentic OTLP trace, not any one benchmark.\n */\n\nimport { LLM_CONTEXT_TOKENS } from '../trace/attribute-vocabulary'\nimport { executionTrackByLane } from '../trace/execution-tracks'\nimport {\n LLM_INPUT_TOKEN_ATTR_KEYS,\n LLM_OUTPUT_TOKEN_ATTR_KEYS,\n TOOL_NAME_ATTR_KEYS,\n} from '../trace/otlp-attributes'\nimport { spanEpochMillis } from './otlp-span'\nimport type { TraceAnalystSpan } from './types'\n\nexport type SuboptimalCode =\n | 'monotonic-input-growth'\n | 'output-length-decay'\n | 'single-tool-dependency'\n | 'no-self-verification'\n\nexport interface SuboptimalSignal {\n code: SuboptimalCode\n severity: 'high' | 'medium' | 'low'\n /** Human-readable claim, with the backing numbers inlined. */\n detail: string\n /** The exact figures the detector fired on — auditable, no model in the loop. */\n evidence: Record<string, number | string | boolean>\n}\n\nexport interface BehavioralMetrics {\n /** The only trace represented by these metrics; null when spans are empty. */\n traceId: string | null\n llmCallCount: number\n /** Causally serial LLM timelines. Parallel branches are never joined. */\n tokenSequences: BehavioralTokenSequence[]\n /** Token values from the longest serial timeline, retained for convenience. */\n inputTokenTrajectory: number[]\n outputTokenTrajectory: number[]\n toolHistogram: Record<string, number>\n totalToolCalls: number\n distinctTools: number\n /** distinct/total tool calls; 1.0 when there are no tool calls. */\n toolDiversityRatio: number\n hasSelfVerification: boolean\n signals: SuboptimalSignal[]\n}\n\nexport interface BehavioralTokenSequence {\n scopeId: string\n spanIds: string[]\n inputTokenTrajectory: Array<number | null>\n outputTokenTrajectory: Array<number | null>\n}\n\ninterface TokenSample {\n span: TraceAnalystSpan\n input: number | null\n output: number | null\n step: number | null\n}\n\n/** ≥ this input-token growth ratio across a run, with no compression, fires. */\nconst INPUT_GROWTH_FACTOR = 3\n/** Tool-usage signals need at least this many calls to be meaningful. */\nconst MIN_TOOL_CALLS = 3\n/** Tool names that read or check state count as self-verification, not mutation.\n * Covers the inspect verbs plus the read/search tools real harnesses use to\n * verify (Claude Code Read/Grep/Glob, codex read_file/ls/cat, git status/diff,\n * test/lint). A pure shell tool (Bash/exec_command) is intentionally NOT matched\n * — its name can't tell a `pytest` from an `rm`. */\nconst VERIFY_RE =\n /verif|eval|inspect|check|assert|validat|review|confirm|read|grep|glob|search|view|\\blist\\b|\\bls\\b|\\bcat\\b|\\bfind\\b|diff|status|\\btest|lint|typecheck/i\n\nfunction num(v: unknown): number | null {\n return typeof v === 'number' && Number.isFinite(v) ? v : null\n}\nfunction numAttr(attrs: Record<string, unknown>, keys: readonly string[]): number | null {\n for (const key of keys) {\n const value = num(attrs[key])\n if (value !== null) return value\n }\n return null\n}\nfunction inputTokensOf(s: TraceAnalystSpan): number | null {\n const exactContext = num(s.attributes[LLM_CONTEXT_TOKENS])\n if (exactContext !== null) return exactContext\n // Foreign producers disagree on whether prompt includes cached tokens.\n // Without an exact total, retain the reported prompt instead of guessing.\n return (\n numAttr(s.attributes, LLM_INPUT_TOKEN_ATTR_KEYS) ?? num(s.attributes['llm.usage.input_tokens'])\n )\n}\nfunction outputTokensOf(s: TraceAnalystSpan): number | null {\n return (\n numAttr(s.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS) ??\n num(s.attributes['llm.usage.output_tokens'])\n )\n}\nfunction stepOf(s: TraceAnalystSpan): number | null {\n return num(s.attributes.step)\n}\nfunction toolNameOf(s: TraceAnalystSpan): string | null {\n if (s.tool_name) return s.tool_name\n for (const key of TOOL_NAME_ATTR_KEYS) {\n const t = s.attributes[key]\n if (typeof t === 'string' && t.length > 0) return t\n }\n return null\n}\n\n/**\n * Reduce a span list to behavioral metrics + fired suboptimality signals.\n * Pure + deterministic: same spans → same output, on any machine, no model.\n */\nexport function computeTraceMetrics(spans: readonly TraceAnalystSpan[]): BehavioralMetrics {\n const traceIds = new Set(spans.map((span) => span.trace_id))\n if (traceIds.size > 1) {\n throw new Error(\n `computeTraceMetrics: expected spans from one trace, received ${traceIds.size} traces`,\n )\n }\n const traceId = traceIds.values().next().value ?? null\n\n const samples: TokenSample[] = spans.map((span) => ({\n span,\n input: inputTokensOf(span),\n output: outputTokensOf(span),\n step: stepOf(span),\n }))\n const llmSamples = samples.filter((sample) => sample.span.kind === 'LLM')\n const tokenSamples =\n llmSamples.length > 0\n ? llmSamples\n : samples.filter((sample) => sample.input !== null || sample.output !== null)\n const tokenSequences = buildTokenSequences(tokenSamples, spans)\n const primarySequence = tokenSequences[0]\n const inputTokenTrajectory =\n primarySequence?.inputTokenTrajectory.filter((value): value is number => value !== null) ?? []\n const outputTokenTrajectory =\n primarySequence?.outputTokenTrajectory.filter((value): value is number => value !== null) ?? []\n const toolHistogram: Record<string, number> = {}\n let hasSelfVerification = false\n\n for (const s of spans) {\n const tool = toolNameOf(s)\n if (tool) {\n toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1\n if (VERIFY_RE.test(tool)) hasSelfVerification = true\n }\n }\n\n const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0)\n const distinctTools = Object.keys(toolHistogram).length\n const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls\n\n const signals: SuboptimalSignal[] = []\n const seenTokenSignals = new Set<SuboptimalCode>()\n for (const sequence of tokenSequences) {\n for (const signal of tokenSignals(sequence)) {\n if (seenTokenSignals.has(signal.code)) continue\n seenTokenSignals.add(signal.code)\n signals.push(signal)\n }\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {\n const only = Object.keys(toolHistogram)[0]!\n signals.push({\n code: 'single-tool-dependency',\n severity: 'medium',\n detail: `All ${totalToolCalls} observed tool calls are \\`${only}\\`; no alternate tool call was observed.`,\n evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 },\n })\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {\n signals.push({\n code: 'no-self-verification',\n severity: 'medium',\n detail: `${totalToolCalls} tool calls were observed without a verification-named tool call.`,\n evidence: { tool_calls: totalToolCalls, verification_calls: 0 },\n })\n }\n\n return {\n traceId,\n llmCallCount: tokenSamples.length,\n tokenSequences,\n inputTokenTrajectory,\n outputTokenTrajectory,\n toolHistogram,\n totalToolCalls,\n distinctTools,\n toolDiversityRatio,\n hasSelfVerification,\n signals,\n }\n}\n\nfunction buildTokenSequences(\n samples: readonly TokenSample[],\n spans: readonly TraceAnalystSpan[],\n): BehavioralTokenSequence[] {\n const spansById = new Map(spans.map((span) => [span.span_id, span]))\n const executionScopeFor = createTokenExecutionScopeResolver(spansById)\n const scopedSamples = samples.map((sample) => ({\n sample,\n ...executionScopeFor(sample.span),\n }))\n const trackByLane = executionTrackByLane(scopedSamples)\n const byTrack = new Map<string, { scopeId: string; samples: TokenSample[] }>()\n for (const scoped of scopedSamples) {\n const trackId = trackByLane.get(scoped.key)!\n const track = byTrack.get(trackId) ?? { scopeId: scoped.scopeId, samples: [] }\n track.samples.push(scoped.sample)\n byTrack.set(trackId, track)\n }\n\n const sequences: BehavioralTokenSequence[] = []\n for (const { scopeId, samples: tracked } of byTrack.values()) {\n const runs = serialTokenRuns([...tracked].sort(compareTokenSamples))\n runs.forEach((run, index) => {\n sequences.push({\n scopeId: runs.length === 1 ? scopeId : `${scopeId}#${index + 1}`,\n spanIds: run.map((sample) => sample.span.span_id),\n inputTokenTrajectory: run.map((sample) => sample.input),\n outputTokenTrajectory: run.map((sample) => sample.output),\n })\n })\n }\n\n return sequences.sort(\n (a, b) =>\n b.spanIds.length - a.spanIds.length ||\n a.scopeId.localeCompare(b.scopeId) ||\n a.spanIds[0]!.localeCompare(b.spanIds[0]!),\n )\n}\n\ninterface AncestorScope {\n agentId: string | null\n rootId: string | null\n missingParentId: string | null\n laneSpanId: string | null\n}\n\nfunction createTokenExecutionScopeResolver(spansById: ReadonlyMap<string, TraceAnalystSpan>): (\n span: TraceAnalystSpan,\n) => {\n key: string\n scopeKey: string\n scopeId: string\n start: number | null\n end: number | null\n} {\n const cache = new Map<string, AncestorScope>()\n return (span) => {\n const ancestry = resolveAncestorScope(span.parent_span_id, spansById, cache)\n const rootId = ancestry.rootId\n const scopeId = ancestry.agentId\n ? `span:${ancestry.agentId}`\n : ancestry.missingParentId\n ? `parent:${ancestry.missingParentId}`\n : rootId\n ? `root:${rootId}`\n : span.agent_name\n ? `agent:${span.agent_name}`\n : `trace:${span.trace_id}`\n const scopeSpanId = ancestry.agentId ?? ancestry.missingParentId ?? rootId\n const laneSpan = ancestry.laneSpanId ? spansById.get(ancestry.laneSpanId) : undefined\n const direct = scopeSpanId === null || ancestry.laneSpanId === scopeSpanId\n const timedSpan = direct ? span : laneSpan\n return {\n key: JSON.stringify([scopeId, direct ? span.span_id : ancestry.laneSpanId]),\n scopeKey: scopeId,\n scopeId,\n start: timedSpan ? spanEpochMillis(timedSpan.start_time) : null,\n end: timedSpan ? spanEpochMillis(timedSpan.end_time) : null,\n }\n }\n}\n\nfunction resolveAncestorScope(\n startId: string | null,\n spansById: ReadonlyMap<string, TraceAnalystSpan>,\n cache: Map<string, AncestorScope>,\n): AncestorScope {\n const empty: AncestorScope = {\n agentId: null,\n rootId: null,\n missingParentId: null,\n laneSpanId: null,\n }\n if (!startId) return empty\n\n const path: string[] = []\n const pathIndex = new Map<string, number>()\n let currentId: string | null = startId\n let resolved = empty\n while (currentId) {\n const cached = cache.get(currentId)\n if (cached) {\n resolved = cached\n break\n }\n const cycleStart = pathIndex.get(currentId)\n if (cycleStart !== undefined) {\n resolved = {\n agentId: null,\n rootId: [...path.slice(cycleStart)].sort()[0]!,\n missingParentId: null,\n laneSpanId: [...path.slice(cycleStart)].sort()[0]!,\n }\n break\n }\n const current = spansById.get(currentId)\n if (!current) {\n resolved = {\n agentId: null,\n rootId: null,\n missingParentId: currentId,\n laneSpanId: currentId,\n }\n break\n }\n if (current.kind === 'AGENT') {\n resolved = {\n agentId: current.span_id,\n rootId: null,\n missingParentId: null,\n laneSpanId: current.span_id,\n }\n break\n }\n pathIndex.set(currentId, path.length)\n path.push(currentId)\n currentId = current.parent_span_id\n }\n\n for (let index = path.length - 1; index >= 0; index -= 1) {\n if (resolved.agentId === null && resolved.rootId === null) {\n resolved = { ...resolved, rootId: path[index]!, laneSpanId: path[index]! }\n } else if (\n resolved.laneSpanId === (resolved.agentId ?? resolved.missingParentId ?? resolved.rootId)\n ) {\n resolved = { ...resolved, laneSpanId: path[index]! }\n }\n cache.set(path[index]!, resolved)\n }\n return resolved\n}\n\nfunction compareTokenSamples(a: TokenSample, b: TokenSample): number {\n const aStart = spanEpochMillis(a.span.start_time)\n const bStart = spanEpochMillis(b.span.start_time)\n if (aStart === null && bStart !== null) return 1\n if (aStart !== null && bStart === null) return -1\n if (aStart !== null && bStart !== null && aStart !== bStart) return aStart - bStart\n if (a.step !== null && b.step !== null && a.step !== b.step) return a.step - b.step\n return a.span.span_id.localeCompare(b.span.span_id)\n}\n\nfunction serialTokenRuns(ordered: readonly TokenSample[]): TokenSample[][] {\n const runs: TokenSample[][] = []\n let serial: TokenSample[] = []\n let overlap: TokenSample[] = []\n let overlapEnd = Number.NEGATIVE_INFINITY\n\n const flushSerial = () => {\n if (serial.length > 0) runs.push(serial)\n serial = []\n }\n const flushOverlap = () => {\n if (overlap.length === 1) {\n serial.push(overlap[0]!)\n } else if (overlap.length > 1) {\n flushSerial()\n for (const sample of overlap) runs.push([sample])\n }\n overlap = []\n overlapEnd = Number.NEGATIVE_INFINITY\n }\n\n for (const sample of ordered) {\n const start = spanEpochMillis(sample.span.start_time)\n const end = spanEpochMillis(sample.span.end_time)\n if (start === null || end === null || sample.span.duration_ms <= 0 || end < start) {\n flushOverlap()\n flushSerial()\n runs.push([sample])\n continue\n }\n if (overlap.length > 0 && start >= overlapEnd) flushOverlap()\n overlap.push(sample)\n overlapEnd = Math.max(overlapEnd, end)\n }\n flushOverlap()\n flushSerial()\n return runs\n}\n\nfunction tokenSignals(sequence: BehavioralTokenSequence): SuboptimalSignal[] {\n const signals: SuboptimalSignal[] = []\n const inputs = sequence.inputTokenTrajectory\n const outputs = sequence.outputTokenTrajectory\n if (inputs.length >= 3 && inputs.every((value): value is number => value !== null)) {\n const first = inputs[0]!\n const last = inputs[inputs.length - 1]!\n const isMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous)\n const growthFromZero = first === 0 && last > 0\n const growth = growthFromZero ? Infinity : first > 0 ? last / first : 0\n if (isMonotonic && last > first && growth >= INPUT_GROWTH_FACTOR) {\n const growthLabel = growthFromZero ? '0→nonzero (unbounded)' : `${growth.toFixed(1)}x`\n signals.push({\n code: 'monotonic-input-growth',\n severity: 'high',\n detail: `LLM input tokens grew ${growthLabel} (${first}→${last}) across ${inputs.length} serial calls without an intervening decrease.`,\n evidence: {\n first,\n last,\n growth_x: growthFromZero ? 'unbounded' : Number(growth.toFixed(2)),\n calls: inputs.length,\n scope: sequence.scopeId,\n first_span_id: sequence.spanIds[0]!,\n last_span_id: sequence.spanIds[sequence.spanIds.length - 1]!,\n },\n })\n }\n }\n\n if (\n inputs.length >= 3 &&\n inputs.length === outputs.length &&\n inputs.every((value): value is number => value !== null) &&\n outputs.every((value): value is number => value !== null)\n ) {\n const first = outputs[0]!\n const last = outputs[outputs.length - 1]!\n const inputIsMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous)\n const outputIsMonotonic = everyAdjacent(outputs, (previous, current) => current <= previous)\n const inputGrew = inputs[inputs.length - 1]! > inputs[0]!\n if (inputIsMonotonic && inputGrew && outputIsMonotonic && last < first) {\n signals.push({\n code: 'output-length-decay',\n severity: 'medium',\n detail: `LLM output tokens shrank ${first}→${last} over ${outputs.length} serial calls while input tokens increased monotonically.`,\n evidence: {\n first,\n last,\n calls: outputs.length,\n scope: sequence.scopeId,\n first_span_id: sequence.spanIds[0]!,\n last_span_id: sequence.spanIds[sequence.spanIds.length - 1]!,\n },\n })\n }\n }\n return signals\n}\n\nfunction everyAdjacent(\n values: readonly number[],\n predicate: (previous: number, current: number) => boolean,\n): boolean {\n return values.slice(1).every((current, index) => predicate(values[index]!, current))\n}\n","/**\n * `behavioralAnalyst` — a DETERMINISTIC analyst (cost.kind = 'deterministic',\n * never calls the LLM). It produces the efficiency/behavioral findings a\n * tolerant agentic analyzer (HALO) re-derives per run inside the model —\n * context bloat, output decay, tool monoculture, missing self-verification —\n * directly from arithmetic over spans (`computeTraceMetrics`).\n *\n * Why it matters: these findings are model-agnostic BY CONSTRUCTION (no model\n * in the loop), so they cannot return 0 on a weak model the way the Ax-RLM\n * does — and they are strictly more reliable than HALO, which spends tokens\n * re-deriving the same numbers and can hallucinate the trend. The agentic\n * RLM kinds remain for SEMANTIC findings that genuinely need a model; this\n * analyst owns the behavioral class.\n */\n\nimport {\n type BehavioralMetrics,\n computeTraceMetrics,\n type SuboptimalCode,\n} from '../trace-analyst/behavioral-metrics'\nimport type { TraceAnalysisStore } from '../trace-analyst/store'\nimport { type Analyst, type AnalystFinding, makeFinding } from './types'\n\nconst RECOMMENDED_ACTION: Record<SuboptimalCode, string> = {\n 'monotonic-input-growth':\n 'Inspect context assembly; if prior history is repeatedly included, summarize completed work before the next model call.',\n 'output-length-decay':\n 'Check late-step completeness; if shorter responses omit required work, add explicit completion criteria to the agent instructions.',\n 'single-tool-dependency':\n 'Test whether an inspect or verification tool improves outcomes after the repeated call fails or returns no progress.',\n 'no-self-verification':\n 'After state-changing actions, require an observable check before the agent proceeds.',\n}\n\nconst ANALYST_ID = 'efficiency-behavioral'\n\nconst AGGREGATE_CLAIM: Record<SuboptimalCode, (observed: number, analyzed: number) => string> = {\n 'monotonic-input-growth': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces showed input tokens grow from zero to nonzero or to at least 3x their initial value across at least 3 serial model calls without a decrease.`,\n 'output-length-decay': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces showed output tokens decrease while input tokens increased monotonically across at least 3 serial model calls.`,\n 'single-tool-dependency': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces used only one named tool across at least 3 tool calls.`,\n 'no-self-verification': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces had at least 3 tool calls without a verification-named tool call.`,\n}\n\n/**\n * Map computed signals → structured AnalystFindings. Pure: no LLM, no clock\n * dependence beyond `produced_at` (overridable for deterministic tests).\n */\nexport function deriveEfficiencyFindings(\n metrics: BehavioralMetrics,\n opts: { analystId?: string; producedAt?: string } = {},\n): AnalystFinding[] {\n const analystId = opts.analystId ?? ANALYST_ID\n const traceId = metrics.traceId\n return metrics.signals.map((sig) =>\n makeFinding({\n analyst_id: analystId,\n area: 'efficiency',\n subject: sig.code,\n claim: sig.detail,\n severity: sig.severity,\n // Deterministic arithmetic over spans, not a model judgment → certain.\n confidence: 1,\n evidence_refs: [\n {\n kind: 'metric',\n uri: traceId\n ? `metric://trace/${encodeURIComponent(traceId)}/efficiency/${sig.code}`\n : `metric://efficiency/${sig.code}`,\n excerpt: JSON.stringify(sig.evidence),\n },\n ],\n recommended_action: RECOMMENDED_ACTION[sig.code],\n metadata: {\n deterministic: true,\n evidence: sig.evidence,\n ...(traceId ? { trace_id: traceId } : {}),\n },\n id_basis: sig.code,\n ...(opts.producedAt ? { produced_at: opts.producedAt } : {}),\n }),\n )\n}\n\n/** The deterministic behavioral/efficiency analyst (no LLM, any-model). */\nexport function behavioralAnalyst(): Analyst<TraceAnalysisStore> {\n return {\n id: ANALYST_ID,\n description:\n 'Deterministic behavioral/efficiency findings over OTLP spans — token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.',\n inputKind: 'trace-store',\n cost: { kind: 'deterministic' },\n version: '2.0.0',\n async analyze(store) {\n const overview = await store.getOverview()\n const analyzedTraceIds = [...new Set(overview.sample_trace_ids)].sort()\n const findingsById = new Map<\n string,\n { finding: AnalystFinding; traceIds: string[]; evidence: AnalystFinding['evidence_refs'] }\n >()\n for (const traceId of analyzedTraceIds) {\n const viewed = await store.viewTrace({ trace_id: traceId })\n if (viewed.trace_id !== traceId) {\n throw new Error(\n `behavioralAnalyst: requested trace '${traceId}', received '${viewed.trace_id}'`,\n )\n }\n if (!viewed.spans) {\n throw new Error(\n `behavioralAnalyst: trace '${traceId}' is oversized; complete spans are required`,\n )\n }\n const metrics = computeTraceMetrics(viewed.spans)\n if (metrics.traceId !== null && metrics.traceId !== traceId) {\n throw new Error(\n `behavioralAnalyst: requested trace '${traceId}', received '${metrics.traceId}'`,\n )\n }\n for (const finding of deriveEfficiencyFindings(metrics)) {\n const current = findingsById.get(finding.finding_id)\n if (!current) {\n findingsById.set(finding.finding_id, {\n finding,\n traceIds: [traceId],\n evidence: [...finding.evidence_refs],\n })\n continue\n }\n current.traceIds.push(traceId)\n current.evidence.push(...finding.evidence_refs)\n }\n }\n return [...findingsById.values()].map(({ finding, traceIds, evidence }) => ({\n ...finding,\n claim: AGGREGATE_CLAIM[finding.subject as SuboptimalCode](\n traceIds.length,\n analyzedTraceIds.length,\n ),\n rationale: `${traceIds.length}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,\n evidence_refs: evidence,\n metadata: {\n deterministic: true,\n trace_ids: traceIds,\n observed_trace_count: traceIds.length,\n analyzed_trace_count: analyzedTraceIds.length,\n },\n }))\n },\n }\n}\n","/**\n * `buildDefaultAnalystRegistry` — the canonical analyst suite, so consumers\n * stop hand-wiring `new AnalystRegistry()` + per-kind `createTraceAnalystKind`.\n *\n * The deterministic `behavioralAnalyst` is ALWAYS registered (it needs no\n * model and is model-agnostic by construction). The agentic RLM kinds are\n * registered only when an `ai` service is supplied — so a caller with no LLM\n * still gets the full behavioral/efficiency diagnosis, and the substrate's\n * \"any model (including no model)\" guarantee holds at the suite level.\n */\n\nimport type { AxAIService } from '@ax-llm/ax'\nimport { behavioralAnalyst } from './behavioral-analyst'\nimport { createTraceAnalystKind, type TraceAnalystKindSpec } from './kind-factory'\nimport { DEFAULT_TRACE_ANALYST_KINDS } from './kinds'\nimport { AnalystRegistry, type AnalystRegistryOptions } from './registry'\n\nexport interface DefaultAnalystRegistryOptions {\n /** Ax service for the agentic RLM kinds. Omit → only the deterministic analyst. */\n ai?: AxAIService\n /** Model for the agentic kinds (falls back to the ai service default). */\n model?: string\n /** Which agentic kinds to register when `ai` is present. Default = the shipped suite. */\n kinds?: readonly TraceAnalystKindSpec[]\n /** Set false to omit the deterministic behavioral analyst (default: include). */\n includeBehavioral?: boolean\n /** Forwarded to the AnalystRegistry constructor (signal, tags, priorFindings). */\n registry?: AnalystRegistryOptions\n}\n\nexport function buildDefaultAnalystRegistry(\n opts: DefaultAnalystRegistryOptions = {},\n): AnalystRegistry {\n const registry = new AnalystRegistry(opts.registry)\n if (opts.includeBehavioral !== false) {\n registry.register(behavioralAnalyst())\n }\n if (opts.ai) {\n const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS\n for (const spec of kinds) {\n registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }))\n }\n }\n return registry\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/analyst/chat-client.ts","../src/trace-analyst/behavioral-metrics.ts","../src/analyst/behavioral-analyst.ts","../src/analyst/default-registry.ts"],"sourcesContent":["/**\n * ChatClient — the single LLM abstraction analysts call.\n *\n * agent-eval already ships an `LlmClient` (OpenAI-compatible, retry,\n * graceful JSON-schema degrade) and judges that talk to `TCloud`. Two\n * mixed patterns force every analyst author to pick a transport, which\n * couples analyst code to runtime concerns (cli-bridge vs router vs\n * sandbox-sdk) it shouldn't know about.\n *\n * `ChatClient` is one interface every analyst takes via `AnalystContext.chat`.\n * The operator decides at the registry boundary which transport binds\n * to it. Analyst code stays transport-agnostic; swapping production\n * (sandbox-sdk) for local dev (cli-bridge) or tests (mock) is a one-\n * line factory call.\n *\n * Designed to coexist: existing `LlmClient` callers and existing\n * `TCloud`-based judges keep working untouched. New analyst code uses\n * `ChatClient`. When old call sites migrate, they pick up budgeting,\n * cancellation, and unified telemetry for free.\n */\n\nimport {\n type LlmCallRequest,\n type LlmCallResult,\n LlmClient,\n type LlmClientOptions,\n} from '../llm-client'\n\n/**\n * Unified chat interface. Mirrors LlmCallRequest/Result so the OpenAI-\n * compatible mental model stays. Two methods: a one-shot `chat()` and\n * an `streamChat()` for future agentic loops (not yet exposed).\n */\nexport interface ChatClient {\n /** Display name of the bound transport — included in telemetry. */\n readonly transport: ChatTransport\n /** Default model when caller omits — operators bind this per environment. */\n readonly defaultModel?: string\n /** Total provider attempts this transport can make for one chat call. */\n readonly maximumAttempts?: number\n\n /** Implementations must enforce `req.maxTokens` when it is present. */\n chat(req: ChatRequest, opts?: ChatCallOpts): Promise<ChatResponse>\n}\n\nexport type ChatTransport =\n | 'router' // router.tangle.tools — production paid models\n | 'sandbox-sdk' // box.streamPrompt() — chat completion via sandbox SDK\n | 'cli-bridge' // local cli-bridge for dev / local-only runs\n | 'direct-provider' // direct OpenAI / Anthropic / etc. — bypass router\n | 'mock' // test-time injection\n\nexport interface ChatRequest extends Omit<LlmCallRequest, 'model'> {\n /** Optional — falls back to ChatClient.defaultModel. */\n model?: string\n}\n\nexport type ChatResponse = LlmCallResult\n\nexport interface ChatCallOpts {\n /** Cancel the in-flight request. */\n signal?: AbortSignal\n /** Hard USD ceiling for this single call (informational; the underlying transport may not enforce). */\n maxCostUsd?: number\n /** Correlation tag carried into request headers when the transport allows. */\n correlationId?: string\n /** Stable provider idempotency key for retries/redrives of one paid call. */\n idempotencyKey?: string\n}\n\n// ── Factory ─────────────────────────────────────────────────────────\n\nexport type CreateChatClientOpts =\n | RouterTransportOpts\n | CliBridgeTransportOpts\n | DirectProviderTransportOpts\n | SandboxSdkTransportOpts\n | MockTransportOpts\n\ninterface BaseTransportOpts {\n defaultModel?: string\n /** Total provider attempts. Required for opaque transports used in capped runs. */\n maximumAttempts?: number\n}\n\nexport interface RouterTransportOpts extends BaseTransportOpts {\n transport: 'router'\n baseUrl?: string\n apiKey: string\n}\n\nexport interface CliBridgeTransportOpts extends BaseTransportOpts {\n transport: 'cli-bridge'\n baseUrl?: string\n bearer?: string\n}\n\nexport interface DirectProviderTransportOpts extends BaseTransportOpts {\n transport: 'direct-provider'\n baseUrl: string\n apiKey: string\n}\n\n/**\n * Sandbox-SDK transport. Provided as a thin pass-through: the caller\n * supplies a callable that mimics LlmClient.chat() against an already-\n * configured Sandbox handle. We don't import the SDK here to keep\n * agent-eval dep-free of @tangle-network/sandbox.\n */\nexport interface SandboxSdkTransportOpts extends BaseTransportOpts {\n transport: 'sandbox-sdk'\n chat: (req: ChatRequest, opts?: ChatCallOpts) => Promise<ChatResponse>\n}\n\n/**\n * Mock transport for tests. The handler receives the request and returns\n * whatever the test wants. No retries, no JSON-schema degrade.\n */\nexport interface MockTransportOpts extends BaseTransportOpts {\n transport: 'mock'\n handler: (req: ChatRequest, opts?: ChatCallOpts) => Promise<ChatResponse>\n}\n\n/**\n * Build a ChatClient bound to a specific transport. The returned client\n * is safe to share across analysts in a single registry run.\n */\nexport function createChatClient(opts: CreateChatClientOpts): ChatClient {\n switch (opts.transport) {\n case 'router':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl ?? 'https://router.tangle.tools/v1',\n apiKey: opts.apiKey,\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'cli-bridge':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl ?? 'http://127.0.0.1:3344/v1',\n apiKey: opts.bearer ?? '',\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'direct-provider':\n return wrapLlmClient(\n opts.transport,\n opts.defaultModel,\n new LlmClient({\n baseUrl: opts.baseUrl,\n apiKey: opts.apiKey,\n maxRetries: opts.maximumAttempts,\n } as LlmClientOptions),\n )\n case 'sandbox-sdk':\n return {\n transport: 'sandbox-sdk',\n defaultModel: opts.defaultModel,\n maximumAttempts: opts.maximumAttempts,\n chat: async (req, callOpts) => opts.chat(resolveModel(req, opts.defaultModel), callOpts),\n }\n case 'mock':\n return {\n transport: 'mock',\n defaultModel: opts.defaultModel,\n maximumAttempts: 1,\n chat: async (req, callOpts) => opts.handler(resolveModel(req, opts.defaultModel), callOpts),\n }\n }\n}\n\nfunction wrapLlmClient(\n transport: ChatTransport,\n defaultModel: string | undefined,\n inner: LlmClient,\n): ChatClient {\n return {\n transport,\n defaultModel,\n maximumAttempts: inner.maximumAttempts,\n chat: (req, callOpts) => {\n const resolved = resolveModel(req, defaultModel)\n const request: LlmCallRequest = {\n model: resolved.model!,\n messages: req.messages,\n jsonMode: req.jsonMode,\n jsonSchema: req.jsonSchema,\n temperature: req.temperature,\n maxTokens: req.maxTokens,\n timeoutMs: req.timeoutMs,\n }\n return inner.call(request, {\n signal: callOpts?.signal,\n idempotencyKey: callOpts?.idempotencyKey,\n })\n },\n }\n}\n\nfunction resolveModel(req: ChatRequest, defaultModel: string | undefined): ChatRequest {\n if (req.model) return req\n if (!defaultModel) {\n throw new Error(\n 'ChatClient.chat: no model on request and no defaultModel on the client. ' +\n 'Either pass req.model or bind defaultModel at createChatClient().',\n )\n }\n return { ...req, model: defaultModel }\n}\n","/**\n * Deterministic behavioral metrics over OTLP spans — pure arithmetic, no LLM.\n *\n * These are the model-independent multiplier: the four trace-quality signals a\n * tolerant analyzer (e.g. HALO) re-derives per run inside the model — token\n * growth, output decay, tool monoculture, missing self-verification — computed\n * here once, in TypeScript, with zero model judgment. A finding that falls out\n * of arithmetic is trivially model-agnostic and cannot hallucinate the trend.\n *\n * General, not trace-specific: the detectors key off token trajectories and\n * tool usage present in any agentic OTLP trace, not any one benchmark.\n */\n\nimport { LLM_CONTEXT_TOKENS } from '../trace/attribute-vocabulary'\nimport { executionTrackByLane } from '../trace/execution-tracks'\nimport {\n LLM_INPUT_TOKEN_ATTR_KEYS,\n LLM_OUTPUT_TOKEN_ATTR_KEYS,\n TOOL_NAME_ATTR_KEYS,\n} from '../trace/otlp-attributes'\nimport { spanEpochMillis } from './otlp-span'\nimport type { TraceAnalystSpan } from './types'\n\nexport type SuboptimalCode =\n | 'monotonic-input-growth'\n | 'output-length-decay'\n | 'single-tool-dependency'\n | 'no-self-verification'\n\nexport interface SuboptimalSignal {\n code: SuboptimalCode\n severity: 'high' | 'medium' | 'low'\n /** Human-readable claim, with the backing numbers inlined. */\n detail: string\n /** The exact figures the detector fired on — auditable, no model in the loop. */\n evidence: Record<string, number | string | boolean>\n}\n\nexport interface BehavioralMetrics {\n /** The only trace represented by these metrics; null when spans are empty. */\n traceId: string | null\n llmCallCount: number\n /** Causally serial LLM timelines. Parallel branches are never joined. */\n tokenSequences: BehavioralTokenSequence[]\n /** Token values from the longest serial timeline, retained for convenience. */\n inputTokenTrajectory: number[]\n outputTokenTrajectory: number[]\n toolHistogram: Record<string, number>\n totalToolCalls: number\n distinctTools: number\n /** distinct/total tool calls; 1.0 when there are no tool calls. */\n toolDiversityRatio: number\n hasSelfVerification: boolean\n signals: SuboptimalSignal[]\n}\n\nexport interface BehavioralTokenSequence {\n scopeId: string\n spanIds: string[]\n inputTokenTrajectory: Array<number | null>\n outputTokenTrajectory: Array<number | null>\n}\n\ninterface TokenSample {\n span: TraceAnalystSpan\n input: number | null\n output: number | null\n step: number | null\n}\n\n/** ≥ this input-token growth ratio across a run, with no compression, fires. */\nconst INPUT_GROWTH_FACTOR = 3\n/** Tool-usage signals need at least this many calls to be meaningful. */\nconst MIN_TOOL_CALLS = 3\n/** Tool names that read or check state count as self-verification, not mutation.\n * Covers the inspect verbs plus the read/search tools real harnesses use to\n * verify (Claude Code Read/Grep/Glob, codex read_file/ls/cat, git status/diff,\n * test/lint). A pure shell tool (Bash/exec_command) is intentionally NOT matched\n * — its name can't tell a `pytest` from an `rm`. */\nconst VERIFY_RE =\n /verif|eval|inspect|check|assert|validat|review|confirm|read|grep|glob|search|view|\\blist\\b|\\bls\\b|\\bcat\\b|\\bfind\\b|diff|status|\\btest|lint|typecheck/i\n\nfunction num(v: unknown): number | null {\n return typeof v === 'number' && Number.isFinite(v) ? v : null\n}\nfunction numAttr(attrs: Record<string, unknown>, keys: readonly string[]): number | null {\n for (const key of keys) {\n const value = num(attrs[key])\n if (value !== null) return value\n }\n return null\n}\nfunction inputTokensOf(s: TraceAnalystSpan): number | null {\n const exactContext = num(s.attributes[LLM_CONTEXT_TOKENS])\n if (exactContext !== null) return exactContext\n // Foreign producers disagree on whether prompt includes cached tokens.\n // Without an exact total, retain the reported prompt instead of guessing.\n return (\n numAttr(s.attributes, LLM_INPUT_TOKEN_ATTR_KEYS) ?? num(s.attributes['llm.usage.input_tokens'])\n )\n}\nfunction outputTokensOf(s: TraceAnalystSpan): number | null {\n return (\n numAttr(s.attributes, LLM_OUTPUT_TOKEN_ATTR_KEYS) ??\n num(s.attributes['llm.usage.output_tokens'])\n )\n}\nfunction stepOf(s: TraceAnalystSpan): number | null {\n return num(s.attributes.step)\n}\nfunction toolNameOf(s: TraceAnalystSpan): string | null {\n if (s.tool_name) return s.tool_name\n for (const key of TOOL_NAME_ATTR_KEYS) {\n const t = s.attributes[key]\n if (typeof t === 'string' && t.length > 0) return t\n }\n return null\n}\n\n/**\n * Reduce a span list to behavioral metrics + fired suboptimality signals.\n * Pure + deterministic: same spans → same output, on any machine, no model.\n */\nexport function computeTraceMetrics(spans: readonly TraceAnalystSpan[]): BehavioralMetrics {\n const traceIds = new Set(spans.map((span) => span.trace_id))\n if (traceIds.size > 1) {\n throw new Error(\n `computeTraceMetrics: expected spans from one trace, received ${traceIds.size} traces`,\n )\n }\n const traceId = traceIds.values().next().value ?? null\n\n const samples: TokenSample[] = spans.map((span) => ({\n span,\n input: inputTokensOf(span),\n output: outputTokensOf(span),\n step: stepOf(span),\n }))\n const llmSamples = samples.filter((sample) => sample.span.kind === 'LLM')\n const tokenSamples =\n llmSamples.length > 0\n ? llmSamples\n : samples.filter((sample) => sample.input !== null || sample.output !== null)\n const tokenSequences = buildTokenSequences(tokenSamples, spans)\n const primarySequence = tokenSequences[0]\n const inputTokenTrajectory =\n primarySequence?.inputTokenTrajectory.filter((value): value is number => value !== null) ?? []\n const outputTokenTrajectory =\n primarySequence?.outputTokenTrajectory.filter((value): value is number => value !== null) ?? []\n const toolHistogram: Record<string, number> = {}\n let hasSelfVerification = false\n\n for (const s of spans) {\n const tool = toolNameOf(s)\n if (tool) {\n toolHistogram[tool] = (toolHistogram[tool] ?? 0) + 1\n if (VERIFY_RE.test(tool)) hasSelfVerification = true\n }\n }\n\n const totalToolCalls = Object.values(toolHistogram).reduce((a, b) => a + b, 0)\n const distinctTools = Object.keys(toolHistogram).length\n const toolDiversityRatio = totalToolCalls === 0 ? 1 : distinctTools / totalToolCalls\n\n const signals: SuboptimalSignal[] = []\n const seenTokenSignals = new Set<SuboptimalCode>()\n for (const sequence of tokenSequences) {\n for (const signal of tokenSignals(sequence)) {\n if (seenTokenSignals.has(signal.code)) continue\n seenTokenSignals.add(signal.code)\n signals.push(signal)\n }\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && distinctTools === 1) {\n const only = Object.keys(toolHistogram)[0]!\n signals.push({\n code: 'single-tool-dependency',\n severity: 'medium',\n detail: `All ${totalToolCalls} observed tool calls are \\`${only}\\`; no alternate tool call was observed.`,\n evidence: { tool: only, calls: totalToolCalls, distinct_tools: 1 },\n })\n }\n\n if (totalToolCalls >= MIN_TOOL_CALLS && !hasSelfVerification) {\n signals.push({\n code: 'no-self-verification',\n severity: 'medium',\n detail: `${totalToolCalls} tool calls were observed without a verification-named tool call.`,\n evidence: { tool_calls: totalToolCalls, verification_calls: 0 },\n })\n }\n\n return {\n traceId,\n llmCallCount: tokenSamples.length,\n tokenSequences,\n inputTokenTrajectory,\n outputTokenTrajectory,\n toolHistogram,\n totalToolCalls,\n distinctTools,\n toolDiversityRatio,\n hasSelfVerification,\n signals,\n }\n}\n\nfunction buildTokenSequences(\n samples: readonly TokenSample[],\n spans: readonly TraceAnalystSpan[],\n): BehavioralTokenSequence[] {\n const spansById = new Map(spans.map((span) => [span.span_id, span]))\n const executionScopeFor = createTokenExecutionScopeResolver(spansById)\n const scopedSamples = samples.map((sample) => ({\n sample,\n ...executionScopeFor(sample.span),\n }))\n const trackByLane = executionTrackByLane(scopedSamples)\n const byTrack = new Map<string, { scopeId: string; samples: TokenSample[] }>()\n for (const scoped of scopedSamples) {\n const trackId = trackByLane.get(scoped.key)!\n const track = byTrack.get(trackId) ?? { scopeId: scoped.scopeId, samples: [] }\n track.samples.push(scoped.sample)\n byTrack.set(trackId, track)\n }\n\n const sequences: BehavioralTokenSequence[] = []\n for (const { scopeId, samples: tracked } of byTrack.values()) {\n const runs = serialTokenRuns([...tracked].sort(compareTokenSamples))\n runs.forEach((run, index) => {\n sequences.push({\n scopeId: runs.length === 1 ? scopeId : `${scopeId}#${index + 1}`,\n spanIds: run.map((sample) => sample.span.span_id),\n inputTokenTrajectory: run.map((sample) => sample.input),\n outputTokenTrajectory: run.map((sample) => sample.output),\n })\n })\n }\n\n return sequences.sort(\n (a, b) =>\n b.spanIds.length - a.spanIds.length ||\n a.scopeId.localeCompare(b.scopeId) ||\n a.spanIds[0]!.localeCompare(b.spanIds[0]!),\n )\n}\n\ninterface AncestorScope {\n agentId: string | null\n rootId: string | null\n missingParentId: string | null\n laneSpanId: string | null\n}\n\nfunction createTokenExecutionScopeResolver(spansById: ReadonlyMap<string, TraceAnalystSpan>): (\n span: TraceAnalystSpan,\n) => {\n key: string\n scopeKey: string\n scopeId: string\n start: number | null\n end: number | null\n} {\n const cache = new Map<string, AncestorScope>()\n return (span) => {\n const ancestry = resolveAncestorScope(span.parent_span_id, spansById, cache)\n const rootId = ancestry.rootId\n const scopeId = ancestry.agentId\n ? `span:${ancestry.agentId}`\n : ancestry.missingParentId\n ? `parent:${ancestry.missingParentId}`\n : rootId\n ? `root:${rootId}`\n : span.agent_name\n ? `agent:${span.agent_name}`\n : `trace:${span.trace_id}`\n const scopeSpanId = ancestry.agentId ?? ancestry.missingParentId ?? rootId\n const laneSpan = ancestry.laneSpanId ? spansById.get(ancestry.laneSpanId) : undefined\n const direct = scopeSpanId === null || ancestry.laneSpanId === scopeSpanId\n const timedSpan = direct ? span : laneSpan\n return {\n key: JSON.stringify([scopeId, direct ? span.span_id : ancestry.laneSpanId]),\n scopeKey: scopeId,\n scopeId,\n start: timedSpan ? spanEpochMillis(timedSpan.start_time) : null,\n end: timedSpan ? spanEpochMillis(timedSpan.end_time) : null,\n }\n }\n}\n\nfunction resolveAncestorScope(\n startId: string | null,\n spansById: ReadonlyMap<string, TraceAnalystSpan>,\n cache: Map<string, AncestorScope>,\n): AncestorScope {\n const empty: AncestorScope = {\n agentId: null,\n rootId: null,\n missingParentId: null,\n laneSpanId: null,\n }\n if (!startId) return empty\n\n const path: string[] = []\n const pathIndex = new Map<string, number>()\n let currentId: string | null = startId\n let resolved = empty\n while (currentId) {\n const cached = cache.get(currentId)\n if (cached) {\n resolved = cached\n break\n }\n const cycleStart = pathIndex.get(currentId)\n if (cycleStart !== undefined) {\n resolved = {\n agentId: null,\n rootId: [...path.slice(cycleStart)].sort()[0]!,\n missingParentId: null,\n laneSpanId: [...path.slice(cycleStart)].sort()[0]!,\n }\n break\n }\n const current = spansById.get(currentId)\n if (!current) {\n resolved = {\n agentId: null,\n rootId: null,\n missingParentId: currentId,\n laneSpanId: currentId,\n }\n break\n }\n if (current.kind === 'AGENT') {\n resolved = {\n agentId: current.span_id,\n rootId: null,\n missingParentId: null,\n laneSpanId: current.span_id,\n }\n break\n }\n pathIndex.set(currentId, path.length)\n path.push(currentId)\n currentId = current.parent_span_id\n }\n\n for (let index = path.length - 1; index >= 0; index -= 1) {\n if (resolved.agentId === null && resolved.rootId === null) {\n resolved = { ...resolved, rootId: path[index]!, laneSpanId: path[index]! }\n } else if (\n resolved.laneSpanId === (resolved.agentId ?? resolved.missingParentId ?? resolved.rootId)\n ) {\n resolved = { ...resolved, laneSpanId: path[index]! }\n }\n cache.set(path[index]!, resolved)\n }\n return resolved\n}\n\nfunction compareTokenSamples(a: TokenSample, b: TokenSample): number {\n const aStart = spanEpochMillis(a.span.start_time)\n const bStart = spanEpochMillis(b.span.start_time)\n if (aStart === null && bStart !== null) return 1\n if (aStart !== null && bStart === null) return -1\n if (aStart !== null && bStart !== null && aStart !== bStart) return aStart - bStart\n if (a.step !== null && b.step !== null && a.step !== b.step) return a.step - b.step\n return a.span.span_id.localeCompare(b.span.span_id)\n}\n\nfunction serialTokenRuns(ordered: readonly TokenSample[]): TokenSample[][] {\n const runs: TokenSample[][] = []\n let serial: TokenSample[] = []\n let overlap: TokenSample[] = []\n let overlapEnd = Number.NEGATIVE_INFINITY\n\n const flushSerial = () => {\n if (serial.length > 0) runs.push(serial)\n serial = []\n }\n const flushOverlap = () => {\n if (overlap.length === 1) {\n serial.push(overlap[0]!)\n } else if (overlap.length > 1) {\n flushSerial()\n for (const sample of overlap) runs.push([sample])\n }\n overlap = []\n overlapEnd = Number.NEGATIVE_INFINITY\n }\n\n for (const sample of ordered) {\n const start = spanEpochMillis(sample.span.start_time)\n const end = spanEpochMillis(sample.span.end_time)\n if (start === null || end === null || sample.span.duration_ms <= 0 || end < start) {\n flushOverlap()\n flushSerial()\n runs.push([sample])\n continue\n }\n if (overlap.length > 0 && start >= overlapEnd) flushOverlap()\n overlap.push(sample)\n overlapEnd = Math.max(overlapEnd, end)\n }\n flushOverlap()\n flushSerial()\n return runs\n}\n\nfunction tokenSignals(sequence: BehavioralTokenSequence): SuboptimalSignal[] {\n const signals: SuboptimalSignal[] = []\n const inputs = sequence.inputTokenTrajectory\n const outputs = sequence.outputTokenTrajectory\n if (inputs.length >= 3 && inputs.every((value): value is number => value !== null)) {\n const first = inputs[0]!\n const last = inputs[inputs.length - 1]!\n const isMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous)\n const growthFromZero = first === 0 && last > 0\n const growth = growthFromZero ? Infinity : first > 0 ? last / first : 0\n if (isMonotonic && last > first && growth >= INPUT_GROWTH_FACTOR) {\n const growthLabel = growthFromZero ? '0→nonzero (unbounded)' : `${growth.toFixed(1)}x`\n signals.push({\n code: 'monotonic-input-growth',\n severity: 'high',\n detail: `LLM input tokens grew ${growthLabel} (${first}→${last}) across ${inputs.length} serial calls without an intervening decrease.`,\n evidence: {\n first,\n last,\n growth_x: growthFromZero ? 'unbounded' : Number(growth.toFixed(2)),\n calls: inputs.length,\n scope: sequence.scopeId,\n first_span_id: sequence.spanIds[0]!,\n last_span_id: sequence.spanIds[sequence.spanIds.length - 1]!,\n },\n })\n }\n }\n\n if (\n inputs.length >= 3 &&\n inputs.length === outputs.length &&\n inputs.every((value): value is number => value !== null) &&\n outputs.every((value): value is number => value !== null)\n ) {\n const first = outputs[0]!\n const last = outputs[outputs.length - 1]!\n const inputIsMonotonic = everyAdjacent(inputs, (previous, current) => current >= previous)\n const outputIsMonotonic = everyAdjacent(outputs, (previous, current) => current <= previous)\n const inputGrew = inputs[inputs.length - 1]! > inputs[0]!\n if (inputIsMonotonic && inputGrew && outputIsMonotonic && last < first) {\n signals.push({\n code: 'output-length-decay',\n severity: 'medium',\n detail: `LLM output tokens shrank ${first}→${last} over ${outputs.length} serial calls while input tokens increased monotonically.`,\n evidence: {\n first,\n last,\n calls: outputs.length,\n scope: sequence.scopeId,\n first_span_id: sequence.spanIds[0]!,\n last_span_id: sequence.spanIds[sequence.spanIds.length - 1]!,\n },\n })\n }\n }\n return signals\n}\n\nfunction everyAdjacent(\n values: readonly number[],\n predicate: (previous: number, current: number) => boolean,\n): boolean {\n return values.slice(1).every((current, index) => predicate(values[index]!, current))\n}\n","/**\n * `behavioralAnalyst` — a DETERMINISTIC analyst (cost.kind = 'deterministic',\n * never calls the LLM). It produces the efficiency/behavioral findings a\n * tolerant agentic analyzer (HALO) re-derives per run inside the model —\n * context bloat, output decay, tool monoculture, missing self-verification —\n * directly from arithmetic over spans (`computeTraceMetrics`).\n *\n * Why it matters: these findings are model-agnostic BY CONSTRUCTION (no model\n * in the loop), so they cannot return 0 on a weak model the way the Ax-RLM\n * does — and they are strictly more reliable than HALO, which spends tokens\n * re-deriving the same numbers and can hallucinate the trend. The agentic\n * RLM kinds remain for SEMANTIC findings that genuinely need a model; this\n * analyst owns the behavioral class.\n */\n\nimport {\n type BehavioralMetrics,\n computeTraceMetrics,\n type SuboptimalCode,\n} from '../trace-analyst/behavioral-metrics'\nimport type { TraceAnalysisStore } from '../trace-analyst/store'\nimport { type Analyst, type AnalystFinding, makeFinding } from './types'\n\nconst RECOMMENDED_ACTION: Record<SuboptimalCode, string> = {\n 'monotonic-input-growth':\n 'Inspect context assembly; if prior history is repeatedly included, summarize completed work before the next model call.',\n 'output-length-decay':\n 'Check late-step completeness; if shorter responses omit required work, add explicit completion criteria to the agent instructions.',\n 'single-tool-dependency':\n 'Test whether an inspect or verification tool improves outcomes after the repeated call fails or returns no progress.',\n 'no-self-verification':\n 'After state-changing actions, require an observable check before the agent proceeds.',\n}\n\nconst ANALYST_ID = 'efficiency-behavioral'\n\nconst AGGREGATE_CLAIM: Record<SuboptimalCode, (observed: number, analyzed: number) => string> = {\n 'monotonic-input-growth': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces showed input tokens grow from zero to nonzero or to at least 3x their initial value across at least 3 serial model calls without a decrease.`,\n 'output-length-decay': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces showed output tokens decrease while input tokens increased monotonically across at least 3 serial model calls.`,\n 'single-tool-dependency': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces used only one named tool across at least 3 tool calls.`,\n 'no-self-verification': (observed, analyzed) =>\n `${observed}/${analyzed} analyzed traces had at least 3 tool calls without a verification-named tool call.`,\n}\n\n/**\n * Map computed signals → structured AnalystFindings. Pure: no LLM, no clock\n * dependence beyond `produced_at` (overridable for deterministic tests).\n */\nexport function deriveEfficiencyFindings(\n metrics: BehavioralMetrics,\n opts: { analystId?: string; producedAt?: string } = {},\n): AnalystFinding[] {\n const analystId = opts.analystId ?? ANALYST_ID\n const traceId = metrics.traceId\n return metrics.signals.map((sig) =>\n makeFinding({\n analyst_id: analystId,\n area: 'efficiency',\n subject: sig.code,\n claim: sig.detail,\n severity: sig.severity,\n // Deterministic arithmetic over spans, not a model judgment → certain.\n confidence: 1,\n evidence_refs: [\n {\n kind: 'metric',\n uri: traceId\n ? `metric://trace/${encodeURIComponent(traceId)}/efficiency/${sig.code}`\n : `metric://efficiency/${sig.code}`,\n excerpt: JSON.stringify(sig.evidence),\n },\n ],\n recommended_action: RECOMMENDED_ACTION[sig.code],\n metadata: {\n deterministic: true,\n evidence: sig.evidence,\n ...(traceId ? { trace_id: traceId } : {}),\n },\n id_basis: sig.code,\n ...(opts.producedAt ? { produced_at: opts.producedAt } : {}),\n }),\n )\n}\n\n/** The deterministic behavioral/efficiency analyst (no LLM, any-model). */\nexport function behavioralAnalyst(): Analyst<TraceAnalysisStore> {\n return {\n id: ANALYST_ID,\n description:\n 'Deterministic behavioral/efficiency findings over OTLP spans — token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.',\n inputKind: 'trace-store',\n cost: { kind: 'deterministic' },\n version: '2.0.0',\n async analyze(store) {\n const overview = await store.getOverview()\n const analyzedTraceIds = [...new Set(overview.sample_trace_ids)].sort()\n const findingsById = new Map<\n string,\n { finding: AnalystFinding; traceIds: string[]; evidence: AnalystFinding['evidence_refs'] }\n >()\n for (const traceId of analyzedTraceIds) {\n const viewed = await store.viewTrace({ trace_id: traceId })\n if (viewed.trace_id !== traceId) {\n throw new Error(\n `behavioralAnalyst: requested trace '${traceId}', received '${viewed.trace_id}'`,\n )\n }\n if (!viewed.spans) {\n throw new Error(\n `behavioralAnalyst: trace '${traceId}' is oversized; complete spans are required`,\n )\n }\n const metrics = computeTraceMetrics(viewed.spans)\n if (metrics.traceId !== null && metrics.traceId !== traceId) {\n throw new Error(\n `behavioralAnalyst: requested trace '${traceId}', received '${metrics.traceId}'`,\n )\n }\n for (const finding of deriveEfficiencyFindings(metrics)) {\n const current = findingsById.get(finding.finding_id)\n if (!current) {\n findingsById.set(finding.finding_id, {\n finding,\n traceIds: [traceId],\n evidence: [...finding.evidence_refs],\n })\n continue\n }\n current.traceIds.push(traceId)\n current.evidence.push(...finding.evidence_refs)\n }\n }\n return [...findingsById.values()].map(({ finding, traceIds, evidence }) => ({\n ...finding,\n claim: AGGREGATE_CLAIM[finding.subject as SuboptimalCode](\n traceIds.length,\n analyzedTraceIds.length,\n ),\n rationale: `${traceIds.length}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,\n evidence_refs: evidence,\n metadata: {\n deterministic: true,\n trace_ids: traceIds,\n observed_trace_count: traceIds.length,\n analyzed_trace_count: analyzedTraceIds.length,\n },\n }))\n },\n }\n}\n","/**\n * `buildDefaultAnalystRegistry` — the canonical analyst suite, so consumers\n * stop hand-wiring `new AnalystRegistry()` + per-kind `createTraceAnalystKind`.\n *\n * The deterministic `behavioralAnalyst` is ALWAYS registered (it needs no\n * model and is model-agnostic by construction). The agentic RLM kinds are\n * registered only when an `ai` service is supplied — so a caller with no LLM\n * still gets the full behavioral/efficiency diagnosis, and the substrate's\n * \"any model (including no model)\" guarantee holds at the suite level.\n */\n\nimport type { AxAIService } from '@ax-llm/ax'\nimport { behavioralAnalyst } from './behavioral-analyst'\nimport { createTraceAnalystKind, type TraceAnalystKindSpec } from './kind-factory'\nimport { DEFAULT_TRACE_ANALYST_KINDS } from './kinds'\nimport { AnalystRegistry, type AnalystRegistryOptions } from './registry'\n\nexport interface DefaultAnalystRegistryOptions {\n /** Ax service for the agentic RLM kinds. Omit → only the deterministic analyst. */\n ai?: AxAIService\n /** Required unless `ai` was created by `createAnalystAi`. */\n model?: string\n /** Which agentic kinds to register when `ai` is present. Default = the shipped suite. */\n kinds?: readonly TraceAnalystKindSpec[]\n /** Set false to omit the deterministic behavioral analyst (default: include). */\n includeBehavioral?: boolean\n /** Forwarded to the AnalystRegistry constructor (signal, tags, priorFindings). */\n registry?: AnalystRegistryOptions\n}\n\nexport function buildDefaultAnalystRegistry(\n opts: DefaultAnalystRegistryOptions = {},\n): AnalystRegistry {\n const registry = new AnalystRegistry(opts.registry)\n if (opts.includeBehavioral !== false) {\n registry.register(behavioralAnalyst())\n }\n if (opts.ai) {\n const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS\n for (const spec of kinds) {\n registry.register(createTraceAnalystKind(spec, { ai: opts.ai, model: opts.model }))\n }\n }\n return registry\n}\n"],"mappings":"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","names":[]}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
LLM_OUTPUT_TOKEN_ATTR_KEYS,
|
|
7
7
|
LLM_REASONING_TOKEN_ATTR_KEYS,
|
|
8
8
|
RUN_COST_ATTR_KEYS
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-K4DBDHLK.js";
|
|
10
10
|
|
|
11
11
|
// src/trace/execution-measurements.ts
|
|
12
12
|
var TOKEN_MEASUREMENT_KEY_GROUPS = [
|
|
@@ -283,4 +283,4 @@ export {
|
|
|
283
283
|
summarizeExecutionMeasurements,
|
|
284
284
|
recordAggregateMeasurements
|
|
285
285
|
};
|
|
286
|
-
//# sourceMappingURL=chunk-
|
|
286
|
+
//# sourceMappingURL=chunk-H5UD2323.js.map
|
|
@@ -61,6 +61,7 @@ var CostLedger = class {
|
|
|
61
61
|
records = /* @__PURE__ */ new Map();
|
|
62
62
|
activeCallIds = /* @__PURE__ */ new Set();
|
|
63
63
|
lateCallIds = /* @__PURE__ */ new Set();
|
|
64
|
+
idleWaiters = /* @__PURE__ */ new Set();
|
|
64
65
|
completedTasks = 0;
|
|
65
66
|
revision = "memory";
|
|
66
67
|
costLimitPersisted = false;
|
|
@@ -181,9 +182,27 @@ var CostLedger = class {
|
|
|
181
182
|
} catch (error) {
|
|
182
183
|
return { succeeded: false, callId: pending.callId, error: toError(error) };
|
|
183
184
|
} finally {
|
|
184
|
-
if (!this.lateCallIds.has(pending.callId)) this.
|
|
185
|
+
if (!this.lateCallIds.has(pending.callId)) this.releaseActiveCall(pending.callId);
|
|
185
186
|
}
|
|
186
187
|
}
|
|
188
|
+
/** Wait until every call started by this ledger has produced a durable outcome. */
|
|
189
|
+
async waitForIdle(options = {}) {
|
|
190
|
+
if (this.activeCallIds.size === 0) return true;
|
|
191
|
+
const timeoutMs = options.timeoutMs ?? 5e3;
|
|
192
|
+
assertTimeout(timeoutMs, "waitForIdle.timeoutMs");
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
let timer;
|
|
195
|
+
const finish = (settled) => {
|
|
196
|
+
this.idleWaiters.delete(onIdle);
|
|
197
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
198
|
+
resolve(settled);
|
|
199
|
+
};
|
|
200
|
+
const onIdle = () => finish(true);
|
|
201
|
+
this.idleWaiters.add(onIdle);
|
|
202
|
+
timer = setTimeout(() => finish(false), timeoutMs);
|
|
203
|
+
if (this.activeCallIds.size === 0) onIdle();
|
|
204
|
+
});
|
|
205
|
+
}
|
|
187
206
|
/** Settle a call left pending by a crashed process after reconciling with the provider. */
|
|
188
207
|
reconcile(callId, observed, options = {}) {
|
|
189
208
|
const pending = [...this.records.values()].find(
|
|
@@ -212,12 +231,16 @@ var CostLedger = class {
|
|
|
212
231
|
);
|
|
213
232
|
let inputTokens = 0;
|
|
214
233
|
let outputTokens = 0;
|
|
234
|
+
let reasoningTokens = 0;
|
|
215
235
|
let cachedTokens = 0;
|
|
236
|
+
let cacheWriteTokens = 0;
|
|
216
237
|
let totalCostUsd = 0;
|
|
217
238
|
for (const receipt of receipts) {
|
|
218
239
|
inputTokens += receipt.inputTokens;
|
|
219
240
|
outputTokens += receipt.outputTokens;
|
|
241
|
+
reasoningTokens += receipt.reasoningTokens ?? 0;
|
|
220
242
|
cachedTokens += receipt.cachedTokens ?? 0;
|
|
243
|
+
cacheWriteTokens += receipt.cacheWriteTokens ?? 0;
|
|
221
244
|
totalCostUsd += receipt.costUsd;
|
|
222
245
|
if (receipt.costUnknown) {
|
|
223
246
|
unpriced.add(receipt.model);
|
|
@@ -239,7 +262,9 @@ var CostLedger = class {
|
|
|
239
262
|
rollup.calls += 1;
|
|
240
263
|
rollup.inputTokens += receipt.inputTokens;
|
|
241
264
|
rollup.outputTokens += receipt.outputTokens;
|
|
265
|
+
rollup.reasoningTokens = (rollup.reasoningTokens ?? 0) + (receipt.reasoningTokens ?? 0);
|
|
242
266
|
rollup.cachedTokens += receipt.cachedTokens ?? 0;
|
|
267
|
+
rollup.cacheWriteTokens = (rollup.cacheWriteTokens ?? 0) + (receipt.cacheWriteTokens ?? 0);
|
|
243
268
|
rollup.costUsd += receipt.costUsd;
|
|
244
269
|
if (receipt.costUnknown) rollup.unpricedCalls += 1;
|
|
245
270
|
if (receipt.usageUnknown) rollup.unknownUsageCalls += 1;
|
|
@@ -254,12 +279,14 @@ var CostLedger = class {
|
|
|
254
279
|
reservedCostUsd: pending.reduce((sum, record) => sum + (record.maximumCostUsd ?? 0), 0),
|
|
255
280
|
inputTokens,
|
|
256
281
|
outputTokens,
|
|
282
|
+
reasoningTokens,
|
|
257
283
|
cachedTokens,
|
|
284
|
+
cacheWriteTokens,
|
|
258
285
|
totalCostUsd,
|
|
259
286
|
byChannel: [...byChannel.values()].sort((a, b) => a.channel.localeCompare(b.channel)),
|
|
260
287
|
unpricedModels: [...unpriced].sort(),
|
|
261
288
|
fullyPriced: unpriced.size === 0,
|
|
262
|
-
usageComplete: receipts.every((receipt) => !receipt.usageUnknown),
|
|
289
|
+
usageComplete: pending.length === 0 && receipts.every((receipt) => !receipt.usageUnknown),
|
|
263
290
|
accountingComplete: incompleteReasons.length === 0,
|
|
264
291
|
incompleteReasons: [...new Set(incompleteReasons)]
|
|
265
292
|
};
|
|
@@ -345,9 +372,15 @@ var CostLedger = class {
|
|
|
345
372
|
}
|
|
346
373
|
).finally(() => {
|
|
347
374
|
this.lateCallIds.delete(pending.callId);
|
|
348
|
-
this.
|
|
375
|
+
this.releaseActiveCall(pending.callId);
|
|
349
376
|
});
|
|
350
377
|
}
|
|
378
|
+
releaseActiveCall(callId) {
|
|
379
|
+
this.activeCallIds.delete(callId);
|
|
380
|
+
if (this.activeCallIds.size > 0) return;
|
|
381
|
+
for (const resolve of this.idleWaiters) resolve();
|
|
382
|
+
this.idleWaiters.clear();
|
|
383
|
+
}
|
|
351
384
|
commitOutcome(pending, error, observed) {
|
|
352
385
|
try {
|
|
353
386
|
const receipt = this.commitReceipt(pending, observed, error.message);
|
|
@@ -420,10 +453,7 @@ var CostLedger = class {
|
|
|
420
453
|
const callId = record.callId;
|
|
421
454
|
const receipt = record.status === "settled" ? record : void 0;
|
|
422
455
|
validateTransition(this.records, record);
|
|
423
|
-
const event = {
|
|
424
|
-
version: 1,
|
|
425
|
-
record: cloneRecord(record)
|
|
426
|
-
};
|
|
456
|
+
const event = record.status === "settled" && (record.reasoningTokens !== void 0 || record.cacheWriteTokens !== void 0) ? { version: 2, record: cloneReceipt(record) } : { version: 1, record: cloneRecord(record) };
|
|
427
457
|
this.appendEvent(event, callId, receipt);
|
|
428
458
|
this.records.set(callId, cloneRecord(record));
|
|
429
459
|
}
|
|
@@ -459,7 +489,11 @@ function costForUsage(model, usage) {
|
|
|
459
489
|
assertUsage(usage);
|
|
460
490
|
if (!resolveModelPricing(model)) return { costUsd: 0, costUnknown: true };
|
|
461
491
|
return {
|
|
462
|
-
costUsd: estimateCost(
|
|
492
|
+
costUsd: estimateCost(
|
|
493
|
+
usage.inputTokens + (usage.cachedTokens ?? 0) + (usage.cacheWriteTokens ?? 0),
|
|
494
|
+
usage.outputTokens,
|
|
495
|
+
model
|
|
496
|
+
),
|
|
463
497
|
costUnknown: false
|
|
464
498
|
};
|
|
465
499
|
}
|
|
@@ -505,7 +539,9 @@ function buildReceipt(pending, observed, error) {
|
|
|
505
539
|
model: observed.model,
|
|
506
540
|
inputTokens: observed.inputTokens,
|
|
507
541
|
outputTokens: observed.outputTokens,
|
|
542
|
+
...observed.reasoningTokens === void 0 ? {} : { reasoningTokens: observed.reasoningTokens },
|
|
508
543
|
...observed.cachedTokens === void 0 ? {} : { cachedTokens: observed.cachedTokens },
|
|
544
|
+
...observed.cacheWriteTokens === void 0 ? {} : { cacheWriteTokens: observed.cacheWriteTokens },
|
|
509
545
|
costUsd: costUnknown ? 0 : hasActual ? observed.actualCostUsd : estimated.costUsd,
|
|
510
546
|
costUnknown,
|
|
511
547
|
usageUnknown,
|
|
@@ -547,7 +583,7 @@ var PendingCostCallSchema = z.strictObject({
|
|
|
547
583
|
status: z.literal("pending"),
|
|
548
584
|
...CostCallBaseShape
|
|
549
585
|
});
|
|
550
|
-
var
|
|
586
|
+
var CostReceiptBaseShape = {
|
|
551
587
|
status: z.literal("settled"),
|
|
552
588
|
...CostCallBaseShape,
|
|
553
589
|
inputTokens: TokenCount,
|
|
@@ -559,7 +595,17 @@ var CostReceiptSchema = z.strictObject({
|
|
|
559
595
|
pricing: CostPricingSchema.optional(),
|
|
560
596
|
actualCostUsd: NonNegative.optional(),
|
|
561
597
|
error: z.string().optional()
|
|
562
|
-
}
|
|
598
|
+
};
|
|
599
|
+
var LegacyCostReceiptSchema = z.strictObject(CostReceiptBaseShape).superRefine((receipt, ctx) => validateCostReceipt(receipt, ctx));
|
|
600
|
+
var CostReceiptSchema = z.strictObject({
|
|
601
|
+
...CostReceiptBaseShape,
|
|
602
|
+
reasoningTokens: TokenCount.optional(),
|
|
603
|
+
cacheWriteTokens: TokenCount.optional()
|
|
604
|
+
}).superRefine((receipt, ctx) => validateCostReceipt(receipt, ctx));
|
|
605
|
+
function validateCostReceipt(receipt, ctx) {
|
|
606
|
+
if (receipt.reasoningTokens !== void 0 && receipt.reasoningTokens > receipt.outputTokens) {
|
|
607
|
+
ctx.addIssue({ code: "custom", message: "reasoningTokens must not exceed outputTokens" });
|
|
608
|
+
}
|
|
563
609
|
if (receipt.actualCostUsd !== void 0) {
|
|
564
610
|
if (receipt.costUnknown || receipt.costUsd !== receipt.actualCostUsd) {
|
|
565
611
|
ctx.addIssue({ code: "custom", message: "actual cost must be known and equal costUsd" });
|
|
@@ -592,11 +638,15 @@ var CostReceiptSchema = z.strictObject({
|
|
|
592
638
|
message: `estimated cost ${receipt.costUsd} does not match pricing snapshot ${expected}`
|
|
593
639
|
});
|
|
594
640
|
}
|
|
595
|
-
}
|
|
641
|
+
}
|
|
596
642
|
var CostLedgerEventSchema = z.union([
|
|
597
643
|
z.strictObject({
|
|
598
644
|
version: z.literal(1),
|
|
599
|
-
record: z.union([PendingCostCallSchema,
|
|
645
|
+
record: z.union([PendingCostCallSchema, LegacyCostReceiptSchema])
|
|
646
|
+
}),
|
|
647
|
+
z.strictObject({
|
|
648
|
+
version: z.literal(2),
|
|
649
|
+
record: CostReceiptSchema
|
|
600
650
|
}),
|
|
601
651
|
z.strictObject({
|
|
602
652
|
version: z.literal(1),
|
|
@@ -618,8 +668,9 @@ function parseEvents(serialized) {
|
|
|
618
668
|
lineNumber += 1;
|
|
619
669
|
const event = CostLedgerEventSchema.parse(JSON.parse(line));
|
|
620
670
|
if ("record" in event) {
|
|
621
|
-
|
|
622
|
-
records
|
|
671
|
+
const record = event.record;
|
|
672
|
+
validateTransition(records, record);
|
|
673
|
+
records.set(record.callId, cloneRecord(record));
|
|
623
674
|
} else if ("completedTasks" in event) {
|
|
624
675
|
completedTasks += event.completedTasks;
|
|
625
676
|
if (!Number.isSafeInteger(completedTasks)) {
|
|
@@ -706,7 +757,7 @@ function cloneReceipt(receipt) {
|
|
|
706
757
|
};
|
|
707
758
|
}
|
|
708
759
|
function costFromPricing(usage, pricing) {
|
|
709
|
-
return (usage.inputTokens + (usage.cachedTokens ?? 0)) / 1e3 * pricing.inputUsdPerThousand + usage.outputTokens / 1e3 * pricing.outputUsdPerThousand;
|
|
760
|
+
return (usage.inputTokens + (usage.cachedTokens ?? 0) + (usage.cacheWriteTokens ?? 0)) / 1e3 * pricing.inputUsdPerThousand + usage.outputTokens / 1e3 * pricing.outputUsdPerThousand;
|
|
710
761
|
}
|
|
711
762
|
function emptyRollup(channel) {
|
|
712
763
|
return {
|
|
@@ -714,7 +765,9 @@ function emptyRollup(channel) {
|
|
|
714
765
|
calls: 0,
|
|
715
766
|
inputTokens: 0,
|
|
716
767
|
outputTokens: 0,
|
|
768
|
+
reasoningTokens: 0,
|
|
717
769
|
cachedTokens: 0,
|
|
770
|
+
cacheWriteTokens: 0,
|
|
718
771
|
costUsd: 0,
|
|
719
772
|
unpricedCalls: 0,
|
|
720
773
|
unknownUsageCalls: 0
|
|
@@ -762,7 +815,16 @@ function validationMessage(cause) {
|
|
|
762
815
|
function assertUsage(usage) {
|
|
763
816
|
assertTokenCount(usage.inputTokens, "inputTokens");
|
|
764
817
|
assertTokenCount(usage.outputTokens, "outputTokens");
|
|
818
|
+
if (usage.reasoningTokens !== void 0) {
|
|
819
|
+
assertTokenCount(usage.reasoningTokens, "reasoningTokens");
|
|
820
|
+
if (usage.reasoningTokens > usage.outputTokens) {
|
|
821
|
+
throw new ValidationError("CostLedger: reasoningTokens must not exceed outputTokens");
|
|
822
|
+
}
|
|
823
|
+
}
|
|
765
824
|
if (usage.cachedTokens !== void 0) assertTokenCount(usage.cachedTokens, "cachedTokens");
|
|
825
|
+
if (usage.cacheWriteTokens !== void 0) {
|
|
826
|
+
assertTokenCount(usage.cacheWriteTokens, "cacheWriteTokens");
|
|
827
|
+
}
|
|
766
828
|
}
|
|
767
829
|
function assertTokenCount(value, name) {
|
|
768
830
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
|
|
@@ -778,6 +840,13 @@ function assertNonNegative(value, name) {
|
|
|
778
840
|
);
|
|
779
841
|
}
|
|
780
842
|
}
|
|
843
|
+
function assertTimeout(value, name) {
|
|
844
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0 || value > 2147483647) {
|
|
845
|
+
throw new ValidationError(
|
|
846
|
+
`CostLedger: ${name} must be a non-negative safe integer no greater than 2147483647, got ${String(value)}`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
781
850
|
function assertString(value, name) {
|
|
782
851
|
if (typeof value !== "string" || value.trim().length === 0) {
|
|
783
852
|
throw new ValidationError(`CostLedger: ${name} must be a non-empty string`);
|
|
@@ -795,4 +864,4 @@ export {
|
|
|
795
864
|
modelPriceKey,
|
|
796
865
|
costForUsage
|
|
797
866
|
};
|
|
798
|
-
//# sourceMappingURL=chunk-
|
|
867
|
+
//# sourceMappingURL=chunk-JHOJHHU7.js.map
|