@tangle-network/agent-eval 0.118.3 → 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.
Files changed (50) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +26 -0
  3. package/dist/analyst/index.d.ts +143 -21
  4. package/dist/analyst/index.js +44 -10
  5. package/dist/analyst/index.js.map +1 -1
  6. package/dist/benchmarks/index.d.ts +50 -40
  7. package/dist/benchmarks/index.js +6 -6
  8. package/dist/campaign/index.d.ts +231 -156
  9. package/dist/campaign/index.js +5 -5
  10. package/dist/{chunk-JTMFT5QZ.js → chunk-4I2E3LLO.js} +2 -2
  11. package/dist/{chunk-VNLWDTDA.js → chunk-7A4LIMMY.js} +845 -252
  12. package/dist/chunk-7A4LIMMY.js.map +1 -0
  13. package/dist/{chunk-KK2O4VWA.js → chunk-D7AEXSM5.js} +2 -2
  14. package/dist/{chunk-EKHHBKS6.js → chunk-F6YUH3L4.js} +39 -28
  15. package/dist/{chunk-EKHHBKS6.js.map → chunk-F6YUH3L4.js.map} +1 -1
  16. package/dist/{chunk-267UCQWI.js → chunk-GERDAIAL.js} +2 -2
  17. package/dist/{chunk-267UCQWI.js.map → chunk-GERDAIAL.js.map} +1 -1
  18. package/dist/{chunk-MLHRFWQK.js → chunk-JHOJHHU7.js} +85 -16
  19. package/dist/chunk-JHOJHHU7.js.map +1 -0
  20. package/dist/{chunk-F5A4GDQW.js → chunk-LMJ2TGWJ.js} +3 -15
  21. package/dist/chunk-LMJ2TGWJ.js.map +1 -0
  22. package/dist/{chunk-EGNRE7VA.js → chunk-PAHNGS65.js} +2 -2
  23. package/dist/{chunk-QWRLW4CT.js → chunk-RLCJQ6VZ.js} +5 -5
  24. package/dist/{chunk-7V3QDWNL.js → chunk-XJ7JVCHB.js} +2 -2
  25. package/dist/cli.js +2 -2
  26. package/dist/contract/index.d.ts +257 -160
  27. package/dist/contract/index.js +6 -6
  28. package/dist/fuzz.d.ts +19 -1
  29. package/dist/fuzz.js +1 -1
  30. package/dist/index.d.ts +154 -48
  31. package/dist/index.js +12 -10
  32. package/dist/index.js.map +1 -1
  33. package/dist/openapi.json +1 -1
  34. package/dist/primeintellect/index.d.ts +311 -0
  35. package/dist/primeintellect/index.js +348 -0
  36. package/dist/primeintellect/index.js.map +1 -0
  37. package/dist/rl.d.ts +4 -0
  38. package/dist/{run-campaign-DWC67KJP.js → run-campaign-OBXSN5WK.js} +3 -3
  39. package/dist/wire/index.d.ts +19 -1
  40. package/dist/wire/index.js +2 -2
  41. package/package.json +6 -1
  42. package/dist/chunk-F5A4GDQW.js.map +0 -1
  43. package/dist/chunk-MLHRFWQK.js.map +0 -1
  44. package/dist/chunk-VNLWDTDA.js.map +0 -1
  45. /package/dist/{chunk-JTMFT5QZ.js.map → chunk-4I2E3LLO.js.map} +0 -0
  46. /package/dist/{chunk-KK2O4VWA.js.map → chunk-D7AEXSM5.js.map} +0 -0
  47. /package/dist/{chunk-EGNRE7VA.js.map → chunk-PAHNGS65.js.map} +0 -0
  48. /package/dist/{chunk-QWRLW4CT.js.map → chunk-RLCJQ6VZ.js.map} +0 -0
  49. /package/dist/{chunk-7V3QDWNL.js.map → chunk-XJ7JVCHB.js.map} +0 -0
  50. /package/dist/{run-campaign-DWC67KJP.js.map → run-campaign-OBXSN5WK.js.map} +0 -0
@@ -6,7 +6,7 @@ import {
6
6
  DEFAULT_TRACE_ANALYST_KINDS,
7
7
  createTraceAnalystKind,
8
8
  makeFinding
9
- } from "./chunk-VNLWDTDA.js";
9
+ } from "./chunk-7A4LIMMY.js";
10
10
  import {
11
11
  LlmClient
12
12
  } from "./chunk-NJC7U437.js";
@@ -547,4 +547,4 @@ export {
547
547
  behavioralAnalyst,
548
548
  buildDefaultAnalystRegistry
549
549
  };
550
- //# sourceMappingURL=chunk-267UCQWI.js.map
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":[]}
@@ -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.activeCallIds.delete(pending.callId);
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.activeCallIds.delete(pending.callId);
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(usage.inputTokens + (usage.cachedTokens ?? 0), usage.outputTokens, model),
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 CostReceiptSchema = z.strictObject({
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
- }).superRefine((receipt, ctx) => {
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, CostReceiptSchema])
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
- validateTransition(records, event.record);
622
- records.set(event.record.callId, cloneRecord(event.record));
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-MLHRFWQK.js.map
867
+ //# sourceMappingURL=chunk-JHOJHHU7.js.map