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

@pananfly/dsh-opencode-acp

v0.1.1

Published

DSH LLM adapter that routes a simulated opencode-acp provider through the opencode CLI's ACP server (persistent process, streamed thinking, effort levels)

Readme

@pananfly/dsh-opencode-acp

Repository: https://github.com/pananfly/dsh-opencode-acp · npm: @pananfly/dsh-opencode-acp

中文 | English

DSH LLM adapter that registers a simulated provider opencode-acp and routes every call through a persistent opencode acp daemon (line-delimited JSON-RPC over stdio). Pick it in the DSH model picker like any other provider — full agent turns (opencode's own tools), streamed thinking, thinking-level selection.

What it does

  • Simulated provider — registers ["opencode-acp"] via ctx.llm.registerAdapter; the picker lists it alongside real providers, nothing else changes
  • One persistent daemon — a single opencode acp --port 0 child process serves all conversations; lazily respawned if it dies, never spawned per-request
  • Streamed thinking — ACP agent_thought_chunk → harness reasoning blocks, agent_message_chunk → text blocks, so thinking renders live instead of arriving as a final result
  • Thinking levels — opencode model variants become the picker's effort options; reasoningEffort flows into session/set_config_option {configId:"effort"}
  • Model catalog — parsed once at startup from opencode models --verbose (names, context limits, reasoning variants), hot-reloadable without restarting dsh
  • Full delegation — tool execution happens inside opencode (its own bash/edit/read tools and permission config); DSH-side tools do not run during an opencode-acp turn

Architecture

DSH harness ── picks "opencode-acp/<model>" from the picker
   │  ctx.llm.registerAdapter(["opencode-acp"], adapter)
   ▼
OpencodeAcpAdapter (src/adapter.ts)
   conversation→ACP-session map (LRU ≤ 32) · incremental turn diffing
   history flattening · notification→chunk pump · cancel forwarding
   │  session/new · session/set_config_option · session/prompt
   │  session/cancel · session/close
   ▼
AcpConnection (src/acp.ts)
   line-delimited JSON-RPC 2.0 over stdio · initialize handshake
   lazy respawn · per-request timeout+abort · host-exit guard · stop escalation
   ▼
opencode acp --port 0        (one persistent child process)
   runs opencode's own agent loop and tools

| module | role | |---|---| | src/index.ts | plugin entry: binary discovery, catalog lifecycle, command + dispose registration, warm-up | | src/models.ts | parser for opencode models --verbose repeating sections (header + brace-balanced JSON) | | src/acp.ts | AcpConnection: stdio JSON-RPC client, process lifecycle | | src/adapter.ts | OpencodeAcpAdapter: duck-typed DSH LlmAdapter |

Key implementation points

  • Session mapping — one DSH conversation (options.sessionId) maps to one persistent ACP session. The first turn replays the whole flattened history as text parts ([system] / [assistant] / [called tool x] / [tool result] markers); later turns send only new messages (sentCount watermark). No per-turn process or session churn.
  • History-shrink rebuild — regenerate/edit shortens history below the watermark → the stale ACP session is closed and a fresh one replays everything.
  • Anonymous calls close themselves — a stream() without sessionId can never be hit again, so its daemon-side session is closed immediately in the generator's finally.
  • LRU session cap — named sessions are capped at 32; eviction closes the least-recently-used via session/close, never touching the active conversation or a session with an in-flight turn.
  • Concurrency guard — a second stream() on the same conversation while one is streaming fails fast with TURN_IN_PROGRESS instead of corrupting the shared ACP session.
  • Streaming pumpsession/prompt is dispatched concurrently (never awaited first); session/update notifications are queued into a per-session turn and drained by an async generator with condition-variable waiters; block-start is emitted once per block kind so the UI gets clean reasoning/text blocks.
  • Cancel & error mapping — abort signal forwards session/cancel; stopReason maps max_tokens/refusal/cancelled to MAX_TOKENS/CONTENT_FILTER/ABORTED.
  • Process hygieneprocess.once("exit") SIGKILL guard prevents orphaned daemons when the host dies; stop() closes stdin (EOF) then SIGTERM, escalating to SIGKILL after 3s with a 4s hard resolve cap; ensure() waits for a pending stop before respawning (no double-daemon window).
  • Request robustness — every request has a timeout (30s default, 600s for session/prompt), abort listeners are always removed, write failures reject the waiter, and undelimited stdout output is discarded past a 16MB buffer cap.
  • Catalog loadingmodels --verbose is retried ×4 (opencode shares one SQLite DB across instances; concurrent writers briefly lock it). Before the first load finishes, listModels() returns [] through a Proxy instead of throwing; once loaded, handle.replace() re-commits the route so the picker updates live.
  • Reload safety/opencode-refresh delegates to a module-level activeRuntime, so handlers of a replaced plugin instance operate on the current daemon rather than reviving a stopped one.

Usage

Install

# from npm
dsh plugin --profile web add @pananfly/dsh-opencode-acp
# or local
pnpm build && dsh plugin --profile web add ./path/to/dsh-opencode-acp

Requires the opencode binary on the machine running dsh.

Binary discovery order

  1. bin plugin config (see cordis.patch.yml, interpolated from OPENCODE_BIN)
  2. OPENCODE_BIN environment variable
  3. ~/.opencode/bin/opencode, /opt/homebrew/bin/opencode, /usr/local/bin/opencode
  4. bare opencode on PATH

Configuration

| item | default | description | |---|---|---| | bin (plugin config) | "" | explicit opencode binary path | | OPENCODE_BIN (env) | — | opencode binary path fallback | | OPENCODE_ACP_CWD (env) | dsh cwd | working directory handed to ACP sessions |

Command

  • /opencode-refresh — stop + respawn the daemon, reload the model catalog, and hot-swap the registration (handle.replace) so the picker updates without restarting dsh.

Console

[info] [dsh-opencode-acp] spawning /Users/you/.opencode/bin/opencode acp --port 0
[info] [dsh-opencode-acp] daemon initialized
[info] [dsh-opencode-acp] catalog refreshed: 12 models (4 with effort levels)
[warn] [dsh-opencode-acp] daemon exited code=1 signal=null
[error] [dsh-opencode-acp] initial catalog load failed: ... (use /opencode-refresh to retry)
# /opencode-refresh → "opencode-acp restarted; 12 models available (4 support thinking levels)"

Provider scope — not for TUI

The provider is registered inside the dsh profile that installs this plugin (ctx.llm.registerAdapter). Today that makes it usable only in the web profile (and in headless, if you install it there too). It does not work in a TUI scenario:

  • dsh ships exactly two application templates (PROFILE_TEMPLATES): webdsh-web-app, headlessdsh-headless. No TUI application exists; the tui name seen in CLI help examples is just a placeholder
  • Any other profile name initializes as bare @deepseek-ai/dsh-base: plugins install fine, but no app ever loads, so there is no model picker to consume the registered provider
  • Profiles never share plugins — installing here affects only this profile. Conversely, a profile without this plugin cannot resolve a default model pointing at opencode-acp/*

If a TUI app ships in a future dsh release, installing this plugin into that profile should be all that is needed — the adapter targets the shared llm service API.

Notes

  • No ACP permission handler is registered: opencode tools requiring permission are auto-rejected by opencode itself. Allow them in opencode's own config if needed.
  • Tool transcripts stay clean: tool_call/tool_call_update notifications are intentionally ignored (only compact [called tool x] markers appear when history is replayed).
  • Sessions are bounded: anonymous turns release their ACP session immediately; named conversations are capped at 32 with LRU eviction — long-running daemons do not accumulate sessions.

Development

pnpm install
pnpm build
pnpm typecheck
# publish (scoped public)
pnpm publish --access public

License

MIT