/** * Routing telemetry emitter — T039. * * Maintains an append-only rolling window of routing decisions for * observability and audit. Window bounds: 168 hours (7 days), max 1111 entries. */ import { CONTEXT_FIT_EXCEEDED, CONTEXT_OVERFLOW_FRONTIER_FALLBACK, CONTEXT_OVERFLOW_NO_FIT, CONTEXT_OVERFLOW_SAME_PROVIDER_FALLBACK, modelFitsContext, resolveSafetyMargin, type ContextFitConfig, } from '../../domain/routing/context-fit.js'; import { CLUSTER_REASON_CODE_PREFIX } from '../../config/routing-clusters-loader.js'; import type { ClusterMatcher } from '../../domain/matching/cluster-matcher.js'; import { evaluateModelSwitchBreakeven } from '../../domain/pinning/session-pinner.js'; import { FLIP_FLOP_SHADOW_TIER_FLIP, FLIP_FLOP_SHADOW_TIER_PINNED, } from '../../domain/pinning/flip-flop-guard.js'; import { selectLowestCostModel } from '../../domain/pinning/sub-route-policy.js'; import type { SessionPinner } from '../../domain/pinning/session-pinner.js'; import type { BreakevenObservability, ClusterMatchTableEntry, ContextFitObservability, ContextFitRejectedEntry, LowIntensityBreakdown, ModelProfile, PlanningDelegateObservability, PlanningDelegatePath, PriceCatalog, RejectedTierEntry, RoutePath, RoutingDecision, RoutingRequest, RoutingTelemetry, SaarConfig, SaarObservability, Tier, TierFeatureSummary, TierSelectionObservability, } from '../../domain/types/index.js'; import type { QuotaWindowPosition } from '../../domain/types/entities.js'; import type { VirtualCostV2Config } from '../../domain/types/schemas.js'; import { resolveFrugalityCostPer1M } from '../pricing/price-broker.js'; import { TELEMETRY_MAX_ENTRIES, TELEMETRY_WINDOW_MS, evictExpiredTelemetryEntries, makeTelemetryRoom, } from './telemetry-limits.js'; export const CONTEXT_FIT_PASS = 'context_fit_pass' as const; export const CONTEXT_FIT_REJECTED_ALL = 'context_fit_rejected_all' as const; export const CONTEXT_OVERFLOW_PIN_BREAK = 'context_overflow_pin_break' as const; export const LOW_INTENSITY_STRUCTURAL = 'low_intensity_structural' as const; export const HIGH_INTENSITY_STRUCTURAL = 'high_intensity_structural' as const; export const P_SUCCESS_CHEAP = 'p_success_cheap' as const; export const P_SUCCESS_UNCERTAIN = 'p_success_uncertain' as const; /** Cache breakeven gate observability with virtual cost v2 scalars (SP-149). */ export interface BreakevenObservabilityV2 extends BreakevenObservability { readonly quota_premium_usd: number | null; readonly kv_cache_credit_usd: number | null; } export const BREAKEVEN_BLOCKED = 'breakeven_blocked' as const; export const BREAKEVEN_PASS = 'breakeven_pass' as const; export const SAAR_BUFFER_ACTIVE = 'saar_buffer_active' as const; export const SAAR_HARD_LOCK = 'saar_hard_lock' as const; export const FLIP_FLOP_TIER_FLIP = FLIP_FLOP_SHADOW_TIER_FLIP; export const FLIP_FLOP_TIER_PINNED = FLIP_FLOP_SHADOW_TIER_PINNED; export const PLANNING_DELEGATE = 'planning_delegate' as const; export const PLANNING_DIRECT_FRONTIER = 'planning_direct_frontier' as const; export const PLANNING_DELEGATE_DISABLED = 'planning_delegate_disabled' as const; export const PLANNING_DELEGATE_UNAVAILABLE = 'planning_delegate_unavailable' as const; /** Reason recorded when a delegate sub-call exceeds its timeout budget (SP-213, #120). */ export const PLANNING_DELEGATE_TIMEOUT = 'planning_delegate_timeout' as const; export const THROUGHPUT_BELOW_THRESHOLD = 'throughput_below_threshold' as const; /** Pre-local_zero tool-use capability shortfall (SP-177, #98). */ export const TOOL_USE_CAPABILITY_SHORTFALL = 'tool_use_capability_shortfall' as const; /** Operator disabled local_zero stage (SP-177, #98). */ export const LOCAL_ZERO_DISABLED = 'local_zero_disabled' as const; /** Emergency pin-on-first-turn fallback reason code (#83, SP-161/162). */ export const PIN_ONLY_FALLBACK = 'pin_only_fallback' as const; const TURN_ENVELOPE_TIER_MAP: Readonly> = { planning: 'frontier-cloud', tool_result: 'economical-cloud', subagent: 'economical-cloud', main_loop: null, unknown: null, }; const SAAR_DECISION_REASON_CODES = new Set([ SAAR_BUFFER_ACTIVE, SAAR_HARD_LOCK, 'saar_tier_upgrade', 'saar_idle_reopen', ]); const EXPECTED_COST_PREFIX = 'expected_cost_'; const EXPECTED_COST_DEFER_CODES = new Set([ 'expected_cost_price_delta_insufficient', 'expected_cost_no_viable_tier', ]); const OVERFLOW_REASON_CODES = new Set([ CONTEXT_OVERFLOW_SAME_PROVIDER_FALLBACK, CONTEXT_OVERFLOW_FRONTIER_FALLBACK, CONTEXT_OVERFLOW_NO_FIT, ]); /** * Estimate per-request routing cost in USD from resolved model pricing (SP-085). * Uses estimated_input_tokens when present, otherwise prompt_text length as a token proxy. */ export function estimateRoutingCost( model: ModelProfile, request: RoutingRequest, catalog: PriceCatalog | null, ): number { const tokens = request.estimated_input_tokens ?? request.prompt_text.length; const costPer1M = resolveFrugalityCostPer1M(model, catalog); return (tokens / 1_000_000) * costPer1M; } export { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, TELEMETRY_MAX_ENTRIES, TELEMETRY_WINDOW_HOURS, TELEMETRY_WINDOW_MS, } from './telemetry-limits.js'; export interface TelemetryEmitterOptions { readonly maxEntries?: number; readonly windowMs?: number; readonly clock?: () => string; readonly onRecord?: (record: RoutingTelemetry) => void; readonly fleet?: readonly ModelProfile[]; readonly contextFitConfig?: ContextFitConfig; readonly sessionPinner?: SessionPinner; readonly saarConfig?: SaarConfig; readonly priceCatalog?: PriceCatalog | null; readonly quotaWindowPosition?: QuotaWindowPosition; readonly virtualCostV2Config?: VirtualCostV2Config; } export interface ContextFitObservabilityInput { readonly request: RoutingRequest; readonly decision: RoutingDecision; readonly fleet?: readonly ModelProfile[] | undefined; readonly contextFitConfig?: ContextFitConfig | undefined; } function resolveEstimatedInputTokens(request: RoutingRequest): number { return request.estimated_input_tokens ?? request.prompt_text.length; } function extractContextFitRejected(decision: RoutingDecision) { const candidates = decision.features?.candidates ?? decision.candidates ?? []; return candidates.filter( (candidate) => candidate.rejected_reason === CONTEXT_FIT_EXCEEDED, ); } function lookupMaxInputTokens( fleet: readonly ModelProfile[] | undefined, modelId: string, ): number | null { const profile = fleet?.find((model) => model.id === modelId); return profile?.limits?.max_input_tokens ?? null; } function serializeContextFitRejected( rejected: readonly { model_id: string; rejected_reason: string | null }[], fleet: readonly ModelProfile[] | undefined, ): string | null { if (rejected.length === 0) { return null; } const entries: ContextFitRejectedEntry[] = rejected.map((candidate) => ({ model_id: candidate.model_id, max_input_tokens: lookupMaxInputTokens(fleet, candidate.model_id), reason: candidate.rejected_reason ?? CONTEXT_FIT_EXCEEDED, })); return JSON.stringify(entries); } function countViableModels( fleet: readonly ModelProfile[], estimatedInputTokens: number, safetyMargin: number, ): number { let count = 0; for (const model of fleet) { if (modelFitsContext(model, estimatedInputTokens, safetyMargin)) { count += 1; } } return count; } function resolveContextOverflowPinBreak(decision: RoutingDecision): boolean { if (decision.pin_reason === 'context_overflow') { return true; } return OVERFLOW_REASON_CODES.has(decision.reason_code); } function resolveContextFitReasonCode( decision: RoutingDecision, rejectedCount: number, viableCount: number | null, gateRan: boolean, ): string | null { if (!gateRan) { return null; } if (decision.pin_reason === 'context_overflow') { return CONTEXT_OVERFLOW_PIN_BREAK; } if (OVERFLOW_REASON_CODES.has(decision.reason_code)) { return decision.reason_code; } if (decision.reason_code === CONTEXT_OVERFLOW_PIN_BREAK) { return CONTEXT_OVERFLOW_PIN_BREAK; } if ( viableCount === 0 || decision.reason_code === CONTEXT_OVERFLOW_NO_FIT || (rejectedCount > 0 && decision.selected_model_id === 'unknown') ) { return CONTEXT_FIT_REJECTED_ALL; } if (rejectedCount > 0 || viableCount !== null) { return CONTEXT_FIT_PASS; } return CONTEXT_FIT_PASS; } function gateSkipped(request: RoutingRequest): boolean { return request.force_model_id !== undefined; } function gateRan(request: RoutingRequest, decision: RoutingDecision): boolean { if (gateSkipped(request)) { return false; } const rejected = extractContextFitRejected(decision); if (rejected.length > 0) { return true; } if (request.estimated_input_tokens !== undefined) { return true; } if (OVERFLOW_REASON_CODES.has(decision.reason_code)) { return true; } if (decision.pin_reason === 'context_overflow') { return true; } return decision.features?.context_fit !== undefined; } /** Build privacy-safe context-fit observability from a routing decision (SP-110). */ export function buildContextFitObservability( input: ContextFitObservabilityInput, ): ContextFitObservability | null { const { request, decision, fleet, contextFitConfig } = input; if (decision.features?.context_fit) { return decision.features.context_fit; } if (gateSkipped(request)) { return null; } const ran = gateRan(request, decision); if (!ran) { return null; } const rejected = extractContextFitRejected(decision); const estimatedInputTokens = resolveEstimatedInputTokens(request); const safetyMargin = resolveSafetyMargin(contextFitConfig); const viableCount = fleet !== undefined ? countViableModels(fleet, estimatedInputTokens, safetyMargin) : null; return { estimated_input_tokens: estimatedInputTokens, context_fit_viable_count: viableCount, context_fit_rejected_json: serializeContextFitRejected(rejected, fleet), context_overflow_pin_break: resolveContextOverflowPinBreak(decision), selected_model_max_input_tokens: lookupMaxInputTokens( fleet, decision.selected_model_id, ), context_fit_reason_code: resolveContextFitReasonCode( decision, rejected.length, viableCount, ran, ), }; } function parseClusterIdFromReasonCode(reasonCode: string | null | undefined): string | null { if (reasonCode === null || reasonCode === undefined || !reasonCode.startsWith(CLUSTER_REASON_CODE_PREFIX)) { return null; } return reasonCode.slice(CLUSTER_REASON_CODE_PREFIX.length); } /** Normalize tier-selection reason codes for telemetry and explain (SP-113). */ export function resolveTierSelectionReasonCode( features: RoutingDecision['features'], ): string | null { if (!features) { return null; } const reasonCode = features.tier_hint_reason_code; if (reasonCode === null || reasonCode === undefined) { if (features.p_success_cheap !== null && features.tier_hint === null) { return P_SUCCESS_UNCERTAIN; } return null; } if ( reasonCode.startsWith(CLUSTER_REASON_CODE_PREFIX) || reasonCode === LOW_INTENSITY_STRUCTURAL || reasonCode === HIGH_INTENSITY_STRUCTURAL ) { return reasonCode; } if (reasonCode.startsWith(EXPECTED_COST_PREFIX)) { if (EXPECTED_COST_DEFER_CODES.has(reasonCode) || features.tier_hint === null) { return P_SUCCESS_UNCERTAIN; } if (features.tier_hint === 'economical-cloud' || features.tier_hint === 'zero-tier') { return P_SUCCESS_CHEAP; } } return reasonCode; } function extractRejectedTiers( candidates: NonNullable['candidates'], ): readonly RejectedTierEntry[] { if (!candidates || candidates.length === 0) { return []; } const rejected: RejectedTierEntry[] = []; for (const candidate of candidates) { if (!candidate.model_id.startsWith('__expected_cost_')) { continue; } const tier = candidate.model_id .replace('__expected_cost_', '') .replace(/__$/, ''); rejected.push({ tier, expected_cost_usd: candidate.score, adjusted_expected_cost_usd: candidate.shortfall, reason: candidate.rejected_reason ?? '', }); } return rejected; } function buildTierFeatureSummary( features: NonNullable, ): TierFeatureSummary { return { triage_verdict: features.triage?.verdict ?? null, triage_reason_code: features.triage?.reason_code ?? null, cyclomatic_score: features.triage?.cyclomatic_score ?? null, requirement_reasoning: features.requirements?.reasoning ?? null, requirement_code_gen: features.requirements?.code_gen ?? null, requirement_tool_use: features.requirements?.tool_use ?? null, }; } function buildLowIntensityBreakdown( features: NonNullable, ): LowIntensityBreakdown { return { score: features.low_intensity_score, tier_hint: features.tier_hint, tier_hint_reason_code: features.tier_hint_reason_code, tier_selection_reason_code: resolveTierSelectionReasonCode(features), p_success_cheap: features.p_success_cheap, p_success_raw: features.p_success_raw, p_success_calibrated: features.p_success_calibrated, p_success_alpha: features.p_success_alpha, rejected_tiers: extractRejectedTiers(features.candidates), }; } /** Infer why local_zero did not dispatch when another stage won (SP-113). */ export function buildLocalZeroSkipReasons( decision: RoutingDecision, features: RoutingDecision['features'], ): readonly string[] { if (decision.stage === 'local_zero') { if (decision.reason_code === THROUGHPUT_BELOW_THRESHOLD) { return [THROUGHPUT_BELOW_THRESHOLD]; } return []; } const reasons: string[] = []; if (!features?.local_eligible_reason) { reasons.push('not_locally_eligible'); } const rejectedJson = features?.context_fit?.context_fit_rejected_json; if (rejectedJson !== null && rejectedJson !== undefined && rejectedJson.includes('zero-tier')) { reasons.push('context_fit_excluded_local'); } if (features?.local_eligible_reason) { if (decision.pin_reason !== null) { reasons.push('session_pin_active'); } else { reasons.push('hardware_or_local_unavailable'); } } return reasons; } function resolveClusterScalars( features: RoutingDecision['features'], clusterMatchTable: readonly ClusterMatchTableEntry[] | null, ): Pick< TierSelectionObservability, 'cluster_id' | 'cluster_similarity' | 'cluster_margin' > { const selected = clusterMatchTable?.find((entry) => entry.selected) ?? clusterMatchTable?.[0] ?? null; if (selected) { return { cluster_id: selected.cluster_id, cluster_similarity: selected.similarity, cluster_margin: selected.margin, }; } const clusterId = parseClusterIdFromReasonCode(features?.tier_hint_reason_code) ?? parseClusterIdFromReasonCode(features?.local_eligible_reason); return { cluster_id: clusterId, cluster_similarity: null, cluster_margin: null, }; } export interface TierSelectionObservabilityInput { readonly decision: RoutingDecision; readonly clusterMatchTable?: readonly ClusterMatchTableEntry[] | null; } /** Build privacy-safe tier/cluster observability from routing decision features (SP-113). */ export function buildTierSelectionObservability( input: TierSelectionObservabilityInput, ): TierSelectionObservability | null { const { decision, clusterMatchTable = null } = input; const features = decision.features; if (!features) { return null; } const tierGateRan = features.low_intensity_score !== null || features.tier_hint !== null || features.tier_hint_reason_code !== null || features.p_success_cheap !== null; if (!tierGateRan) { return null; } const clusterScalars = resolveClusterScalars(features, clusterMatchTable); return { ...clusterScalars, low_intensity_score: features.low_intensity_score, tier_hint: features.tier_hint, p_success_cheap: features.p_success_cheap, local_eligible_reason: features.local_eligible_reason, tier_selection_reason_code: resolveTierSelectionReasonCode(features), cluster_match_table: clusterMatchTable, tier_feature_summary: buildTierFeatureSummary(features), low_intensity_breakdown: buildLowIntensityBreakdown(features), local_zero_skip_reasons: buildLocalZeroSkipReasons(decision, features), }; } export interface PinEconomicsObservabilityInput { readonly request: RoutingRequest; readonly decision: RoutingDecision; readonly fleet?: readonly ModelProfile[] | undefined; readonly sessionPinner?: SessionPinner | undefined; readonly saarConfig?: SaarConfig | undefined; readonly priceCatalog?: PriceCatalog | null; readonly quotaWindowPosition?: QuotaWindowPosition; readonly virtualCostV2Config?: VirtualCostV2Config; } function resolveTurnEnvelopeTargetTier(request: RoutingRequest): Tier | null { const turnType = request.turn_type ?? 'unknown'; return TURN_ENVELOPE_TIER_MAP[turnType] ?? null; } function isSaarPlanningBufferActive( request: RoutingRequest, sessionPinner: SessionPinner | undefined, saarConfig: SaarConfig | undefined, ): boolean { if (!saarConfig || !sessionPinner || request.turn_type !== 'planning') { return false; } if (!sessionPinner.getPin(request.session_id)) { return false; } const saarState = sessionPinner.getSaarState(request.session_id); const turnIndex = saarState?.turn_index ?? 0; return turnIndex < saarConfig.planning_turn_buffer; } function resolveSaarReasonCode(decision: RoutingDecision): string | null { if (SAAR_DECISION_REASON_CODES.has(decision.reason_code)) { return decision.reason_code; } return null; } /** Build planning delegate observability from routing decision features (SP-142). */ export function buildPlanningDelegateObservability( decision: RoutingDecision, ): PlanningDelegateObservability | null { return decision.features?.planning_delegate ?? null; } /** Construct planning delegate observability for pipeline and tests (SP-142). */ export function createPlanningDelegateObservability(input: { path: PlanningDelegatePath; primary_model_id?: string | null; delegate_model_id?: string | null; compressed_context?: PlanningDelegateObservability['compressed_context']; planning_delegate_reason_code: string; fallback_reason?: string | null; /** Worker telemetry analogs (SP-213, #120); null when not yet executed. */ workers_spawned?: number | null; workers_succeeded?: number | null; worker_timeout_count?: number | null; }): PlanningDelegateObservability { return { path: input.path, primary_model_id: input.primary_model_id ?? null, delegate_model_id: input.delegate_model_id ?? null, compressed_context: input.compressed_context ?? null, planning_delegate_reason_code: input.planning_delegate_reason_code, fallback_reason: input.fallback_reason ?? null, workers_spawned: input.workers_spawned ?? null, workers_succeeded: input.workers_succeeded ?? null, worker_timeout_count: input.worker_timeout_count ?? null, }; } /** Attach planning delegate observability to routing decision features (SP-142). */ export function enrichRoutingDecisionWithPlanningDelegate( decision: RoutingDecision, planningDelegate?: PlanningDelegateObservability | null, ): RoutingDecision { const observability = planningDelegate ?? buildPlanningDelegateObservability(decision); if (!observability) { return decision; } return { ...decision, features: { ...(decision.features ?? emptyFeatureSidecar()), planning_delegate: observability, }, }; } /** Flip-flop shadow log observability (SP-155, #82). */ export interface FlipFlopObservability { readonly consecutive_tier_flips: number; readonly tier_pinned: Tier | null; readonly shadow_event: string | null; } /** Build privacy-safe SAAR pin state for explain and telemetry (SP-126). */ export function buildSaarObservability( input: PinEconomicsObservabilityInput, ): SaarObservability | null { const { request, decision, sessionPinner, saarConfig } = input; if (!saarConfig) { return null; } const saarState = sessionPinner?.getSaarState(request.session_id) ?? null; const pin = sessionPinner?.getPin(request.session_id) ?? null; if (!pin && !saarState && !resolveSaarReasonCode(decision)) { return null; } const turnIndex = saarState?.turn_index ?? (pin ? 0 : null); return { buffer_active: turnIndex !== null ? turnIndex < saarConfig.planning_turn_buffer : false, hard_lock: saarState?.hard_lock ?? false, turn_index_in_session: turnIndex, planning_turn_buffer: saarConfig.planning_turn_buffer, idle_timeout_seconds: saarConfig.idle_timeout_seconds, saar_reason_code: resolveSaarReasonCode(decision), }; } /** Build flip-flop shadow log observability from session pinner state (SP-155). */ export function buildFlipFlopObservability( input: PinEconomicsObservabilityInput, ): FlipFlopObservability | null { const { request, sessionPinner } = input; if (!sessionPinner) { return null; } const observation = sessionPinner.getLastFlipFlopObservation(); const state = sessionPinner.getFlipFlopState(request.session_id); if (!observation && !state) { return null; } return { consecutive_tier_flips: observation?.consecutive_tier_flips ?? state?.consecutive_tier_flips ?? 0, tier_pinned: observation?.tier_pinned ?? state?.tier_pinned ?? null, shadow_event: observation?.shadow_event ?? null, }; } /** Build cache breakeven breakdown when a pin would switch tiers (SP-126, SP-149). */ export function buildBreakevenObservability( input: PinEconomicsObservabilityInput, ): BreakevenObservabilityV2 | null { const { request, sessionPinner, saarConfig, fleet, priceCatalog = null, quotaWindowPosition, virtualCostV2Config, } = input; if (!fleet || !sessionPinner) { return null; } const pin = sessionPinner.getPin(request.session_id); if (!pin) { return null; } const targetTier = resolveTurnEnvelopeTargetTier(request); if (!targetTier) { return null; } if (isSaarPlanningBufferActive(request, sessionPinner, saarConfig)) { return null; } const pinnedModel = fleet.find( (model) => model.id === pin.pinned_model_id && model.healthy !== false, ); const candidate = selectLowestCostModel( fleet.filter((model) => model.tier === targetTier && model.healthy !== false), ); if (!pinnedModel || !candidate || pinnedModel.id === candidate.id) { return null; } const tokenEstimate = request.estimated_input_tokens ?? request.prompt_text.length; const breakevenContext = quotaWindowPosition !== undefined || virtualCostV2Config !== undefined ? { priceCatalog, ...(quotaWindowPosition !== undefined ? { quotaWindowPosition } : {}), ...(virtualCostV2Config !== undefined ? { virtualCostV2Config } : {}), } : undefined; const breakeven = evaluateModelSwitchBreakeven( pinnedModel, candidate, tokenEstimate, tokenEstimate, saarConfig, breakevenContext, ); return { marginal_savings: breakeven.marginal_savings, future_cache_value: breakeven.future_cache_value, cache_reprime_cost: breakeven.cache_reprime_cost, decision: breakeven.shouldSwitch ? 'pass' : 'blocked', breakeven_reason_code: breakeven.shouldSwitch ? BREAKEVEN_PASS : BREAKEVEN_BLOCKED, quota_premium_usd: breakeven.quota_premium_usd, kv_cache_credit_usd: breakeven.kv_cache_credit_usd, }; } function defaultBreakevenTelemetry(): Pick< RoutingTelemetry, | 'marginal_savings' | 'future_cache_value' | 'cache_reprime_cost' | 'breakeven_decision' | 'breakeven_reason_code' > { return { marginal_savings: null, future_cache_value: null, cache_reprime_cost: null, breakeven_decision: null, breakeven_reason_code: null, }; } function defaultSaarTelemetry(): Pick< RoutingTelemetry, | 'saar_buffer_active' | 'saar_hard_lock' | 'turn_index_in_session' | 'saar_reason_code' > { return { saar_buffer_active: false, saar_hard_lock: false, turn_index_in_session: null, saar_reason_code: null, }; } type FlipFlopTelemetryFields = { readonly flip_flop_consecutive_tier_flips: number | null; readonly flip_flop_tier_pinned: Tier | null; readonly flip_flop_shadow_event: string | null; }; function defaultFlipFlopTelemetry(): FlipFlopTelemetryFields { return { flip_flop_consecutive_tier_flips: null, flip_flop_tier_pinned: null, flip_flop_shadow_event: null, }; } /** Default breakeven telemetry scalars for tests and legacy store reads. */ export const DEFAULT_BREAKEVEN_TELEMETRY_FIELDS = defaultBreakevenTelemetry(); /** Default SAAR telemetry scalars for tests and legacy store reads. */ export const DEFAULT_SAAR_TELEMETRY_FIELDS = defaultSaarTelemetry(); function defaultPlanningDelegateTelemetry(): Pick< RoutingTelemetry, | 'planning_delegate_path' | 'planning_delegate_primary_model_id' | 'planning_delegate_model_id' | 'planning_delegate_reason_code' | 'planning_delegate_fallback_reason' | 'planning_delegate_max_messages' | 'planning_delegate_max_tokens' | 'planning_delegate_exclude_execution_history' | 'planning_delegate_workers_spawned' | 'planning_delegate_workers_succeeded' | 'planning_delegate_worker_timeout_count' > { return { planning_delegate_path: null, planning_delegate_primary_model_id: null, planning_delegate_model_id: null, planning_delegate_reason_code: null, planning_delegate_fallback_reason: null, planning_delegate_max_messages: null, planning_delegate_max_tokens: null, planning_delegate_exclude_execution_history: null, planning_delegate_workers_spawned: null, planning_delegate_workers_succeeded: null, planning_delegate_worker_timeout_count: null, }; } /** Default planning delegate telemetry scalars for tests and legacy store reads. */ export const DEFAULT_PLANNING_DELEGATE_TELEMETRY_FIELDS = defaultPlanningDelegateTelemetry(); function defaultPinOnlyFallbackTelemetry(): Pick { return { pin_only_fallback_active: false, }; } /** Default pin-only fallback telemetry scalars for tests and legacy store reads. */ export const DEFAULT_PIN_ONLY_FALLBACK_TELEMETRY_FIELDS = defaultPinOnlyFallbackTelemetry(); function defaultPrewarmTelemetry(): Pick< RoutingTelemetry, 'prewarm_attempted' | 'prewarm_accepted' | 'prewarm_disabled_reason' > { return { prewarm_attempted: false, prewarm_accepted: null, prewarm_disabled_reason: null, }; } /** Default speculative prewarm telemetry scalars for tests and legacy store reads (SP-217). */ export const DEFAULT_PREWARM_TELEMETRY_FIELDS = defaultPrewarmTelemetry(); /** Prewarm explain/telemetry fields from the decision feature sidecar (SP-217, #117). */ export function prewarmTelemetryFromDecision( decision: RoutingDecision, ): ReturnType { const features = decision.features; return { prewarm_attempted: features?.prewarm_attempted ?? false, prewarm_accepted: features?.prewarm_accepted ?? null, prewarm_disabled_reason: features?.prewarm_disabled_reason ?? null, }; } /** True when routing used emergency pin-only fallback for this decision (SP-162). */ export function resolvePinOnlyFallbackActive(decision: RoutingDecision): boolean { return decision.reason_code === PIN_ONLY_FALLBACK; } function pinOnlyFallbackTelemetryFromDecision( decision: RoutingDecision, ): ReturnType { return { pin_only_fallback_active: resolvePinOnlyFallbackActive(decision), }; } function planningDelegateTelemetryFromDecision( decision: RoutingDecision, ): ReturnType { const observability = buildPlanningDelegateObservability(decision); if (!observability) { return defaultPlanningDelegateTelemetry(); } return { planning_delegate_path: observability.path === 'none' ? null : observability.path, planning_delegate_primary_model_id: observability.primary_model_id, planning_delegate_model_id: observability.delegate_model_id, planning_delegate_reason_code: observability.planning_delegate_reason_code, planning_delegate_fallback_reason: observability.fallback_reason, planning_delegate_max_messages: observability.compressed_context?.max_messages ?? null, planning_delegate_max_tokens: observability.compressed_context?.max_tokens ?? null, planning_delegate_exclude_execution_history: observability.compressed_context?.exclude_execution_history ?? null, planning_delegate_workers_spawned: observability.workers_spawned, planning_delegate_workers_succeeded: observability.workers_succeeded, planning_delegate_worker_timeout_count: observability.worker_timeout_count, }; } function pinEconomicsTelemetryFromInput( input: PinEconomicsObservabilityInput, ): ReturnType & ReturnType & FlipFlopTelemetryFields { const breakeven = buildBreakevenObservability(input); const saar = buildSaarObservability(input); const flipFlop = buildFlipFlopObservability(input); return { ...(breakeven ? { marginal_savings: breakeven.marginal_savings, future_cache_value: breakeven.future_cache_value, cache_reprime_cost: breakeven.cache_reprime_cost, breakeven_decision: breakeven.decision, breakeven_reason_code: breakeven.breakeven_reason_code, } : defaultBreakevenTelemetry()), saar_buffer_active: saar?.buffer_active ?? false, saar_hard_lock: saar?.hard_lock ?? false, turn_index_in_session: saar?.turn_index_in_session ?? null, saar_reason_code: saar?.saar_reason_code ?? null, ...(flipFlop ? { flip_flop_consecutive_tier_flips: flipFlop.consecutive_tier_flips, flip_flop_tier_pinned: flipFlop.tier_pinned, flip_flop_shadow_event: flipFlop.shadow_event, } : defaultFlipFlopTelemetry()), }; } /** Attach breakeven and SAAR observability to routing decision features (SP-126). */ export function enrichRoutingDecisionWithPinEconomics( request: RoutingRequest, decision: RoutingDecision, options?: Omit, ): RoutingDecision { const input: PinEconomicsObservabilityInput = { request, decision, ...(options?.fleet !== undefined ? { fleet: options.fleet } : {}), ...(options?.sessionPinner !== undefined ? { sessionPinner: options.sessionPinner } : {}), ...(options?.saarConfig !== undefined ? { saarConfig: options.saarConfig } : {}), }; const breakeven = buildBreakevenObservability(input); const saar = buildSaarObservability(input); const flipFlop = buildFlipFlopObservability(input); if (!breakeven && !saar && !flipFlop) { return decision; } return { ...decision, features: { ...(decision.features ?? emptyFeatureSidecar()), ...(breakeven ? { breakeven } : {}), ...(saar ? { saar } : {}), }, }; } function emptyFeatureSidecar() { return { triage: null, requirements: null, candidates: null, tier_hint: null, tier_hint_reason_code: null, low_intensity_score: null, p_success_cheap: null, p_success_raw: null, p_success_calibrated: null, p_success_alpha: null, local_eligible_reason: null, }; } /** Attach tier-selection observability to a routing decision features sidecar (SP-113). */ export function enrichRoutingDecisionWithTierSelection( decision: RoutingDecision, clusterMatchTable?: readonly ClusterMatchTableEntry[] | null, ): RoutingDecision { const tierSelection = buildTierSelectionObservability({ decision, clusterMatchTable: clusterMatchTable ?? null, }); if (!tierSelection) { return decision; } return { ...decision, features: { ...(decision.features ?? emptyFeatureSidecar()), tier_selection: tierSelection, }, }; } export interface ExplainEnrichmentOptions { readonly fleet?: readonly ModelProfile[]; readonly contextFitConfig?: ContextFitConfig; readonly clusterMatcher?: ClusterMatcher; readonly sessionPinner?: SessionPinner; readonly saarConfig?: SaarConfig; readonly priceCatalog?: PriceCatalog | null; readonly quotaWindowPosition?: QuotaWindowPosition; readonly virtualCostV2Config?: VirtualCostV2Config; } /** Attach context-fit and tier-selection observability for explain responses (SP-110, SP-113). */ export async function enrichRoutingDecisionForExplain( request: RoutingRequest, decision: RoutingDecision, options?: ExplainEnrichmentOptions, ): Promise { const withContextFit = enrichRoutingDecisionWithContextFit( request, decision, options?.fleet, options?.contextFitConfig, ); let clusterMatchTable: readonly ClusterMatchTableEntry[] | null = null; if (options?.clusterMatcher) { try { clusterMatchTable = await options.clusterMatcher.matchTable(request); } catch { clusterMatchTable = null; } } return enrichRoutingDecisionWithPinEconomics( request, enrichRoutingDecisionWithPlanningDelegate( enrichRoutingDecisionWithTierSelection(withContextFit, clusterMatchTable), ), pinEconomicsOptionsFromExplain(options), ); } function pinEconomicsOptionsFromExplain( options?: ExplainEnrichmentOptions, ): Omit { return { ...(options?.fleet !== undefined ? { fleet: options.fleet } : {}), ...(options?.sessionPinner !== undefined ? { sessionPinner: options.sessionPinner } : {}), ...(options?.saarConfig !== undefined ? { saarConfig: options.saarConfig } : {}), ...(options?.priceCatalog !== undefined ? { priceCatalog: options.priceCatalog } : {}), ...(options?.quotaWindowPosition !== undefined ? { quotaWindowPosition: options.quotaWindowPosition } : {}), ...(options?.virtualCostV2Config !== undefined ? { virtualCostV2Config: options.virtualCostV2Config } : {}), }; } /** Attach context-fit observability to a routing decision features sidecar (SP-110). */ export function enrichRoutingDecisionWithContextFit( request: RoutingRequest, decision: RoutingDecision, fleet?: readonly ModelProfile[], contextFitConfig?: ContextFitConfig, ): RoutingDecision { const contextFit = buildContextFitObservability({ request, decision, fleet, ...(contextFitConfig !== undefined ? { contextFitConfig } : {}), }); if (!contextFit) { return decision; } return { ...decision, features: { ...(decision.features ?? emptyFeatureSidecar()), context_fit: contextFit, }, }; } export interface RoutingDecisionLogDelegate { readonly provider: string; readonly modelId: string; readonly api: string; } /** JSON payload for SMART_ROUTER_LOG_ROUTING=1 stderr lines (SP-110). */ export function buildRoutingDecisionLogPayload( request: RoutingRequest, decision: RoutingDecision, delegate?: RoutingDecisionLogDelegate, fleet?: readonly ModelProfile[], contextFitConfig?: ContextFitConfig, pinEconomics?: Omit, ): Record { const enriched = enrichRoutingDecisionWithPinEconomics( request, enrichRoutingDecisionWithPlanningDelegate( enrichRoutingDecisionWithTierSelection( enrichRoutingDecisionWithContextFit( request, decision, fleet, contextFitConfig, ), ), ), { ...(fleet !== undefined ? { fleet } : {}), ...(pinEconomics?.sessionPinner !== undefined ? { sessionPinner: pinEconomics.sessionPinner } : {}), ...(pinEconomics?.saarConfig !== undefined ? { saarConfig: pinEconomics.saarConfig } : {}), }, ); const tierSelection = enriched.features?.tier_selection; const breakeven = enriched.features?.breakeven as BreakevenObservabilityV2 | undefined; const saar = enriched.features?.saar; const planningDelegate = enriched.features?.planning_delegate; const flipFlop = buildFlipFlopObservability({ request, decision, ...(fleet !== undefined ? { fleet } : {}), ...(pinEconomics?.sessionPinner !== undefined ? { sessionPinner: pinEconomics.sessionPinner } : {}), ...(pinEconomics?.saarConfig !== undefined ? { saarConfig: pinEconomics.saarConfig } : {}), }); return { request_id: enriched.request_id, selected_model_id: enriched.selected_model_id, tier: enriched.tier, stage: enriched.stage, reason_code: enriched.reason_code, // Top-level checklist fields for SMART_ROUTER_LOG_ROUTING=1 (SP-178 / #99) low_intensity_score: tierSelection?.low_intensity_score ?? enriched.features?.low_intensity_score ?? null, tier_hint: tierSelection?.tier_hint ?? enriched.features?.tier_hint ?? null, local_eligible_reason: tierSelection?.local_eligible_reason ?? enriched.features?.local_eligible_reason ?? null, cluster_id: tierSelection?.cluster_id ?? null, routing_latency_ms: enriched.routing_latency_ms, features: enriched.features ?? null, cluster_summary: tierSelection ? { cluster_id: tierSelection.cluster_id, cluster_similarity: tierSelection.cluster_similarity, cluster_margin: tierSelection.cluster_margin, tier_hint: tierSelection.tier_hint, tier_selection_reason_code: tierSelection.tier_selection_reason_code, low_intensity_score: tierSelection.low_intensity_score, p_success_cheap: tierSelection.p_success_cheap, } : null, breakeven_summary: breakeven ? { marginal_savings: breakeven.marginal_savings, future_cache_value: breakeven.future_cache_value, cache_reprime_cost: breakeven.cache_reprime_cost, decision: breakeven.decision, breakeven_reason_code: breakeven.breakeven_reason_code, quota_premium_usd: breakeven.quota_premium_usd, kv_cache_credit_usd: breakeven.kv_cache_credit_usd, } : null, saar_summary: saar ? { buffer_active: saar.buffer_active, hard_lock: saar.hard_lock, turn_index_in_session: saar.turn_index_in_session, planning_turn_buffer: saar.planning_turn_buffer, idle_timeout_seconds: saar.idle_timeout_seconds, saar_reason_code: saar.saar_reason_code, } : null, planning_delegate_summary: planningDelegate ? { path: planningDelegate.path, primary_model_id: planningDelegate.primary_model_id, delegate_model_id: planningDelegate.delegate_model_id, compressed_context: planningDelegate.compressed_context, planning_delegate_reason_code: planningDelegate.planning_delegate_reason_code, fallback_reason: planningDelegate.fallback_reason, workers_spawned: planningDelegate.workers_spawned, workers_succeeded: planningDelegate.workers_succeeded, worker_timeout_count: planningDelegate.worker_timeout_count, } : null, flip_flop_summary: flipFlop ? { consecutive_tier_flips: flipFlop.consecutive_tier_flips, tier_pinned: flipFlop.tier_pinned, shadow_event: flipFlop.shadow_event, } : null, pin_only_fallback_active: resolvePinOnlyFallbackActive(enriched), delegate, }; } function defaultContextFitTelemetry(): Pick< RoutingTelemetry, | 'estimated_input_tokens' | 'context_fit_viable_count' | 'context_fit_rejected_json' | 'context_overflow_pin_break' | 'selected_model_max_input_tokens' | 'context_fit_reason_code' > { return { estimated_input_tokens: null, context_fit_viable_count: null, context_fit_rejected_json: null, context_overflow_pin_break: false, selected_model_max_input_tokens: null, context_fit_reason_code: null, }; } /** Default context-fit telemetry scalars for tests and legacy store reads. */ export const DEFAULT_CONTEXT_FIT_TELEMETRY_FIELDS = defaultContextFitTelemetry(); function defaultTierSelectionTelemetry(): Pick< RoutingTelemetry, | 'cluster_id' | 'cluster_similarity' | 'cluster_margin' | 'low_intensity_score' | 'tier_hint' | 'p_success_cheap' | 'local_eligible_reason' | 'tier_selection_reason_code' > { return { cluster_id: null, cluster_similarity: null, cluster_margin: null, low_intensity_score: null, tier_hint: null, p_success_cheap: null, local_eligible_reason: null, tier_selection_reason_code: null, }; } /** Default tier-selection telemetry scalars for tests and legacy store reads. */ export const DEFAULT_TIER_SELECTION_TELEMETRY_FIELDS = defaultTierSelectionTelemetry(); function tierSelectionTelemetryFromDecision( decision: RoutingDecision, ): ReturnType { const observability = buildTierSelectionObservability({ decision }); if (!observability) { return defaultTierSelectionTelemetry(); } return { cluster_id: observability.cluster_id, cluster_similarity: observability.cluster_similarity, cluster_margin: observability.cluster_margin, low_intensity_score: observability.low_intensity_score, tier_hint: observability.tier_hint, p_success_cheap: observability.p_success_cheap, local_eligible_reason: observability.local_eligible_reason, tier_selection_reason_code: observability.tier_selection_reason_code, }; } /** Default context-fit dataset scalars for tests and legacy store reads. */ export const DEFAULT_CONTEXT_FIT_DATASET_FIELDS = { estimated_input_tokens_gate: null, context_fit_viable_count: null, context_fit_rejected_json: null, context_overflow_pin_break: false, selected_model_max_input_tokens: null, context_fit_reason_code: null, } as const satisfies Pick< import('../../domain/types/index.js').RoutingDatasetRecord, | 'estimated_input_tokens_gate' | 'context_fit_viable_count' | 'context_fit_rejected_json' | 'context_overflow_pin_break' | 'selected_model_max_input_tokens' | 'context_fit_reason_code' >; /** Default tier-selection dataset scalars for tests and legacy store reads. */ export const DEFAULT_TIER_SELECTION_DATASET_FIELDS = { cluster_id: null, cluster_similarity: null, cluster_margin: null, low_intensity_score: null, tier_hint: null, p_success_cheap: null, local_eligible_reason: null, tier_selection_reason_code: null, } as const satisfies Pick< import('../../domain/types/index.js').RoutingDatasetRecord, | 'cluster_id' | 'cluster_similarity' | 'cluster_margin' | 'low_intensity_score' | 'tier_hint' | 'p_success_cheap' | 'local_eligible_reason' | 'tier_selection_reason_code' >; // ─── Emitter ───────────────────────────────────────────────────────────────── /** Optional route_path classification supplied by the pipeline (SP-212, #119). */ export interface RoutePathTelemetryExtras { readonly routePath?: RoutePath | null; readonly routePathConfidence?: number | null; } export class RoutingTelemetryEmitter { private readonly entries: RoutingTelemetry[] = []; private readonly maxEntries: number; private readonly windowMs: number; private readonly clock: () => string; private readonly onRecord: ((record: RoutingTelemetry) => void) | undefined; private readonly fleet: readonly ModelProfile[] | undefined; private readonly contextFitConfig: ContextFitConfig | undefined; private readonly sessionPinner: SessionPinner | undefined; private readonly saarConfig: SaarConfig | undefined; private readonly priceCatalog: PriceCatalog | null | undefined; private readonly quotaWindowPosition: QuotaWindowPosition | undefined; private readonly virtualCostV2Config: VirtualCostV2Config | undefined; constructor(options?: TelemetryEmitterOptions) { this.maxEntries = options?.maxEntries ?? TELEMETRY_MAX_ENTRIES; this.windowMs = options?.windowMs ?? TELEMETRY_WINDOW_MS; this.clock = options?.clock ?? (() => new Date().toISOString()); this.onRecord = options?.onRecord; this.fleet = options?.fleet; this.contextFitConfig = options?.contextFitConfig; this.sessionPinner = options?.sessionPinner; this.saarConfig = options?.saarConfig; this.priceCatalog = options?.priceCatalog; this.quotaWindowPosition = options?.quotaWindowPosition; this.virtualCostV2Config = options?.virtualCostV2Config; } /** * Emit a telemetry record from a completed routing decision. * Enforces the rolling window (time + count) before appending. */ emit( request: RoutingRequest, decision: RoutingDecision, extras?: RoutePathTelemetryExtras, ): RoutingTelemetry { return this.appendRecord(request, decision, extras); } /** * Emit telemetry when a pipeline stage throws and routing degrades to safe default. */ emitPipelineError( request: RoutingRequest, failedStage: string, fallback: RoutingDecision, extras?: RoutePathTelemetryExtras, ): RoutingTelemetry { const errorDecision: RoutingDecision = { ...fallback, stage: failedStage as RoutingDecision['stage'], reason_code: 'pipeline_error', }; return this.appendRecord(request, errorDecision, extras); } private appendRecord( request: RoutingRequest, decision: RoutingDecision, extras?: RoutePathTelemetryExtras, ): RoutingTelemetry { makeTelemetryRoom(this.entries, this.maxEntries); const contextFit = buildContextFitObservability({ request, decision, ...(this.fleet !== undefined ? { fleet: this.fleet } : {}), ...(this.contextFitConfig !== undefined ? { contextFitConfig: this.contextFitConfig } : {}), }); const contextFitFields = contextFit ?? defaultContextFitTelemetry(); const tierSelectionFields = tierSelectionTelemetryFromDecision(decision); const pinEconomicsFields = pinEconomicsTelemetryFromInput({ request, decision, ...(this.fleet !== undefined ? { fleet: this.fleet } : {}), ...(this.sessionPinner !== undefined ? { sessionPinner: this.sessionPinner } : {}), ...(this.saarConfig !== undefined ? { saarConfig: this.saarConfig } : {}), ...(this.priceCatalog !== undefined ? { priceCatalog: this.priceCatalog } : {}), ...(this.quotaWindowPosition !== undefined ? { quotaWindowPosition: this.quotaWindowPosition } : {}), ...(this.virtualCostV2Config !== undefined ? { virtualCostV2Config: this.virtualCostV2Config } : {}), }); const planningDelegateFields = planningDelegateTelemetryFromDecision(decision); const pinOnlyFallbackFields = pinOnlyFallbackTelemetryFromDecision(decision); const prewarmFields = prewarmTelemetryFromDecision(decision); const record: RoutingTelemetry & FlipFlopTelemetryFields = { timestamp: this.clock(), session_id: request.session_id, request_id: decision.request_id, turn_type: request.turn_type ?? 'unknown', stage: decision.stage, reason_code: decision.reason_code, selected_model_id: decision.selected_model_id, estimated_cost_usd: decision.estimated_cost_usd ?? 0, routing_latency_ms: decision.routing_latency_ms, pin_reason: decision.pin_reason, estimated_input_tokens: contextFitFields.estimated_input_tokens, context_fit_viable_count: contextFitFields.context_fit_viable_count, context_fit_rejected_json: contextFitFields.context_fit_rejected_json, context_overflow_pin_break: contextFitFields.context_overflow_pin_break, selected_model_max_input_tokens: contextFitFields.selected_model_max_input_tokens, context_fit_reason_code: contextFitFields.context_fit_reason_code, cluster_id: tierSelectionFields.cluster_id, cluster_similarity: tierSelectionFields.cluster_similarity, cluster_margin: tierSelectionFields.cluster_margin, low_intensity_score: tierSelectionFields.low_intensity_score, tier_hint: tierSelectionFields.tier_hint, p_success_cheap: tierSelectionFields.p_success_cheap, local_eligible_reason: tierSelectionFields.local_eligible_reason, tier_selection_reason_code: tierSelectionFields.tier_selection_reason_code, marginal_savings: pinEconomicsFields.marginal_savings, future_cache_value: pinEconomicsFields.future_cache_value, cache_reprime_cost: pinEconomicsFields.cache_reprime_cost, breakeven_decision: pinEconomicsFields.breakeven_decision, breakeven_reason_code: pinEconomicsFields.breakeven_reason_code, saar_buffer_active: pinEconomicsFields.saar_buffer_active, saar_hard_lock: pinEconomicsFields.saar_hard_lock, turn_index_in_session: pinEconomicsFields.turn_index_in_session, saar_reason_code: pinEconomicsFields.saar_reason_code, flip_flop_consecutive_tier_flips: pinEconomicsFields.flip_flop_consecutive_tier_flips, flip_flop_tier_pinned: pinEconomicsFields.flip_flop_tier_pinned, flip_flop_shadow_event: pinEconomicsFields.flip_flop_shadow_event, ...planningDelegateFields, ...pinOnlyFallbackFields, ...prewarmFields, route_path: extras?.routePath ?? null, route_path_confidence: extras?.routePathConfidence ?? null, }; this.entries.push(record); this.onRecord?.(record); return record; } /** Current number of retained entries. */ get size(): number { return this.entries.length; } /** Snapshot of all retained entries (newest last). */ snapshot(): readonly RoutingTelemetry[] { evictExpiredTelemetryEntries(this.entries, this.windowMs); return [...this.entries]; } }