Skip to content

feat: OpenCode 1.x native support — V1 .server() hooks on the standard install path - #919

Merged
ranxianglei merged 4 commits into
feat/pi-native-pluginfrom
2026-09-18_opencode-v1-native
Sep 18, 2026
Merged

ranxianglei merged 4 commits into
feat/pi-native-pluginfrom
2026-09-18_opencode-v1-native

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

Fixes #918

What

The same bili plugin install opencode wrapper now serves OpenCode 1.x through the V1 .server() hook surface, delivering near-v2 effectiveness on the standard install path:

  • config hook: rewrites every provider options.baseURL to <proxy>/bili/<upstream> by mutating the shared in-process config object (idempotent; unwraps stale wraps from other proxy origins) + compaction.auto: false
  • chat.headers: stamps the plugin headers per request → plugin mode (no wire tool injection)
  • tool: registers the full bili tool set natively with real zod shapes (JSON-Schema→zod conversion; host registry wraps with its own z.object — zod pinned to 4.1.8 matching the host)
  • /acp command: same status panel as v2, via the shared opencode-acp-command.ts factory
  • reuses the self-spawn/attach proxy bootstrap (bili: attaching to running proxy … observed across processes); degrades to plain proxy mode when zod is unresolvable (rewrite only, wire-injected tools)

Refactor

  • src/agent/opencode-acp-command.ts (new): /acp hooks extracted from the launcher plugin — V1 .server and V2 setup now share one implementation
  • src/agent/opencode.ts: launcher plugin consumes the shared factory
  • zod becomes the package's only runtime dependency (external; only dist/agent/opencode-native.js imports it lazily — dist/index.js verified zod-free)

