{"_id":"@ai-craft/agent-observer","name":"@ai-craft/agent-observer","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ai-craft/agent-observer","version":"0.1.0","description":"Provider-agnostic agent observability harness for @ai-craft","type":"module","main":"./index.js","module":"./index.js","types":"./index.d.ts","exports":{".":{"types":"./index.d.ts","import":"./index.js"}},"bin":{"agent-observer-usage":"bin/usage.mjs"},"engines":{"node":">=22.0.0"},"license":"MIT","publishConfig":{"access":"public"},"dependencies":{},"_id":"@ai-craft/agent-observer@0.1.0","_integrity":"sha512-4A5Y5I2A0YYsbtM9rYWMpAF1SvLc3fiwIm+8lfF0PpxstDJsuvUNeeBJE7aVQrbCKjW1j3NBCtDvHS1DzilqOA==","_resolved":"/private/var/folders/_j/tzygz83s12v4rnxgchnxz55m0000gp/T/317492a85fb2d4dd04c7c877c60b39e5/ai-craft-agent-observer-0.1.0.tgz","_from":"file:ai-craft-agent-observer-0.1.0.tgz","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-4A5Y5I2A0YYsbtM9rYWMpAF1SvLc3fiwIm+8lfF0PpxstDJsuvUNeeBJE7aVQrbCKjW1j3NBCtDvHS1DzilqOA==","shasum":"2a9b6650276df3b7d65ae53788885e2821243a11","tarball":"https://registry.npmjs.org/@ai-craft/agent-observer/-/agent-observer-0.1.0.tgz","fileCount":42,"unpackedSize":139402,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDc7YyRR/8hMZiKBh4yUD4aaNVHjAvfLbTIYEtUEFKPXgIhAJ8VHwbP0DJI7554gaWQwou0+LxzuEuZrp1M2MVUPT1p"}]},"_npmUser":{"name":"volkz","email":"delacruzd93@gmail.com"},"directories":{},"maintainers":[{"name":"volkz","email":"delacruzd93@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/agent-observer_0.1.0_1784450388022_0.7883514187235194"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-19T08:39:47.920Z","0.1.0":"2026-07-19T08:39:48.150Z","modified":"2026-07-19T08:39:48.330Z"},"maintainers":[{"name":"volkz","email":"delacruzd93@gmail.com"}],"description":"Provider-agnostic agent observability harness for @ai-craft","license":"MIT","readme":"# @ai-craft/agent-observer\n\nProvider-agnostic agent observability harness. Zero external runtime dependencies.\n\nWraps any agent call and emits structured `AgentRunEvent` objects to pluggable sinks — with concrete adapters for Anthropic, OpenAI, Gemini, Snowflake Cortex, and agent-loop.\n\n## Installation\n\n```bash\npnpm add @ai-craft/agent-observer\n```\n\n## Quick start\n\n```ts\nimport { EventBus, RunSession, ConsoleSink, TokentrackerSink, mapAnthropicEvent } from '@ai-craft/agent-observer';\n\nconst bus = new EventBus();\nbus.register(new ConsoleSink());\nbus.register(new TokentrackerSink()); // → ~/.ai-craft/usage.jsonl\n\nconst session = new RunSession(bus, 'anthropic', 'claude-haiku-4-5-20251001', {\n  sessionId: 'my-conversation-id', // optional — groups runs in the ledger\n});\n\n// Pipe any Anthropic streaming response through mapAnthropicEvent\nlet inputTokens;\nfor await (const raw of stream) {\n  const event = mapAnthropicEvent(raw, { run_id: session.run_id, turn: 1, provider: 'anthropic', model: 'claude-haiku-4-5-20251001', input_tokens: inputTokens });\n  if (!event) continue;\n  if (event.event_type === 'run_start') inputTokens = event.usage?.input_tokens;\n  bus.publish(event);\n}\n```\n\n## Core concepts\n\n### EventBus\n\nThe central hub. All adapters publish to it; all sinks consume from it.\n\n```ts\n// Standard — process all events\nconst bus = new EventBus();\n\n// High-traffic sampling — only observe 10% of runs\nconst bus = new EventBus({ sampleRate: 0.1 });\n```\n\n### RunSession\n\nTracks a single agent run. Auto-generates `run_id`, tracks TTFT, and propagates `session_id`.\n\n```ts\nconst session = new RunSession(bus, provider, model, { sessionId, parentRunId });\n\nsession.emit({ event_type: 'run_start' });\nsession.emit({ event_type: 'text_delta', text: 'Hello' });\nsession.complete(latency_ms, usage);  // emits run_complete with ttft_ms injected automatically\nsession.error(err);                   // emits event_type: error\nsession.retry();                      // re-emits run_start, incrementing retry_count\n```\n\n### AgentRunEvent schema\n\n```ts\ninterface AgentRunEvent {\n  event_type: 'run_start' | 'text_delta' | 'thinking_delta' | 'status'\n             | 'tool_use' | 'tool_result' | 'run_complete' | 'error';\n  run_id: string;\n  session_id?: string;        // groups runs by conversation\n  parent_run_id?: string;     // links child run to parent in multi-agent setups\n  turn: number;\n  ts: string;                 // ISO 8601 UTC\n  provider: string;\n  model: string | null;\n  usage?: {\n    input_tokens: number | null;\n    output_tokens: number | null;\n    total_tokens: number | null;\n    token_source: 'api' | 'estimated' | 'unavailable';\n    cache_write_tokens?: number | null;  // Anthropic prompt cache write tokens\n    cache_read_tokens?: number | null;   // Anthropic prompt cache read tokens\n  };\n  latency_ms?: number;        // set on run_complete\n  ttft_ms?: number;           // time-to-first-token, set on run_complete when text_delta fired\n  cost_usd?: number;          // computed from PRICE_MAP; undefined for unknown models\n  text?: string;\n  tool_calls?: ToolCallRecord[];\n  tool_result_content?: unknown;  // payload of tool_result events\n  status_message?: string;\n  error?: { code?: string; message: string };\n  retry_count?: number;       // retry attempt counter; present on run_start when > 0\n}\n```\n\n## Adapters\n\n| Adapter | Function | Source |\n|---------|----------|--------|\n| Anthropic | `mapAnthropicEvent(raw, ctx)` | Anthropic Messages streaming SSE |\n| OpenAI | `mapOpenAIChunk(raw, ctx)` | OpenAI Chat Completions streaming chunks |\n| Gemini / Vertex | `mapGeminiChunk(raw, ctx)` | Gemini `generateContentStream` chunks |\n| Snowflake Cortex | `mapCortexEvent(raw, ctx)` | Cortex `:run` SSE events |\n| agent-loop | `mapLoopEvent(event, provider?)` | `@ai-craft/agent-loop` LoopEvents (`event.type` or `event.kind`) |\n\n## Sinks\n\n### TokentrackerSink\n\nAppends one JSONL line per `run_complete` to `~/.ai-craft/usage.jsonl`.\n\n```jsonc\n{\n  \"ts\": \"2026-07-10T10:00:00.000Z\",\n  \"run_id\": \"run-1720605600-abc123\",\n  \"session_id\": \"my-conversation\",     // present when sessionId was set\n  \"provider\": \"anthropic\",\n  \"model\": \"claude-haiku-4-5-20251001\",\n  \"input_tokens\": 19,\n  \"output_tokens\": 8,\n  \"token_source\": \"api\",\n  \"latency_ms\": 834,\n  \"ttft_ms\": 312,                      // present when text_delta fired\n  \"cost_usd\": 0.0000474                // present for known models\n}\n```\n\nOverride the ledger path:\n\n```ts\nnew TokentrackerSink('/my/custom/path.jsonl')\n// or via env:\nAICRAFT_USAGE_LOG=/my/custom/path.jsonl\n```\n\n`token_source` can be `'estimated'` for providers with no API-reported usage (e.g. Snowflake\nCortex, whose SSE stream never returns token counts). In that case `input_tokens`/`output_tokens`\nare computed via `estimateTokenCount()` from the `request_sent` event's outbound `messages` and\nthe accumulated response text — a directional ~4-characters-per-token heuristic, not a real\ntokenizer. `cost_usd` is only populated for estimated runs when a per-1K-token rate is configured,\nvia the optional `AGENT_OBSERVER_COST_PER_1K_TOKENS` env var or `TokentrackerSink`'s 2nd\nconstructor arg (`new TokentrackerSink(path, ratePerThousandTokens)`); otherwise `cost_usd` is\nomitted. The `DebuggerSink` dashboard labels estimated figures `(est.)` to distinguish them from\nreal API-reported usage.\n\n### ConsoleSink\n\nPrints every event to stdout with icons and compact formatting.\n\n### HttpSink\n\nPOSTs every event as JSON to a webhook URL.\n\n```ts\nbus.register(new HttpSink('https://my-telemetry-endpoint.example.com/events'));\n```\n\n### AlertSink\n\nFires a callback and/or webhook when latency or token thresholds are exceeded.\n\n```ts\nbus.register(new AlertSink({\n  latencyThresholdMs: 5000,\n  tokenThreshold: 10_000,\n  onAlert: (event, reason) => console.error('[ALERT]', reason),\n  webhook: 'https://hooks.slack.com/...',  // optional — fire-and-forget POST\n}));\n```\n\n## Cost model\n\n```ts\nimport { computeCost, PRICE_MAP, CORTEX_PRICE_MAP, registerPrices } from '@ai-craft/agent-observer';\n\n// Direct USD pricing (Anthropic, OpenAI, Google)\nconst cost = computeCost('anthropic', 'claude-sonnet-4-6', inputTokens, outputTokens);\n// → number (USD) | null (unknown model or missing token counts)\n\n// Snowflake Cortex credit-based pricing\n// cost = (tokens / 1M) × creditsPerMillion × creditCostUsd\nconst cortexCost = computeCost('cortex', 'claude-3-5-sonnet', inputTokens, outputTokens, {\n  creditCostUsd: 3.0,  // USD per Snowflake credit\n});\n// or set AGENT_OBSERVER_SNOWFLAKE_CREDIT_COST_USD=3.0 as a process env fallback\n\n// Register prices for new or private models at runtime\nregisterPrices({\n  'cortex/my-private-model': { inputCredits: 5, outputCredits: 5 },\n  'myprovider/gpt-custom':   { input: 2 / 1e6, output: 8 / 1e6 },\n});\n```\n\nSupported providers in `PRICE_MAP`: Anthropic Claude 3.x / 4.x, OpenAI GPT-4o / o1, Google Gemini 1.5 / 2.x.\n\n`CORTEX_PRICE_MAP` covers Snowflake Cortex models: `claude-3-5-sonnet`, `claude-3-haiku`, `mistral-large2`, `llama3-70b`, `llama3-8b`, `mixtral-8x7b`, `snowflake-arctic`. Keys use the `cortex/${model}` format.\n\n## Usage CLI\n\nView the JSONL ledger as a formatted table:\n\n```bash\n# From the dist dir after build, or via npx when published:\nnode dist/packages/agent-observer/bin/usage.mjs\nnode dist/packages/agent-observer/bin/usage.mjs --tail 10\nnode dist/packages/agent-observer/bin/usage.mjs --session my-conversation-id\n```\n\n## xfuse / Snowflake Cortex integration\n\nSet env vars before starting entity-service:\n\n```bash\nAGENT_OBSERVER_DIST=/path/to/dist/packages/agent-observer/index.js\nAGENT_OBSERVER_SAMPLE_RATE=1  # 0.0–1.0; default 1 (observe all)\n```\n\nThe `withCortexObserver` wrapper in `ai-chat-observer.helper.ts` handles the rest:\n- Lazy singleton load of the dist\n- Per-agent turn counter (increments per `SendMessage` call)\n- Transparent `yield*` passthrough — stream is never affected by observer errors\n\n## Running tests\n\n```bash\nnx test agent-observer\nnx run agent-observer:typecheck\nnx run agent-observer:typecheck-spec\nnx build agent-observer\n```\n\n121 unit + integration tests. Zero external runtime dependencies.\n\n## Examples\n\n```bash\n# Live Anthropic stream (needs ANTHROPIC_API_KEY)\nnode packages/agent-observer/examples/anthropic-stream.mjs\n\n# agent-loop tap (needs ANTHROPIC_API_KEY + nx build agent-loop agent-llm)\nnode packages/agent-observer/examples/agent-loop-tap.mjs\n```\n","readmeFilename":"README.md","_rev":"1-525ca47bb525adfdaabbe96b060c1c54"}