npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pdbr/opencode-plugin-langfuse

v2026.5.3

Published

Langfuse observability plugin for OpenCode - LLM tracing and cost tracking via OpenTelemetry

Downloads

720

Readme

OpenCode Langfuse Plugin

npm version License: MIT

Automatic LLM observability for OpenCode using Langfuse via OpenTelemetry.

Zero-config tracing of sessions, messages, tool calls, costs, and performance.


Installation

bun add @pdbr/opencode-plugin-langfuse
# or
npm install @pdbr/opencode-plugin-langfuse

Setup

1. Get Langfuse Credentials

Sign up at cloud.langfuse.com and create a project.

Go to Settings → API Keys and copy your keys.

2. Configure Environment

export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASEURL="https://cloud.langfuse.com"  # Optional

3. Enable Plugin + OTEL

In .opencode/opencode.json:

{
  "experimental": {
    "openTelemetry": true
  },
  "plugin": ["@pdbr/opencode-plugin-langfuse"]
}

4. Run OpenCode

That's it! All traces appear automatically in your Langfuse dashboard.


How It Works

This plugin initializes a LangfuseSpanProcessor that captures all OpenTelemetry spans emitted by OpenCode when experimental.openTelemetry is enabled.

OpenCode (OTEL spans) → LangfuseSpanProcessor → Langfuse Dashboard

Cost hydration from OpenCode

OpenCode's OTel telemetry attaches token counts and the model name to spans but not cost — cost is computed locally inside OpenCode (using its bundled pricing tables and your custom-model overrides). For models that aren't in Langfuse Cloud's built-in pricing table (custom proxies, rotating gateway models, self-hosted endpoints), generations would otherwise show up with tokens but cost = $0.

The plugin closes that gap by setting Langfuse's documented OTel attribute langfuse.observation.cost_details on the AI SDK generation span before it exports, so the span ships once with cost already in place:

  1. The wrapper buffers every inner generation span the AI SDK emits — names matching ^ai\.(generateText|streamText|generateObject|streamObject)\.do(Generate|Stream)$ with a non-empty ai.telemetry.metadata.sessionId attribute — into a per-sessionId FIFO. Non-generation spans (outer wrappers, tool calls, anything else) are forwarded to the inner LangfuseSpanProcessor immediately.
  2. On OpenCode's message.updated event, the plugin computes the per-step cost delta (info.cost - lastSeenCost), pops the oldest buffered span for that session, mutates span.attributes['langfuse.observation.cost_details'] = '{"total":Δcost}', then forwards to the inner processor.
  3. If no message.updated arrives within LANGFUSE_COST_HYDRATION_TIMEOUT_MS (default 10s), the buffered span is flushed uncosted — tokens still arrive via OTel.

OpenCode runs steps strictly serially per session, so the k-th message.updated after a message starts maps to the k-th buffered generation span ended after that message started. No extra correlation key is needed at the plugin layer.

Behaviour is fully defensive:

  • cost === 0, null, undefined, or NaN → no cost attached; buffered span flushes uncosted at the timeout. Local/free models still get their tokens via OTel.
  • No buffered span matches when message.updated fires → silently logged at info and skipped.
  • Missing Langfuse credentials → none of this code runs (plugin returns {} early, same as before).

Known limitations:

  • If OpenCode sub-agents ever share a parent session ID, the FIFO may attribute the wrong cost slice to a peer span in the same session. As of current OpenCode, sub-agents run in their own sessions, so this is theoretical for v1.
  • A plugin reload mid-session resets the cost map; the first message.updated after reload treats the running cost as the delta. Slight over-attribution on that single step; subsequent steps are correct.

Trace stitching

When OpenCode is invoked as a sub-step of a larger AI workflow, you can stitch its spans into the caller's existing Langfuse trace by setting two environment variables before launching opencode:

  • LANGFUSE_TRACE_ID (32-char hex) — reuse this trace ID for all spans the plugin emits.
  • LANGFUSE_PARENT_OBSERVATION_ID (16-char hex, requires the above) — nest every root span under this parent observation.
caller's trace ──┐
                 └── parent observation
                       └── OpenCode session
                             ├── tool: read
                             ├── tool: edit
                             └── llm: generation

Design ported from upstream PR omercnet/opencode-plugin-langfuse#17.


Environment Variables

| Variable | Required | Default | Description | | -------------------------------- | -------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------- | | LANGFUSE_PUBLIC_KEY | Yes | - | Langfuse public key | | LANGFUSE_SECRET_KEY | Yes | - | Langfuse secret key | | LANGFUSE_BASEURL | No | https://cloud.langfuse.com | Self-hosted instance | | LANGFUSE_DISPOSE_FLUSH_MS | No | 8000 | Bounded timeout (ms) for the final span flush when the OpenCode server is disposed; prevents Bun OTLP keep-alive hangs | | LANGFUSE_COST_HYDRATION_TIMEOUT_MS | No | 10000 | Max time (ms) a generation span is buffered while waiting for the matching message.updated. On timeout it ships uncosted. | | LANGFUSE_TRACE_ID | No | - | 32-char hex; reuse this trace ID for all spans (caller-set) | | LANGFUSE_PARENT_OBSERVATION_ID | No | - | 16-char hex; nest spans under this parent observation (caller-set, requires LANGFUSE_TRACE_ID) |


Self-Hosting

export LANGFUSE_BASEURL="https://langfuse.yourcompany.com"

See Langfuse self-hosting docs.


Troubleshooting

No traces appearing

  1. Verify experimental.openTelemetry: true is set
  2. Check credentials: echo $LANGFUSE_PUBLIC_KEY
  3. Check Langfuse health: curl https://cloud.langfuse.com/api/public/health

Plugin not loading

  • Ensure @pdbr/opencode-plugin-langfuse is in dependencies (not devDependencies)
  • Verify .opencode/opencode.json syntax

License

MIT © pdbr


Related