/** * NDJSON observability — one line per call to ~/.ollama-intern/log.ndjson. * * This is what lets Claude tune delegation instead of guessing: * "that call used 5k tokens on Deep when fast would have sufficed." * * Also logs timeout events and fallback decisions so later we can prove * the system degraded correctly under pressure, not just that a call was slow. */ import type { Envelope, Residency } from "./envelope.js"; import type { Tier } from "./tiers.js"; /** * Closed enum for the `op` field on NDJSON events (Phase 7 / FT-001). * * Values borrowed from OpenTelemetry's `gen_ai.operation.name` semantic * convention (v1.37 Experimental) so a future OTel exporter can map * cleanly without renaming. The set is closed — adding a new value is * a deliberate schema change. `startup` / `shutdown` are MCP-server * concerns not covered by OTel's GenAI vocab; they live here as the * minimum-needed extensions for lifecycle events. * * - `chat` — Ollama /api/chat HTTP call * - `embeddings` — Ollama /api/embed HTTP call (mapped from generate * for non-chat completions per OTel mapping) * - `pack_step` — One step in an incident/repo/change pack pipeline * - `semaphore_wait` — Operator wait on the global semaphore * - `guardrail` — A guardrail rule fired (warn / deny / strip) * - `shutdown` — Server received SIGTERM/SIGINT (lifecycle) * - `startup` — Server startup-probe failure (lifecycle) */ export type CorrelationOp = "chat" | "embeddings" | "pack_step" | "semaphore_wait" | "guardrail" | "shutdown" | "startup"; /** * Phase 7 / FT-001 — correlation fields that may appear on ANY LogEvent * variant. These are populated by: * - `run_id` — auto-stamped from the active `CorrelationContext` via * `withCorrelation()` on every `log()` call. * - `call_id` / `parent_call_id` / `op` — explicitly set by emitters * that own the HTTP-attempt or pack-sub-step granularity (ollama.ts * for HTTP `op:'chat'|'embeddings'`, pack handlers for the * `op:'pack_step'` + parent linkage). * * Lifted onto every variant via intersection so the discriminated union * stays intact (TypeScript narrows by `kind`) while new fields don't * require a parallel-types explosion. Operators can grep ANY event line * for `run_id` regardless of `kind`. */ export interface CorrelationFields { run_id?: string; call_id?: string; parent_call_id?: string; op?: CorrelationOp; } export type LogEvent = CorrelationFields & ({ kind: "call"; ts: string; tool: string; envelope: Envelope; } | { kind: "timeout"; ts: string; tool: string; tier: Tier; timeout_ms: number; /** Concrete model that timed out (pulled from ctx.tiers[tier]). */ model?: string; /** Active profile name — lets operators diff timeouts across profiles. */ profile_name?: string; } | { kind: "fallback"; ts: string; tool: string; from: Tier; to: Tier; reason: string; profile_name?: string; } | { kind: "backend_fallback"; ts: string; from: "cloud"; to: "local"; /** cloud_timeout | cloud_5xx | cloud_rate_limited | cloud_unreachable | cloud_auth_failed | circuit_open */ reason: string; tier?: Tier; model?: string; } | { kind: "cloud_egress"; ts: string; host: string; model: string; mode: "standby" | "primary"; tier?: Tier; } | { kind: "guardrail"; ts: string; tool: string; rule: string; action: string; detail?: unknown; } | { kind: "prewarm"; ts: string; tier: Tier; model: string; hardware_profile: string; success: boolean; elapsed_ms: number; residency: Residency | null; error?: string; } | { kind: "prewarm:in_progress_request"; ts: string; tool: string; } | { kind: "semaphore:wait"; ts: string; tier: Tier | "unknown"; queue_depth: number; in_flight: number; expected_wait_ms: number; profile_name?: string; } | { kind: "pack_step"; ts: string; pack: "incident" | "repo" | "change"; step: string; step_index: number; total_steps: number; }); export interface Logger { log(event: LogEvent): Promise; } export declare class NdjsonLogger implements Logger { private path; private readyPromise; /** * Logger failures (EACCES, ENOSPC, read-only fs) used to be fully silent — * observability would quietly disable itself and the operator had no way * to know. Emit ONCE to stderr on the first write failure so the operator * sees "log disabled because of X" without drowning them in per-call noise. * Subsequent failures still swallow; tool calls never break on log writes. */ private warnedOnFailure; constructor(path?: string); private ready; log(event: LogEvent): Promise; } /** No-op logger for tests. */ export declare class NullLogger implements Logger { events: LogEvent[]; log(event: LogEvent): Promise; } export declare function timestamp(): string; /** Build a LogEvent for a completed tool call from its envelope. */ export declare function callEvent(tool: string, envelope: Envelope): LogEvent; /** * Build a pack_step progress event. Emitted by packs before entering * each deterministic step — coarse-grained progress that costs a single * NDJSON line per step, not mid-step streaming. */ export declare function packStepEvent(args: { pack: "incident" | "repo" | "change"; step: string; step_index: number; total_steps: number; }): LogEvent; //# sourceMappingURL=observability.d.ts.map