@ramarivera/coding-agent-langfuse
v0.2.2
Published
Universal coding-agent Langfuse backfiller and live OTLP helpers, built on Effect
Readme
coding-agent-langfuse
Universal coding-agent Langfuse backfiller and OTLP exporter helpers, built on Effect.
v0.2 — Effect rewrite. As of
0.2.0this package is implemented on Effect and@effect/platformwith an@effect/clicommand surface. The CLI flags, env vars, and on-the-wire OTLP output are unchanged, but it now depends oneffect,@effect/platform,@effect/platform-node, and@effect/cliat runtime (it is no longer zero-dependency). See Architecture below.
It imports local histories from Codex, Claude Code, Grok, OpenCode, and Pi into
Langfuse as session traces with child observations. LLM generations include
Langfuse canonical usage_details and cost_details attributes so historical
backfills participate in Langfuse model-usage and cost dashboards. Tool calls
remain child spans under the same session.
Codex event_msg token_count rows are imported as cost-accounting
generations when they include input/output/cache buckets for a known priced
model. The importer preserves the original token snapshot in metadata and sends
Langfuse canonical usage_details/cost_details derived from the bucketed
usage. Total-only Codex snapshots and unknown/free-preview models stay
non-billable to avoid inventing cost from ambiguous telemetry.
coding-agent-langfuse-backfill --agents codex,claude,grok,pi,opencodeUse the public Langfuse OTLP hostname for real imports from any host:
npx @ramarivera/coding-agent-langfuse@latest \
--agents claude,codex,grok,pi,opencode \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces \
--state "$HOME/.local/state/coding-agent-langfuse/backfill-v6.json" \
--batch-size 10For a Langfuse endpoint that requires project API keys, pass Basic auth
credentials as publicKey:secretKey or set LANGFUSE_BACKFILL_AUTH:
npx @ramarivera/coding-agent-langfuse@latest \
--agents claude,codex,grok,pi,opencode \
--endpoint http://127.0.0.1:3000/api/public/otel/v1/traces \
--auth pk-lf-example:sk-lf-exampleRun live incremental forwarding without putting inference behind a gateway:
npx @ramarivera/coding-agent-langfuse@latest \
--agents codex \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/traces \
--state "$HOME/.local/state/coding-agent-langfuse/live-codex.json" \
--batch-size 10 \
--followThe importer retries transient OTLP POST failures before aborting. Defaults are
three retries with a one-second linear backoff; tune with --post-retries,
--post-retry-delay-ms, LANGFUSE_BACKFILL_POST_RETRIES, or
LANGFUSE_BACKFILL_POST_RETRY_DELAY_MS. If retries are exhausted, the run
prints the real network cause and preserves local state.
Project tags and metadata
Backfills and live followers can attach stable Langfuse dimensions based on the session cwd. Use this for client/workstream dashboards without guessing from host names or Windows paths in the Langfuse UI.
There are two config layers:
- Global host config:
~/.config/coding-agent-langfuse/path-tags.json - Optional project-local overlays: every
.langfuse-ca.jsonfound while walking from the session cwd up to the scanned home directory
Global config uses path-prefix or Git-remote rules:
{
"rules": [
{
"pathPrefix": "/Users/example/dev/acme",
"gitRemoteIncludes": ["github.com/acme"],
"tags": ["acme", "client:acme"],
"metadata": {
"project_group": "acme",
"project_owner": "platform-team"
},
"projectName": "acme",
"projectFolder": "acme"
}
]
}Use pathPrefix for stable checkout roots. Use gitRemoteIncludes for
temporary agent worktrees, where the cwd may live under a random directory such
as .codex/worktrees/<id>/repo but the Git remote still identifies the real
project.
Project-local config is intentionally smaller and overrides/extends the matched
global rule. Parent overlays are applied before child overlays, so a config at
~/work/client-a/.langfuse-ca.json can tag a whole workstream while nested
repos add repo-specific fields:
{
"tags": ["repo:portal"],
"metadata": {
"service": "portal"
},
"projectName": "acme-portal"
}Matched values are emitted on root trace metadata, observation metadata, and
top-level OTLP attributes such as project.tags, project.group, and
project.owner. When the session cwd is inside a Git repository, the exporter
also emits git.worktree.path, git.branch, and git.commit plus matching
Langfuse metadata fields. Trace tags are emitted from both the session root and
tagged child spans, so long sessions that move into a tagged cwd still repair
the Langfuse trace tag used by custom dashboards. Historical repair runs use the
same OTLP builder as live follow mode, so rerunning with --force can backfill
newly added tags and Git metadata for existing sessions.
Use a non-default global config path with:
npx @ramarivera/coding-agent-langfuse@latest \
--path-tags-config "$HOME/.config/coding-agent-langfuse/path-tags.json"Generated services keep their command small and rely on the package's default
global config plus upward project-local discovery. Use --path-tags-config only
for one-off CLI runs that need a non-default global config path.
Cost calculation
Backfill cost calculation follows Langfuse's OpenTelemetry mapping: generation
spans receive langfuse.observation.usage_details and
langfuse.observation.cost_details JSON attributes. If a source history already
records a total cost, that recorded value wins. Otherwise, the importer
calculates per-usage-type USD costs from a model catalog using rates in USD per
1M tokens.
By default, the importer refreshes model pricing from https://models.dev/api.json
and caches it locally. The cache is used for both one-shot backfills and
--follow services, so long-running collectors do not hit the network for every
scan. The built-in catalog remains only as an offline fallback for known local
models; explicit --cost-rates and --cost-rates-json overrides always win.
Control the pricing cache with:
npx @ramarivera/coding-agent-langfuse@latest \
--models-dev-cache "$HOME/.cache/coding-agent-langfuse/models-dev-v1.json" \
--models-dev-ttl-ms 86400000Use --models-dev to force-enable the cache if an environment disabled it. Use
--no-models-dev or CODING_AGENT_LANGFUSE_MODELS_DEV=0 for fully static
offline pricing. Advanced deployments can set --models-dev-url,
CODING_AGENT_LANGFUSE_MODELS_DEV_CACHE,
CODING_AGENT_LANGFUSE_MODELS_DEV_TTL_MS, or
CODING_AGENT_LANGFUSE_MODELS_DEV_FETCH_TIMEOUT_MS.
When a billable generation source only records a total token count without
input/output/cache breakdown, the importer charges that total at the model input
rate and marks the cost source as calculated_total_as_input.
Codex token-count snapshots are the exception: they are charged only when the
snapshot has explicit input/output/cache buckets. Codex reasoning tokens are
kept in metadata, but not charged separately because Codex token-count output
totals already include reasoning tokens.
Use an override only when you intentionally want a different accounting policy:
npx @ramarivera/coding-agent-langfuse@latest \
--agents codex \
--cost-rates-json '{"gpt-5.5":{"input":1,"output":2,"cacheRead":0.1,"cacheWrite":0}}'You can also keep the policy in a JSON file and pass --cost-rates PATH, or set
CODING_AGENT_LANGFUSE_COST_RATES_PATH /
CODING_AGENT_LANGFUSE_COST_RATES_JSON for both manual backfills and generated
services. A file can be either a direct model map or { "rates": { ... } }:
{
"rates": {
"gpt-5.5": {
"input": 1,
"output": 2,
"cacheRead": 0.1,
"cacheWrite": 0,
"cacheWrite5m": 0,
"cacheWrite1h": 0
}
}
}Follow as a host service
Install a live follower directly from npm. The generated service keeps inference outside any gateway: agents keep calling their normal providers, while this tool tails local histories and posts Langfuse OTLP traces.
Preview the service without touching the host:
npx @ramarivera/coding-agent-langfuse@latest service print \
--platform linux \
--agents codex,pi \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/tracesInstall and start it on the current host:
npx @ramarivera/coding-agent-langfuse@latest service install \
--agents codex,pi \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/tracesThe service installer supports:
- macOS: LaunchAgent under
~/Library/LaunchAgents - Linux: systemd user unit under
~/.config/systemd/user - Windows: scheduled task installer script under
%APPDATA%\\coding-agent-langfuse
Use --dry-run to print the exact file and commands, --no-start to only write
the service file, service status to inspect the platform registration, and
service uninstall to remove it.
npx @ramarivera/coding-agent-langfuse@latest service status \
--agents codex,piThe Windows path intentionally uses a per-user Scheduled Task for the default
npx workflow. A true Windows SCM service needs an installed wrapper or native
service binary; raw sc.exe is not a good fit for a transient npm command.
The generated services are package-manager agnostic. By default the installer
uses npx through the service PATH (/usr/bin/env npx on macOS and Linux)
with a conservative cross-platform PATH. Use --npx-path and --path when a
host keeps Node.js somewhere outside the normal npm/Homebrew/system locations,
including nvm, fnm, Volta, asdf, mise, or another shell manager.
The public API exposes serviceCreators for macOS LaunchAgent, Linux systemd
user units, and Windows Scheduled Task scripts, plus agentProcessors for
Claude Code, Codex, Grok, OpenCode, and Pi. These registries are the extension
points for adding another host service target or coding-agent history reader
without adding more platform or agent conditionals to the CLI internals.
Backfill windows
Backfill only a timeframe when repairing a host or replaying a recent window:
npx @ramarivera/coding-agent-langfuse@latest \
--agents claude,codex,grok,pi,opencode \
--since 2026-05-31T00:00:00Z \
--until 2026-06-01T00:00:00Z \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/tracesDeduplication is state-file based and keyed by importer state identity, agent,
session id, and source record id. Reuse the same --state path for normal
incremental runs.
For an intentional repair replay, add --force to resend the selected window
even when the state file says those events were already sent:
npx @ramarivera/coding-agent-langfuse@latest \
--agents claude,codex,grok,pi,opencode \
--since 2026-05-01T00:00:00Z \
--until 2026-06-01T00:00:00Z \
--force \
--endpoint https://langfuse.ai.roxasroot.net/otel/v1/tracesThe Langfuse trace/span IDs intentionally stay pinned to the original pre-cost identity, while the state-file key can advance with importer payload changes. That lets cost repairs replace historical zero-cost rows instead of creating a new duplicate identity for the same source event.
Architecture
The package is organized as a small Effect application:
src/domain.ts—Schemadomain (AgentName,Usage,CostRates,CostCatalog,BackfillEvent) andsrc/errors.ts—Schema.TaggedErrorfailures (OtlpPostError,SingleEventTooLargeError,CostRatesError, …).src/internal/*— pure, side-effect-free transforms: cost math, OTLP serialization, the per-agent row→event parsers, path-tag matching, dedupe pruning, and OS-service template rendering.src/services/*—Effect.Servicelayers over@effect/platform:AgentSources(FileSystem + Command/sqlite discovery),CostCatalogService(FileSystem + HttpClient + Clock for models.dev),ProjectMetadata(Command/git + FileSystem, memoized viaRef),DedupeStateStore(atomic state load/prune/save),OtlpExporter(HttpClient POST withScheduleretry on 429/5xx/network),Backfill(orchestrates discovery → dedupe → batching → export + follow),OsService(installs the systemd/launchd/Windows follower service).
src/cli.ts— the@effect/clicommand plus theservicesubcommand, run viaNodeRuntime.runMainwithNodeContext+NodeHttpClientlayers.
Tooling: TypeScript with the @effect/language-service plugin, Biome for lint +
format, and Vitest (@effect/vitest) for tests.
Verification
The test suite covers the pure parsers and cost math, the Effect services (via
@effect/vitest test layers), and exercises the built CLI against a local OTLP
collector to assert on-the-wire spans, usage, and cost details.
npm run lint # Biome check + format
npm run check # tsc --noEmit
npm test # unit + service tests (Vitest)
npm run test:e2e # builds, then runs the local-collector e2eThe e2e suite verifies:
- A Codex session imported end-to-end through the built CLI, asserting the
generation observation,
usage_details, and computedcost_detailson the wire. - Incremental
--followscans skipping files unmodified since the last scan. - Dedupe-state pruning of stale identity-version keys.
- Service plan generation (auth injected as an env var, never argv) for Linux systemd user units, macOS LaunchAgents, and Windows Scheduled Tasks.