Verification

  • npm run typecheck / npm run build clean; full npm test 1692 pass / 0 fail (11 new tests: schema→zod mapping incl. cross-instance z.object interop, provider-rewrite idempotency/unwrapping, hooks wiring, zod-unavailable degrade)
  • Live E2E in container (real sglang upstream) via bili plugin install opencode + plain opencode on 1.14.46 and 1.18.31:
    • [bili-opencode-native] v1 active (proxy http://127.0.0.1:PORT) + rewrote 2 provider baseURL(s)
    • plugin mode on the wire: [plugin] [acp-usage] input=… cached=… (cache hit 98%)
    • native tool round-trip: [plugin] tool acp_status executed via plugin (378 chars)
    • /acp in the TUI command palette + full panel rendered into the session (idle + live variants)
    • tag-echo guard stripping model-emitted render tags (plugin passthrough)

Known V1 limitation (documented in README): providers without an explicit baseURL (SDK defaults) keep going direct — V1 has no request-level URL seam to catch them.

…d install path

- src/agent/opencode-native.ts: V1 plugin surface — config hook rewrites
  provider baseURLs to <proxy>/bili/<upstream> on the shared in-process
  config object (idempotent, unwraps stale wraps), chat.headers stamps the
  plugin headers, tool registers the bili tool set with real zod shapes,
  /acp command renders the same status panel. Reuses the self-spawn/attach
  bootstrap; degrades to plain proxy mode when zod is unresolvable.
- src/agent/opencode-acp-command.ts: /acp hooks extracted from the launcher
  plugin, shared by V1 (.server) and V2 (setup) entries.
- src/agent/opencode.ts: launcher plugin now consumes the shared factory.
- zod 4.1.8 (exact, matches the opencode host's zod) added as the only
  runtime dependency; dist/index.js stays zod-free.
- tests/opencode-native-v1.test.ts: 11 tests (schema->zod mapping incl.
  cross-instance interop, provider rewrite idempotency, hooks + degrade).
- E2E verified in-container on opencode-ai 1.14.46 and 1.18.31 via
  'bili plugin install opencode': providers rewritten, plugin-mode headers,
  native acp_status round-trip, /acp panel render, tag-echo guard.
@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 收到。这是 [PR] 类唤醒,不重做实现——正在核对:拉取 2026-09-18_opencode-v1-native 分支、核对 diff 与 CI 状态、本地跑 typecheck/test/build 验证。结论稍后回帖。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

Verification complete — all green. Details:

CI status (head 086e80a)

  • ✅ e2e-fake-codex (real codex vs deterministic fake upstream, zero tokens) — passed (run #35248016415)
  • ℹ️ ci.yml (typecheck + test + build, ubuntu/windows × node 22/24 + version-guard) did not run: it only triggers on PRs targeting master, and this PR targets feat/pi-native-plugin. Filled the gap locally:

Local verification (branch checked out, node 22)

  • npm ci — clean; zod resolves to exactly 4.1.8
  • npm run typecheck — clean
  • npm test — 1694 tests: 1692 pass / 0 fail / 2 skipped, matches your claim; the three new suites (jsonSchemaToZodShape / rewriteV1Providers / createV1ServerHooks) all green
  • npm run build — clean
  • zod-free claim verified: dist/index.js contains zero references to zod; dist/agent/opencode-native.js has exactly one lazy import("zod"), kept external (tsup noExternal list unchanged — acp-kernel & co still inlined)

Diff audit (every referenced symbol cross-checked against feat/pi-native-plugin)

  • wrapUpstream/unwrapUpstream (src/launcher.ts:205/:213), ACP_TOOLS_OPENAI (src/compress-tool.ts:42), forwardTool (src/agent/shared.ts:117) — all exist, signatures match
  • V1 header stamps use the same names as V2 (x-bili-plugin, x-bili-plugin-conversation) → same plugin-mode session binding path
  • version field untouched ✓ (non-release branch rule)
  • Scope check: diff touches agent-side plugins + docs + package.json only — no server.ts / loop/ / adapter / preflight changes, so the mandatory codex E2E re-run for pipeline changes doesn't apply (and the e2e-fake job covered the codex path anyway)

Minor observation (non-blocking, nothing pushed): in jsonSchemaToZodShape, the array-case guard item === undefined is dead code — the recursive call never returns undefined. Purely cosmetic; left alone per the [PR] verify-only rule. Say the word and I'll squash it into a commit.

Merge planning note: when this lands toward master (directly, or once feat/pi-native-plugin merges), ci.yml's full matrix will run again on the master-targeting PR.

一句话:PR 代码核对与本地 typecheck / test(1692 通过)/ build 全部干净,dist/index.js 确认无 zod、原生插件懒加载外置,无阻塞问题,可以合并。

ework-agent and others added 3 commits September 18, 2026 01:40
…lared limits

Completes V1<->V2 header parity flagged during #919 review. The V1 .server()
path reads provider.<id>.models.<m>.limit.context from the shared config object
(V1 exposes no catalog API like V2's ctx.catalog.model.list()) into a
provider/model map rebuilt on each config reload, then chat.headers stamps
x-bili-plugin-context-window only when the requested model carries a finite,
positive declared limit -- byte-compatible with the V2/pi stamping guard. When
absent (common for registry-discovered models) nothing is stamped, so the proxy
falls back to its models.dev registry window as before; never fabricates a
threshold. Adds extractV1Windows (+4 unit tests incl. end-to-end stamping).
Local pre-flight: typecheck + build + full suite green (1696 pass).
The V1 config-hook rewrite only reaches providers with an explicit
options.baseURL; providers relying on the SDK default (e.g. bare
@ai-sdk/openai -> api.openai.com) silently bypassed the proxy. Install
the shared native fetch intercept (pi-native's mechanism) in both the
native V1 server() and the launcher plugin server() — v1's provider
stack resolves fetch per-request, so a global patch catches those calls.

Verified end-to-end on opencode-ai 1.18.31 (container, hosts-redirect
api.openai.com -> local https shim -> sglang): leaktest provider with no
baseURL produced proxy log 'forward POST -> https://api.openai.com/v1/
responses' with x-bili-plugin headers — the request was captured,
/bili/-wrapped, and answered.
Adds the 1.x/2.x client-table rows, the native-vs-standalone mutual
exclusion note, the 2.x launcher bullet, and a condensed Chinese
counterpart of the OpenCode-specific section (launcher / native
install / V1 .server() details incl. the SDK-default fetch patch).
@ranxianglei
ranxianglei merged commit 2c5a6f8 into feat/pi-native-plugin Sep 18, 2026
1 check passed
ranxianglei added a commit that referenced this pull request Sep 18, 2026
Union of the pi/opencode native branch stack (PRs #706/#919/#921 + #927
jsonc fix) with master's #923 lineage:

- Legacy opencode-acp sessions: keep the branch's opencode-legacy.ts
  (docker E2E verified on opencode 1.18.31 + acp 1.18.1: absorbed-acp
  compress/decompress/acp_status + x-bili-plugin-bypass verbatim
  forwarding) as the single legacy machinery; master's acp-bridge.ts is
  dropped as the duplicate. Its BILI_OPENCODE_ACP_SPEC launcher hint is
  adopted into legacyAcpCandidates (authoritative when the cache slot
  exists).
- src/agent/opencode.ts stays the branch's thin version-dispatch entry
  (V1 -> opencode-native server, V2 -> opencode-v2 setup).
- pi.ts: event-time proxy ownership gate (#519 native) UNION master's
  omp auto_compaction_start announcement tracking (#851).
- server.ts: master's early-return legacy bypass (BILI_PLUGIN_BYPASS_HEADER
  from util) replaces the branch's late pipeline exclusion; branch's
  pluginBypassHeader plumbing removed.
- plugin-install.ts: branch's full native installer (jsonc-first #927,
  plugin-entry + compaction.auto + stripLegacyOpencodeAcp) + master's
  claudeStatus isPlainMcpObject hardening (#839).
- Docs: table rows and launcher notes merged to describe both the native
  installer and the launcher legacy-session behavior; CHANGELOG union.

Verified: typecheck clean, 1822 tests (1820 pass / 2 E2E-gated skip),
build ok.
antclt pushed a commit to antclt/billion-context that referenced this pull request Sep 18, 2026
…ons, bili owns new ones (ranxianglei#920)

OpenCode 1.x hosts migrating from opencode-acp keep their pre-migration
sessions WORKING. The V1 native entry absorbs the installed opencode-acp
package (loadLegacyAcp: .opencode/node_modules > project node_modules >
global npm root > config-scope modules) by importing its server() with
BILLION_CONTEXT_PROXY unset strictly around the call (its only env check
is at entry), then routes per session:

- legacy session (acp state file exists in
  <XDG_DATA_HOME>/opencode/storage/plugin/acp/<sid>.json) — compression
  runs INSIDE the absorbed acp (its dcp refs, block store, all five
  tools). Model requests carry x-bili-plugin-bypass: 1; the proxy
  forwards them verbatim (no injection/nudge/binding).
- new session — tool executors forward to the proxy plugin endpoints
  (plugin mode). Tool slots carry the DCP schemas for both lanes (v1
  registers one def per name process-wide); the executor picks the lane.

acp's transforms (system/messages/text.complete) are gated on the
legacy predicate so new sessions never enter its registry; its config
hook runs on a provider-hidden shadow (its /bili/ self-disable is
neutered) and owned keys are merged back. /acp and /dcp route per lane.
Degradation: package absent/import-failed -> bili alone, legacy sessions
read-only (documented).

Also: cherry-picked 74701fb (installer strips legacy opencode-acp
entries, ranxianglei#918) — it was pushed to the already-merged ranxianglei#919 branch and
orphaned.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant