Repository navigation
v0.7.0
[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=..., TypeScriptspoolDir/AGENT_VIEWER_SPOOL_DIR) is opt-in:
without it, both SDKs behave exactly as before (an unrecovered transient failure still raises/throws). GET /metricsis opt-in (AGENT_VIEWER_METRICS=1, CLI--metrics); unset, the route answers404
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-agentsinstead 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=..., stdlibsqlite3only, no third-party dependency) so a transient failure (connection down, reset, timeout,408,429, any5xx) after the normal retries never raises into the host agent. Events are written to disk with their originalidandtimestampand replayed in enqueue order once the server returns, throughPOST /api/v1/events/batch, so the server's own idempotency byidkeeps totals honest. NewAgentViewerarguments (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 anatexitfinal 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 inSpoolStats, never silently lost, and the spool never sums, prices or defaults usage (a missingcoststays absent, never0). Also fixes transient-failure classification with the spool disabled: a rawOSError/http.client.HTTPExceptionand HTTP408are now retried instead of escaping or failing immediately. Newdocs/sdk-queue.md(shared contract with the TypeScript SDK, #102) andtests/fixtures/sdk-queue-scenarios.json(shared test vectors). Newtests/test_python_sdk_spool.py(34 tests: outages, restart persistence, overflow matrix, poison events,413/429/401handling, 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
sessionIdonemit()/emitBatch()anddurationMsontoolCompleted()/toolFailed()(first sub-issue of #99, v0.7.0 milestone): prerequisite for the packaged LangGraph adapter (#99), which serves manythread_ids through one process and builds events through the low-levelemit/emitBatchpath, not through a per-sessionAgentViewerinstance. TypeScript'sEmitEventInput.sessionId?(sdk/typescript/index.ts) now overrides the client's ownsessionIdfor that one event in bothemit()andemitBatch()(previously hardcoded tothis.sessionId, ignoring any such field on the input); Python'sAgentViewer.emit()gains the samesession_id/timestamp_mskeyword argumentsemit_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(toolCallIdplusdurationMs) is the new option type fortoolCompleted()/toolFailed()(TypeScript) andduration_msthe new keyword fortool_completed()/tool_failed()(Python), matching the contract:durationMsis valid onToolCompletedPayloadSchema/ToolFailedPayloadSchemabut notToolStartedPayloadSchema, sotoolStarted()/tool_started()do not accept it. New tests intests/sdk-typescript.test.mjsandtests/test_python_sdk.pycover the per-event session override and the duration field landing only on completed/failed. Deferred to later sub-issues of #99: thepackages/sdknpm 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 rootpackage.jsonnow declares"workspaces": ["packages/*"], and the logic that used to live only atsdk/typescript/index.tsmoved to the new workspace package@warlockcode/agent-viewer-sdk(packages/sdk/src/index.ts), with no runtime or type import ofsrc/integrations/*orzod: every structural type it needs (CanonicalEvent,CanonicalEventType,EventSeverity,LlmErrorKind, thellm.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.tsreferences no file outside the package.sdk/typescript/index.tsis now a thin re-export of the package, sotests/sdk-typescript.test.mjsand every example adapter keep working unchanged. A new CI type test (tests/types/sdk-package-canonical-event.ts, checked bynpm run lint) asserts the package'sCanonicalEvent/CanonicalEventType/EventSeverity/LlmErrorKindstay assignable to the root contract's types in both directions.packages/sdk/package.jsonhasexports,files,publishConfig.access: public, is ESM only, has its owntsconfig.jsonemitting declarations, and acheck:packagescript runningpublint --strictandattw --pack .(also runnable at the root asnpm run check:package:sdk). The rootnpm run build:packages(npm run build --workspaces --if-present) builds every workspace package and now runs automatically beforenpm run lintandnpm test(prelint/pretest), since thesdk/typescript/index.tsre-export needspackages/sdk/distto exist to resolve the package by name.docker/Dockerfile'sdepsstage now also copiespackages/sdk/package.jsonbeforenpm 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 packthe package, install the.tgzinto a throwaway ESM project, import it).packages/sdk/README.mddocuments 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.tsmove, unchanged in behavior, to the new sibling workspace package@warlockcode/agent-viewer-openai-agents(packages/openai-agents/src/), reusingpackages/sdk's exact layout (issue #343) rather than inventing a new convention: sametsconfig.jsonshape, sameexports/files/publishConfigfields, samecheck:package(publint --strict && attw --pack . --profile esm-only), no second"workspaces"field at the root. The processor now importsAgentViewer/EmitEventInputfrom the published@warlockcode/agent-viewer-sdkpackage instead of the repo-relativesdk/typescript/index.tspath;@openai/agentsis apeerDependenciesentry (the host brings its own version). Deviates from issue #361's own text in one place, documented inpackages/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 regulardependenciesentry instead (an internal implementation detail, not a version the host needs to pin like@openai/agents).tests/openai-agents-adapter.test.mjsnow imports the processor from its new path; its test suite, assertions and thetests/examples-adapters.test.mjsdoc comment are otherwise unchanged. Rootbuild:packagesnow buildspackages/sdkbeforepackages/openai-agentsexplicitly (npm run build --workspaceshas no dependency-graph ordering, and this package's.tssources import the sdk package by name, needing itsdist/first); a newnpm run check:package:openai-agentsscript, a CI step for it, and a packed-tarball smoke test (installing both this package's andpackages/sdk's local tarballs together, since neither is on the public npm registry yet, plus the real@openai/agents) are added alongside the existingpackages/sdkones..github/workflows/release.ymlgains arelease-openai-agentsjob (needsrelease) publishing@warlockcode/agent-viewer-openai-agentswith provenance after the main package, same skip-without-NPM_TOKEN/skip-on-prerelease pattern, plus a version-lockstep check against the rootpackage.json(a direct comparison here, not an extension ofscripts/release-rules.mjs, which does not yet know about workspace packages). Known limitation, same aspackages/sdk's own publish gap (#346): this package's one real dependency,@warlockcode/agent-viewer-sdk, is not yet published to npm, so a realnpm installof this package outside this monorepo will not resolve until #346 ships it; the release job above is wired and will skip cleanly today (noNPM_TOKENconfigured), 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 Pythonopenai-agentsadapter (both already deferred by #360, out of scope for #100), and adopting the SDK's newer per-eventsessionIdoverride (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 canonicalllm.usage/llm.failedandtool.started/tool.completed/tool.failedevents, reusing the shared OTLP body parsing, gzip, auth and rate-limit plumbing/v1/logs//v1/metricsalready mounted (#59/#73). It never stores spans: only the derived events reachstore.appendBatch. A new pure mapper,src/integrations/otlpTraces.ts(mapOtlpTraces), is exported at the top level ofsrc/integrationsso 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 fromgen_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); onlygen_ai.*attributes named indocs/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 to0when absent (theX-Agent-Viewer-OTLPresponse header reportsaccepted/duplicates/ignored/rejected/warningsso a dropped or malformed attribute is visible instead of silently zeroed).canonicalContract.tsgains aspanIdcorrelation field (onllm.usage/llm.failedand the threetool.*payloads), which issue #64 had not added. Addstests/otlp-mapping.test.mjs(pure mapper) andtests/otlp-traces.test.mjs(route: auth, limits, http/json and http/protobuf, idempotency, cross-source dedup, privacy). Deferred to a follow-up: the exhaustivetests/fixtures/otlp/*trace fixture library, a real-OTel-SDK-exporter integration test (new dev dependencies), the property-based re-mapping fuzz test, and theagent-viewer startbanner 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 aresourceSpansOTLP/JSON export (single document, pretty-printed, or one request per NDJSON line, including mixed with canonical lines or other OTLP signals) intollm.usage/llm.failed/tool.*events by calling the exact samemapOtlpTracesthe livePOST /v1/tracesreceiver uses, so the file path and the live path can never disagree about a run. The removedotlp-traces-not-supportedissue code (and itsdrop.otlpTracesdemo 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 withotlp-record-skippedplus the existingotlp-no-usage-recordsissue when nothing in the file converts, same as logs. TheEventLogOtlpSummary.logRecords/converted/skipped/rejectedcounters (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. UpdatedREADME.md/docs/README.es.md(maturity table, roadmap, "What it is not"),docs/event-log.mdanddocs/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 explicitduplicateEvents/usageGapSpansbreakdown 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 oldsdk/typescript/index.ts, which only re-exports it. WithspoolDir(orAGENT_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; addsflush(),close(),spoolStats(),spoolRejected(),AgentViewerError.transient, and the exportedcomputeSpoolFileName/isTransientHttpStatus/TransientNetworkError.postWithRetrynow also retries 408. Without a spool directory, behavior is unchanged. Contract indocs/sdk-queue.md; tests intests/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.ymlgains arelease-sdkjob that publishes@warlockcode/agent-viewer-sdk(skipped when that version is already on npm) and arelease-langgraphjob that publishes@warlockcode/agent-viewer-langgraphafter it, both withnpm publish --provenance --access public --ignore-scriptsand the same skip-on-prerelease and skip-without-NPM_TOKENpattern as the other packages. The release job now also fails whenpackages/langgraph/package.jsondoes not match the release tag. The Python handler (agent_viewer_langgraph, issue #345) already ships in the PyPI distribution as thelanggraphextra. New runnable exampleexamples/langgraph/(a two-node graph with a fake chat model and a local tool, no provider or API key) using the real package. New guidedocs/adapters/langgraph.md(and.es.md), newpackages/langgraph/README.md, LangGraph rows and a 📦 legend entry in theREADME.md/docs/README.es.mdmaturity tables, roadmap line, anddocs/integration.mdsection 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}andagent_viewer_http_request_duration_seconds(histogram, labelsmethod/route/status_class). A middleware mounted first (beforeexpress.json) hooksres.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 underendpoint="metrics"./metricsitself and the SSE stream route are excluded from the duration histogram.docs/metrics.mdgains the "In-process ingestion counters" section. Newtests/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/metricsendpoint shipped by #350. Newserver/metrics/ledgerFamilies.ts(computeLedgerFamilies, built on the existingstore.rollup()call used to pin the ledger's current end-of-sequence and global oldest/newestreceived_at, plus one pass overstore.iterateExportRows(), the same streaming primitiveGET /api/v1/usage/exportalready uses) andserver/metrics/ledgerFamiliesCache.ts(LedgerFamiliesCache:AGENT_VIEWER_METRICS_CACHE_MS, default 5000ms,0disables; 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, buckets0.1..120,+Inf),agent_viewer_ledger_retained_since_seconds,agent_viewer_ledger_last_usage_timestamp_seconds. Cardinality capAGENT_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 intoprovider="__other__",model="__other__"; label sanitize-then-merge happens before grouping. A failing ledger query answers503 {"error":"metrics_unavailable"}, never a partial body, and is not cached.docs/metrics.mdgains the "Ledger-derived metrics" section (full catalog, cardinality/scrape-cost/retention/memory-mode notes, PromQL examples);README.md/docs/README.es.mdgain the two new environment variables and an updated/metricsrow. Newtests/metrics-ledger.test.mjs(tokens by kind and unreported counting, mixed/missing currency, reported vs. estimated cost, unknown-cost counting, calls/failures byerror_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-writtenLangGraphViewerAdapterexample, never wired into LangChain's callback system, is replaced by the@warlockcode/agent-viewer-langgraphpackage (andexamples/langgraph/). Its case intests/examples-adapters.test.mjsis removed; the package is covered bytests/langgraph-adapter.test.mjs. To migrate, drop the manualonNodeStart/onToolStart/onModelUsagecalls 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 oftests/openai-agents-adapter.test.mjs), so it only duplicatedpackages/openai-agents/src/processor.tswith 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';