Skip to content

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 15:52
· 21 commits to main since this release
79cb2f1

[0.7.0] - 2026-10-12

"Captura automática e interoperabilidad" (automatic capture and interoperability): an OTLP/HTTP GenAI traces
receiver and file import, packaged LangGraph and OpenAI Agents SDK adapters tested against the real
frameworks in CI, a durable local queue in both SDKs, and a Prometheus /metrics export (core endpoint,
ledger-derived families, in-process ingestion counters, optional OTLP metrics push).

Upgrading

Purely additive: no breaking change to the event contract, the HTTP API, or the embeddable library's props.
Every new setting defaults to off or to its existing behavior, so an existing deployment needs no
configuration change to keep working.

  • The SDK durable queue (Python spool_dir=..., TypeScript spoolDir/AGENT_VIEWER_SPOOL_DIR) is opt-in:
    without it, both SDKs behave exactly as before (an unrecovered transient failure still raises/throws).
  • GET /metrics is opt-in (AGENT_VIEWER_METRICS=1, CLI --metrics); unset, the route answers 404
    exactly like before this release. See docs/metrics.md for every new environment
    variable it reads.
  • The OTLP traces receiver (POST /v1/traces) and file import share the existing OTLP auth, rate limit and
    size-limit settings already in place for /v1/logs//v1/metrics; no new required variable.
  • examples/openai-agents-adapter/ is removed (dead code since issue #361); import
    @warlockcode/agent-viewer-openai-agents instead if anything in your own tree still referenced that path
    (see the Removed section below).

Added

  • Durable local queue in the Python SDK (#101, v0.7.0 milestone): optional, bounded, on-disk spool (spool_dir=..., stdlib sqlite3 only, no third-party dependency) so a transient failure (connection down, reset, timeout, 408, 429, any 5xx) after the normal retries never raises into the host agent. Events are written to disk with their original id and timestamp and replayed in enqueue order once the server returns, through POST /api/v1/events/batch, so the server's own idempotency by id keeps totals honest. New AgentViewer arguments (spool_dir, spool_max_events, spool_max_bytes, spool_overflow, spool_batch_size, spool_background, spool_probe_interval, spool_max_probe_interval) and methods (flush(), spool_stats(), spool_rejected(), close(), usable as a context manager, plus an atexit final flush). A size cap evicts the oldest non-usage events before usage events (drop_oldest, the default) or refuses the new event (drop_newest); every drop and every server-side rejection is counted in SpoolStats, never silently lost, and the spool never sums, prices or defaults usage (a missing cost stays absent, never 0). Also fixes transient-failure classification with the spool disabled: a raw OSError/http.client.HTTPException and HTTP 408 are now retried instead of escaping or failing immediately. New docs/sdk-queue.md (shared contract with the TypeScript SDK, #102) and tests/fixtures/sdk-queue-scenarios.json (shared test vectors). New tests/test_python_sdk_spool.py (34 tests: outages, restart persistence, overflow matrix, poison events, 413/429/401 handling, corrupt/newer-schema spool files, permissions, fork safety, a seeded randomized concurrency test). CI now runs both Python SDK test files and adds a Python 3.9 job to exercise the SDK's declared minimum version.
  • SDK: per-event sessionId on emit()/emitBatch() and durationMs on toolCompleted()/toolFailed() (first sub-issue of #99, v0.7.0 milestone): prerequisite for the packaged LangGraph adapter (#99), which serves many thread_ids through one process and builds events through the low-level emit/emitBatch path, not through a per-session AgentViewer instance. TypeScript's EmitEventInput.sessionId? (sdk/typescript/index.ts) now overrides the client's own sessionId for that one event in both emit() and emitBatch() (previously hardcoded to this.sessionId, ignoring any such field on the input); Python's AgentViewer.emit() gains the same session_id/timestamp_ms keyword arguments emit_batch() already had per item, so a callback's own fire time can be recorded instead of the time it reaches a background queue. ToolCallFinishOptions (toolCallId plus durationMs) is the new option type for toolCompleted()/toolFailed() (TypeScript) and duration_ms the new keyword for tool_completed()/tool_failed() (Python), matching the contract: durationMs is valid on ToolCompletedPayloadSchema/ToolFailedPayloadSchema but not ToolStartedPayloadSchema, so toolStarted()/tool_started() do not accept it. New tests in tests/sdk-typescript.test.mjs and tests/test_python_sdk.py cover the per-event session override and the duration field landing only on completed/failed. Deferred to later sub-issues of #99: the packages/sdk npm workspace extraction (#343), the TypeScript (#344) and Python (#345) LangGraph adapters and their recorded-run fixtures, and the release/documentation plumbing (#346).
  • packages/sdk: publishable npm workspace extraction of the TypeScript SDK (second sub-issue of #99, v0.7.0 milestone, issue #343): the root package.json now declares "workspaces": ["packages/*"], and the logic that used to live only at sdk/typescript/index.ts moved to the new workspace package @warlockcode/agent-viewer-sdk (packages/sdk/src/index.ts), with no runtime or type import of src/integrations/* or zod: every structural type it needs (CanonicalEvent, CanonicalEventType, EventSeverity, LlmErrorKind, the llm.usage/llm.failed/tool.* payload shapes, ViewerSnapshot, UsageSummary, CallRecord, the usage rollup types, the budget types, UsageFigures) is copied, not imported, from the root contract and server modules, so the published .d.ts references no file outside the package. sdk/typescript/index.ts is now a thin re-export of the package, so tests/sdk-typescript.test.mjs and every example adapter keep working unchanged. A new CI type test (tests/types/sdk-package-canonical-event.ts, checked by npm run lint) asserts the package's CanonicalEvent/CanonicalEventType/EventSeverity/LlmErrorKind stay assignable to the root contract's types in both directions. packages/sdk/package.json has exports, files, publishConfig.access: public, is ESM only, has its own tsconfig.json emitting declarations, and a check:package script running publint --strict and attw --pack . (also runnable at the root as npm run check:package:sdk). The root npm run build:packages (npm run build --workspaces --if-present) builds every workspace package and now runs automatically before npm run lint and npm test (prelint/pretest), since the sdk/typescript/index.ts re-export needs packages/sdk/dist to exist to resolve the package by name. docker/Dockerfile's deps stage now also copies packages/sdk/package.json before npm ci, so the workspace lockfile resolves inside the image. CI gained three new steps: building the workspace packages, check:package:sdk, and a packed-tarball smoke test (npm pack the package, install the .tgz into a throwaway ESM project, import it). packages/sdk/README.md documents the package and the workspace layout convention (issue #100's OpenAI Agents adapter reuses this same layout). Deferred to later sub-issues of #99: the TypeScript (#344) and Python (#345) LangGraph adapters and their recorded-run fixtures, and the release/documentation plumbing (#346) that will actually publish this package to npm.
  • packages/openai-agents: publishable npm workspace extraction of the OpenAI Agents adapter (second sub-issue of #100, v0.7.0 milestone, issue #361): examples/openai-agents-adapter/processor.ts/index.ts move, unchanged in behavior, to the new sibling workspace package @warlockcode/agent-viewer-openai-agents (packages/openai-agents/src/), reusing packages/sdk's exact layout (issue #343) rather than inventing a new convention: same tsconfig.json shape, same exports/files/publishConfig fields, same check:package (publint --strict && attw --pack . --profile esm-only), no second "workspaces" field at the root. The processor now imports AgentViewer/EmitEventInput from the published @warlockcode/agent-viewer-sdk package instead of the repo-relative sdk/typescript/index.ts path; @openai/agents is a peerDependencies entry (the host brings its own version). Deviates from issue #361's own text in one place, documented in packages/openai-agents/README.md: the issue lists @warlockcode/agent-viewer (the main library package) as a peer dependency, but the processor never imports anything from it, only from @warlockcode/agent-viewer-sdk; that is declared as a regular dependencies entry instead (an internal implementation detail, not a version the host needs to pin like @openai/agents). tests/openai-agents-adapter.test.mjs now imports the processor from its new path; its test suite, assertions and the tests/examples-adapters.test.mjs doc comment are otherwise unchanged. Root build:packages now builds packages/sdk before packages/openai-agents explicitly (npm run build --workspaces has no dependency-graph ordering, and this package's .ts sources import the sdk package by name, needing its dist/ first); a new npm run check:package:openai-agents script, a CI step for it, and a packed-tarball smoke test (installing both this package's and packages/sdk's local tarballs together, since neither is on the public npm registry yet, plus the real @openai/agents) are added alongside the existing packages/sdk ones. .github/workflows/release.yml gains a release-openai-agents job (needs release) publishing @warlockcode/agent-viewer-openai-agents with provenance after the main package, same skip-without-NPM_TOKEN/skip-on-prerelease pattern, plus a version-lockstep check against the root package.json (a direct comparison here, not an extension of scripts/release-rules.mjs, which does not yet know about workspace packages). Known limitation, same as packages/sdk's own publish gap (#346): this package's one real dependency, @warlockcode/agent-viewer-sdk, is not yet published to npm, so a real npm install of this package outside this monorepo will not resolve until #346 ships it; the release job above is wired and will skip cleanly today (no NPM_TOKEN configured), not block anything, but a real publish attempt before #346 would fail on that unresolved dependency. Also not done here, deferred to future sub-issues: the partial-rejection/HTTP-413 batch-halving handling and a Python openai-agents adapter (both already deferred by #360, out of scope for #100), and adopting the SDK's newer per-event sessionId override (added by #343) in place of this processor's one-AgentViewer-per-session workaround (a behavior change, not a packaging one).
  • OTLP/HTTP GenAI traces receiver: POST /v1/traces (issue #97, v0.7.0 milestone): converts OpenTelemetry GenAI spans (gen_ai.* semantic conventions, for example OpenLLMetry/Traceloop) into canonical llm.usage/llm.failed and tool.started/tool.completed/tool.failed events, reusing the shared OTLP body parsing, gzip, auth and rate-limit plumbing /v1/logs//v1/metrics already mounted (#59/#73). It never stores spans: only the derived events reach store.appendBatch. A new pure mapper, src/integrations/otlpTraces.ts (mapOtlpTraces), is exported at the top level of src/integrations so issue #98 (OTLP trace file import) can reuse it; it is excluded from the embeddable library bundle. Deterministic ids (otlp:{traceId}:{spanId}:llm / :tool.started / :tool.end) make retries idempotent; cross-source dedup with the SDK/webhook path relies on the existing (provider, requestId) key (#47/#48), filled from gen_ai.response.id. invoke_agent/create_agent/other GenAI operations are never turned into events (used only to resolve an agent name for their descendants, walked up to 32 hops, cycle-safe); only gen_ai.* attributes named in docs/otlp.md's mapping table are ever read, so prompts, completions, tool arguments/results and span events/links are never stored, broadcast or logged. Token counts, latency and cost are never defaulted to 0 when absent (the X-Agent-Viewer-OTLP response header reports accepted/duplicates/ignored/rejected/warnings so a dropped or malformed attribute is visible instead of silently zeroed). canonicalContract.ts gains a spanId correlation field (on llm.usage/llm.failed and the three tool.* payloads), which issue #64 had not added. Adds tests/otlp-mapping.test.mjs (pure mapper) and tests/otlp-traces.test.mjs (route: auth, limits, http/json and http/protobuf, idempotency, cross-source dedup, privacy). Deferred to a follow-up: the exhaustive tests/fixtures/otlp/* trace fixture library, a real-OTel-SDK-exporter integration test (new dev dependencies), the property-based re-mapping fuzz test, and the agent-viewer start banner line for /v1/traces (none of these are required by this item's own acceptance criteria).
  • OTLP trace file import, reusing the #97 mapper (issue #98, v0.7.0 milestone): parseEventLog (src/integrations/eventLogParser.ts) now converts a resourceSpans OTLP/JSON export (single document, pretty-printed, or one request per NDJSON line, including mixed with canonical lines or other OTLP signals) into llm.usage/llm.failed/tool.* events by calling the exact same mapOtlpTraces the live POST /v1/traces receiver uses, so the file path and the live path can never disagree about a run. The removed otlp-traces-not-supported issue code (and its drop.otlpTraces demo message, English and Spanish) is gone: a traces file with no convertible spans now reports nothing (parity with a zero-record logs file), and a GenAI span missing usage is rejected with otlp-record-skipped plus the existing otlp-no-usage-records issue when nothing in the file converts, same as logs. The EventLogOtlpSummary.logRecords/converted/skipped/rejected counters (issue #74, already released) now also count trace spans under their original names, kept for backward compatibility; a tool span's started+completed pair still counts as one converted span. Updated README.md/docs/README.es.md (maturity table, roadmap, "What it is not"), docs/event-log.md and docs/library.md/.es.md. Deferred: a non-blocking "N spans skipped" count in the demo app's success state (the demo already shows the real issue text on zero events; the per-signal running-tally UI was not added for logs either) and an explicit duplicateEvents/usageGapSpans breakdown beyond the existing shared counters.
  • TypeScript SDK: optional durable local queue / spool (issue #102): ported into packages/sdk/src/index.ts (issue #343's package) instead of the old sdk/typescript/index.ts, which only re-exports it. With spoolDir (or AGENT_VIEWER_SPOOL_DIR) set, emit()/emitBatch() write an event that failed transiently (network error, timeout, 408, 429, 5xx) to a bounded SQLite (node:sqlite) spool instead of throwing, and replay it in order once the server is back; adds flush(), close(), spoolStats(), spoolRejected(), AgentViewerError.transient, and the exported computeSpoolFileName/isTransientHttpStatus/TransientNetworkError. postWithRetry now also retries 408. Without a spool directory, behavior is unchanged. Contract in docs/sdk-queue.md; tests in tests/sdk-typescript-queue.test.mjs.
  • LangGraph adapter: release plumbing and docs (issue #346, last sub-issue of #99, v0.7.0 milestone): .github/workflows/release.yml gains a release-sdk job that publishes @warlockcode/agent-viewer-sdk (skipped when that version is already on npm) and a release-langgraph job that publishes @warlockcode/agent-viewer-langgraph after it, both with npm publish --provenance --access public --ignore-scripts and the same skip-on-prerelease and skip-without-NPM_TOKEN pattern as the other packages. The release job now also fails when packages/langgraph/package.json does not match the release tag. The Python handler (agent_viewer_langgraph, issue #345) already ships in the PyPI distribution as the langgraph extra. New runnable example examples/langgraph/ (a two-node graph with a fake chat model and a local tool, no provider or API key) using the real package. New guide docs/adapters/langgraph.md (and .es.md), new packages/langgraph/README.md, LangGraph rows and a 📦 legend entry in the README.md/docs/README.es.md maturity tables, roadmap line, and docs/integration.md section 10.
  • Prometheus /metrics: in-process ingestion counters (issue #365, v0.7.0 milestone): agent_viewer_events_accepted_total{type}, agent_viewer_events_duplicate_total{kind=identical|conflicting}, agent_viewer_events_rejected_total{endpoint,reason} and agent_viewer_http_request_duration_seconds (histogram, labels method/route/status_class). A middleware mounted first (before express.json) hooks res.on('finish'); every refusal site across /api/v1/events, /api/v1/events/batch, the generic webhook and the OTLP logs/metrics/traces receivers records its reason (validation, empty_batch, batch_too_large, malformed_json, payload_too_large, unrecognized_payload, unauthorized, rate_limited, signature, replay), and /metrics's own 401/403/429 count under endpoint="metrics". /metrics itself and the SSE stream route are excluded from the duration histogram. docs/metrics.md gains the "In-process ingestion counters" section. New tests/metrics-ingestion.test.mjs.
  • Prometheus /metrics: ledger-derived metric families (issue #348, sub-issue of #103, v0.7.0 milestone): adds the token/cost/call/failure/latency families to the /metrics endpoint shipped by #350. New server/metrics/ledgerFamilies.ts (computeLedgerFamilies, built on the existing store.rollup() call used to pin the ledger's current end-of-sequence and global oldest/newest received_at, plus one pass over store.iterateExportRows(), the same streaming primitive GET /api/v1/usage/export already uses) and server/metrics/ledgerFamiliesCache.ts (LedgerFamiliesCache: AGENT_VIEWER_METRICS_CACHE_MS, default 5000ms, 0 disables; concurrent scrapes share one in-flight computation; a failing ledger query is never cached). New families: agent_viewer_tokens_total, agent_viewer_token_kind_unreported_calls_total, agent_viewer_cost_reported_total, agent_viewer_cost_estimated_total, agent_viewer_cost_unknown_calls_total, agent_viewer_llm_calls_total, agent_viewer_llm_failures_total, agent_viewer_llm_latency_seconds (histogram, buckets 0.1..120,+Inf), agent_viewer_ledger_retained_since_seconds, agent_viewer_ledger_last_usage_timestamp_seconds. Cardinality cap AGENT_VIEWER_METRICS_MAX_MODELS (default 100): the first N (provider, model) pairs by first appearance in the ledger keep their own series, the rest fold into provider="__other__",model="__other__"; label sanitize-then-merge happens before grouping. A failing ledger query answers 503 {"error":"metrics_unavailable"}, never a partial body, and is not cached. docs/metrics.md gains the "Ledger-derived metrics" section (full catalog, cardinality/scrape-cost/retention/memory-mode notes, PromQL examples); README.md/docs/README.es.md gain the two new environment variables and an updated /metrics row. New tests/metrics-ledger.test.mjs (tokens by kind and unreported counting, mixed/missing currency, reported vs. estimated cost, unknown-cost counting, calls/failures by error_kind, the latency histogram, the cardinality cap, label sanitize-then-merge, the two ledger gauges, memory-mode ring-buffer independence, a SQLite close/reopen, the cache's TTL/concurrency/failure rules, and the HTTP route's 503).

Removed

  • examples/langgraph-adapter.ts (issue #346): the old hand-written LangGraphViewerAdapter example, never wired into LangChain's callback system, is replaced by the @warlockcode/agent-viewer-langgraph package (and examples/langgraph/). Its case in tests/examples-adapters.test.mjs is removed; the package is covered by tests/langgraph-adapter.test.mjs. To migrate, drop the manual onNodeStart/onToolStart/onModelUsage calls and pass the handler in the graph config:

    - import { LangGraphViewerAdapter } from './langgraph-adapter';
    + import { AgentViewerCallbackHandler } from '@warlockcode/agent-viewer-langgraph';
    + await graph.invoke(inputs, { callbacks: [new AgentViewerCallbackHandler({ runtimeId: 'my-graph' })] });
  • examples/openai-agents-adapter/ (issue #104): dead code left behind when issue #361 moved this adapter into the published workspace package. Nothing imported it (tests/examples-adapters.test.mjs's own doc comment already said the case was removed in favor of tests/openai-agents-adapter.test.mjs), so it only duplicated packages/openai-agents/src/processor.ts with a stale doc comment. To migrate, import from the package instead:

    - import { AgentViewerTracingProcessor } from './openai-agents-adapter/processor';
    + import { AgentViewerTracingProcessor } from '@warlockcode/agent-viewer-openai-agents';