Repository navigation
[OpenCode V2][Epic] Deliver opt-in server API V2 support in five reviewable upstream PRs #1
Description
Goal
Deliver genuinely usable, opt-in OpenCode V2 server API support in opencode-a2a while retaining the verified OpenCode V1 path and the existing A2A 1.0 public contract. Track five small, understandable, independently reviewed upstream PRs, not a gateway rewrite.
Upstream coordination: Intelligent-Internet#550
Source baseline when planned: 5d4700204445ce658361010cbda2fd344bc60dec (fork matched upstream).
Not to be confused with: upstream A2A SDK V2 lifecycle PR Intelligent-Internet#538.
Background
The current adapter calls OpenCode V1 endpoints such as POST /session, POST /session/{sessionID}/message and GET /event; docs/compatibility.md declares V1 1.18.x, verified on 1.18.19. OpenCode V2 uses /api/* with different request/response and execution semantics. V2 POST /api/session/{sessionID}/prompt returns an admitted inbox input, not a generated assistant result. V2 GET /api/event is volatile. Its default external-directory permission is broader than V1.
References: https://opencode.ai/v2/docs/migrate-v1#server-api-and-clients and https://opencode.ai/v2/docs/api .
KISS / architectural rules
- One A2A runtime, two thin OpenCode adapters. Keep A2A auth, task persistence, session ownership, workspace policy, output formatting, and common event rendering shared.
- Expose a minimal typed contract for session, admission, correlated execution updates, outcome, and interventions; no universal "all agent runtimes" framework.
- Separate accepted from completed. A task completes only on correlated terminal evidence, never solely on HTTP 200 or unrelated
session.idle. - V1 remains the default. Explicit V2 selection; no auto-detection or silent fallback in this epic.
- Honest capabilities throughout: any unimplemented V2 extension/model/tool behavior is hidden from discovery and rejected explicitly, starting with the first V2 PR.
- Each PR must work independently against upstream
main, or be clearly marked as a draft with documented dependency; never ask maintainers to review a giant stacked diff as one PR. - Preserve existing security: auth, identity-to-session ownership, location/workspace restrictions, concurrency budgets, cancellation semantics, and log redaction.
Planned upstream PR sequence (one fork issue each)
- PR 1 — Refactor V1 upstream boundary (no behavior changes). [OpenCode V2][1/5] Extract typed upstream adapter boundary without changing V1 behavior #2 — [OpenCode V2][1/5] Extract typed upstream adapter boundary without changing V1 behavior #2
- PR 2 — Add explicitly selected V2 connection/session backend; gate unimplemented execution. [OpenCode V2][2/5] Add opt-in V2 transport, session lifecycle and location mapping #3 — [OpenCode V2][2/5] Add opt-in V2 transport, session lifecycle and location mapping #3
- PR 3 — Implement correlated prompt execution, output streaming, terminal states, model/tool compatibility, and cancellation. [OpenCode V2][3/5] Complete correlated V2 prompt execution, streaming and cancellation #4 — [OpenCode V2][3/5] Complete correlated V2 prompt execution, streaming and cancellation #4
- PR 4 — Map V2 permissions/forms to secure A2A input-required interventions. [OpenCode V2][4/5] Map V2 permissions and forms to secure A2A input-required flows #5 — [OpenCode V2][4/5] Map V2 permissions and forms to secure A2A input-required flows #5
- PR 5 — Finalize truthful capability discovery, pinned V1/V2 tests, manual evidence and compatibility docs. [OpenCode V2][5/5] Finalize capability honesty, pinned V1/V2 E2E and compatibility docs #6 — [OpenCode V2][5/5] Finalize capability honesty, pinned V1/V2 E2E and compatibility docs #6
Each child issue must contain a junior-friendly implementation checklist, pitfalls, unit/HTTP tests, final manual E2E acceptance instructions, acceptance criteria, and Definition of Done. Cross-link its upstream PR and this epic. Update source paths and API details if upstream changes before implementation.
Execution plan refinement — applies to issues #4–#6 (October 25, 2026)
Work on #2 and #3 is left unchanged; this is a forward-looking refinement for implementation issues #4, #5 and #6, not a request to redo V1 extraction or V2 session transport. Issue #3 is reported as the user's current stage, not marked complete or merged (verify its actual PR/branch state before starting dependent work).
- [OpenCode V2][3/5] Complete correlated V2 prompt execution, streaming and cancellation #4 / upstream PR 3: Reuse a shared
httpx.AsyncClient; prefer a bounded, shared nativeGET /api/eventSSE subscription per authenticated upstream backend with session/input/task and owner-aware fanout, not one global SSE stream per A2A task. Open/ready the event feed before prompt admission; parse native SSE frames correctly, use V2 prompt inputidand returned inbox receipt as admission, never as completion. Favor event-driven updates over polling; handle gaps with bounded targeted message/inbox reconciliation, no blind retry. Evaluate the experimental per-session durable log only if verified and justified. Distinguish queued inbox cancel from active session interrupt, verifying the latter's200 SessionInterruptResponsepayload. - [OpenCode V2][4/5] Map V2 permissions and forms to secure A2A input-required flows #5 / upstream PR 4: Reuse [OpenCode V2][3/5] Complete correlated V2 prompt execution, streaming and cancellation #4's same event pipeline and targeted HTTP get/list reconciliation for permissions and forms; correct session/request-bound reply schemas (
decisionvs V1reply) and204no-body behavior; no new subscription or busy polling while awaiting human input. Preserve owner/task binding and V2 external-directory policy. - [OpenCode V2][5/5] Finalize capability honesty, pinned V1/V2 E2E and compatibility docs #6 / upstream PR 5: Release-level tests must prove connection reuse, SSE fanout isolation, bounded queues/backpressure, no normal-path hot polling, disconnect/reconciliation, exact cancellation semantics, shutdown cleanup and optional log cursor recovery only if chosen. Publish pinned real-model V1/V2 E2E evidence and sanitized HTTP/subscription counts.
KISS decision: No custom message polling daemon, no new SDK/client stack, no forced experimental-log dependency, and no speculative generalized transport framework. Do not promise seamless replay when the only active V2 source is the volatile global event feed.
Reference contracts: https://opencode.ai/v2/docs/api (session.prompt, event.subscribe, experimental session.log, session.inbox, session.interrupt, session.permission.reply, session.form.reply). Final implementation must verify these against its pinned OpenCode server build.
Epic-level pitfall gates (must not regress)
- Prompt correlation: admitted input ID vs assistant output ID vs A2A task ID. Concurrent or queued prompts and unrelated idle events must not complete another task.
- Event loss: volatile V2 events do not imply replay; reconnect must reconcile against authoritative session state or fail as uncertain, never fabricate success or blindly resubmit. Experimental
/api/experimental/session/{sessionID}/logmay be evaluated, but cannot be assumed supported without version-pinned evidence. - Security: OpenCode V2 defaults
external_directoryto allow; test explicit upstream policy and gateway path restrictions. Reject ownership mismatches and invalid location changes before privileged calls. - Response typing: V2 has
data-wrapped JSON results and204 No Contentmutations; never assume every successful call yields a JSON object or V1-shaped payload. - Model selection: V2 model switch is session-scoped, whereas this adapter advertises request-scoped model selection; maintain semantic isolation or reject the override.
- Tool bridge: existing
a2a_call/tool-result reinjection assumes V1-style parts. Do not claim it works in V2 until proven end-to-end; gate and document unsupported behavior. - Compatibility: SDK "V2" is not OpenCode server V2. Keep V1 live test and V2 pinned test isolated (different binaries/config HOME/XDG/workspaces), with no claims based only on mocked responses.
- Staging: after PR 2/3/4, partially supported V2 must be clearly experimental/limited; advertised features match implemented and tested features.
Detailed implementation / tracking steps (for a junior contributor)
- Read
AGENTS.md,CONTRIBUTING.md,docs/maintainer-architecture.md,docs/security-architecture.md,docs/compatibility.mdandscripts/README.md. - Before each PR, confirm current upstream head/maintainer feedback on [Question] OpenCode V2 server API 兼容性与支持计划 Intelligent-Internet/opencode-a2a#550; branch from the latest upstream
main(not from a chain of unmerged feature branches unless visibly marked stacked). - Implement only the child issue scope. Reuse
opencode_upstream_client.py,execution/*,server/*,contracts/extensions/*and existing tests; don't reorganize unrelated modules. - For each PR run
bash ./scripts/doctor.sh, relevant targeted tests, and the issue's manual E2E scenario. Record exact OpenCode and Python/adapter versions and command output. - Open a narrowly scoped upstream PR describing why, code-path changes, what is intentionally unsupported, security impact, tests, and dependencies. Link child issue and upstream [Question] OpenCode V2 server API 兼容性与支持计划 Intelligent-Internet/opencode-a2a#550.
- Mark an epic checkbox only after the PR is reviewed/merged upstream (or record clearly if fork-only). Do not close this epic before the acceptance matrix below passes.
Final manual E2E acceptance (epic release gate)
Use real pinned V1 and V2 opencode serve installations, isolated temporary HOME/XDG state, isolated workspace dirs and ports, a configured real model, local-only upstream listeners, upstream Basic auth and separate inbound A2A credentials. Record exact versions and results:
- Run the existing V1
bash ./scripts/live_opencode_smoke.shand verify streaming completes with the expected assistant text; repeat with a non-streaming A2A send. - Switch the adapter to V2 explicitly, verify
GET /api/info, create a new session, stream a prompt to a terminal A2A task, and continue the same session/context with a follow-up. - Repeat the central V2 request over the other supported A2A transport (REST and JSON-RPC). Verify no unsupported feature is advertised in Agent Card or extension discovery.
- Execute a task that emits reasoning and a tool update; verify correct block metadata, final artifact snapshot and persisted terminal state.
- Create two independent A2A tasks and queued/overlapping prompts; verify no cross-task output or premature completion. Trigger failure, cancellation and an upstream event disconnect; never get false success.
- Trigger one human-approved permission request and one form; verify
input-required, authorized reply, continued execution, rejection and cross-principal denial. - Try unauthorized inbound A2A calls, unauthorized upstream HTTP, cross-owner session reuse, prohibited workspace/outside-root path, and an unsupported V2 extension; all must fail safely and predictably.
- Confirm V2 request-scoped model handling is semantically verified or explicitly unavailable. Confirm
a2a_call/tool-result round trip is either proven working or explicitly not advertised. - Run deterministic suites and
bash ./scripts/conformance.shas well as pinned live checks. Publish the pass/fail table and known limitations indocs/compatibility.md.
If real model credentials/manual interaction are unavailable, mark the relevant gate not verified and keep the epic open: do not substitute mocks for a passed live acceptance test.
Epic acceptance criteria
- Each child PR has independent reviewable change scope and linked test evidence.
- Unchanged V1 default path passes all existing regression + live smoke tests.
- Opt-in V2 can create/continue sessions and complete real streamed + unary A2A tasks.
- V2 request correlation, terminal/failure/cancellation, and intervention workflows are tested.
- No V2 operation violates existing ownership/workspace/security boundaries.
- Capability advertisements and error behavior match actual support on both versions.
- Pinned compatibility matrix, manual E2E evidence, and upstream maintenance policy/decision are documented.
Definition of Done
- All five implementation issues resolved with upstream PR links and merged/reconciled status.
- CI/typecheck/linters/contract tests and the final manual V1/V2 E2E acceptance pass.
- No known P0/P1 semantic or security regressions; unresolved optional features explicitly gated and documented.
- Upstream maintainer decisions from [Question] OpenCode V2 server API 兼容性与支持计划 Intelligent-Internet/opencode-a2a#550 recorded; docs distinguish OpenCode API V2 from A2A SDK V2.
- Epic is closed only with a linked release/merge summary and tested upstream version numbers.
Explicitly out of scope
Automatic API-version detection, speculative multi-runtime abstraction, a general cleanup, V1 deprecation, and full parity for every OpenCode-private session/workspace extension. These may be separate non-blocking follow-ups.
Activity
tbrandenburg commentedon Oct 25, 2026
受限 V2 profile:实现与 scoped live 验收结果
本轮用户明确授权 private / gateway-exclusive / single-flight 首批范围。该 profile 已通过真实模型 API 验收,不等同于非独占 shared-session 支持,也不会静默关闭原 epic 的广义验收。未解决的通用关联/恢复/取消单独跟踪 #9。
OPENCODE_V2_EXECUTION=exclusive才启用受限路径。a2a_call不支持。Live reasoning NOT VERIFIED。代码与 sanitized evidence:
上游顺序:typed boundary → session slice → bounded native engine → exclusive activation/interventions → acceptance harness。跨仓库链接:
每个 stacked PR 明确说明依赖,不伪装为独立 diff。未 force-push、自动 merge 或关闭 tracking issue。请 maintainer 明确接受 restricted-first 范围,并按依赖顺序审查;upstream 尚未发布该支持。各 PR 的 exact-head QA 记录与 author live/source-identity 证据分开陈述。
tbrandenburg commentedon Oct 25, 2026
Fork 已合并并发布 prerelease;upstream / 广义范围保持待完成
#7 已 normal merge 到本 fork
main:19943955704bf05347619ca5b98addb94a683c97。GitHub-only prerelease:1.4.1rc1+fork.v2exclusive,标题为 OpenCode V2 2.0.28 — experimental private exclusive execution;未发布 PyPI。Candidate
da51304与 merge tree 相同。两次 exact-head doctor 均 1021 passed / 0 failed(candidate 92.65%、main 92.67% coverage),两次 conformance 均 8/8;main real 2.0.28 session/auth smoke 通过,36 个 default/opt-in focused tests 通过,tagged wheel/sdist smoke 与下载 checksum 通过,runtime/dev/build audit 未发现已知漏洞。GitHub 无已上报 CI checks;不冒称 hosted CI 通过。Recorded V1 1.18.19 / V2 2.0.28 real-model evidence 的生产 Python digest 与 released main 精确一致:
ecd2b57cfab3506704aeabacb8acdc58d4ddc9e6f4bd578be691536e8cf3bef8。本轮复核历史 live evidence,没有重跑完整 model matrix。证据与边界:acceptance、deployment contract。V1 默认;V2 execution 仍 double opt-in,private network/credentials/fixed workspace/fresh state/单 gateway pair 等要求不变。Issue disposition
Upstream stack ancestry:不做 cosmetic history rewrite
五个 upstream PR 仍 open,分支未修改/删除:
Intelligent-Internet#551 → Intelligent-Internet#552 → Intelligent-Internet#553 → Intelligent-Internet#554 → Intelligent-Internet#555 。
实际检查:Intelligent-Internet#552 不包含 Intelligent-Internet#551 最新 commit 的 ancestry,但
65c15e9与其73ffcd8是 patch-equivalent outcome-safety fix;Intelligent-Internet#555 不包含 Intelligent-Internet#554 最新四个 commits 的 ancestry,但包含对应 follow-up fixes,Intelligent-Internet#554/Intelligent-Internet#555 的生产src/无 diff。两处都不是 fast-forward;为了没有 source 收益的 graph 整理追加重复 merge 并不必要,因此本次安全保留原 stack。后续如 maintainer 要求 ancestry reconciliation:按 Intelligent-Internet#551→Intelligent-Internet#552→Intelligent-Internet#553→Intelligent-Internet#554→Intelligent-Internet#555 顺序,在各 child 的临时 reconciliation branch 上 non-destructive merge 最新 parent;先检查冲突/最终 source diff,再对每个新 head 运行 doctor 和对应 smoke,然后按 maintainer 决定正常推送或开 follow-up PR。若 upstream squash/改变 base,先从其最终 accepted commit 建新分支,仅移植尚未包含的 diff,再按相同顺序验证;不要 force-push 当前 published PR branches,也不要凭 patch-ID 自动丢弃非等价 fix。本次未重建 stack、未合并 upstream PR。
tbrandenburg commentedon Oct 25, 2026
已合并 fork,PR 与 main hosted CI green
Fork PR #11 已 normal merge,main commit
37a49bbd3d81adb777b85d9616b883009d8719a4。Merge tree 与 tested candidatef224037相同。docs/evidence/v2-default-acceptance.json匹配。现在 V2 是 API default;V1 需要
OPENCODE_API_VERSION=v1。V2 execution 仍需OPENCODE_V2_EXECUTION=exclusive,private deployment 前提和 unsupported scope 不变。没有自动 fallback,没有改写旧 prerelease/tag 或发布 PyPI。Upstream follow-up Intelligent-Internet#556 保持 stacked Draft,关联 Intelligent-Internet#550 ,依赖既有前置贡献。Exact upstream head
fab667a独立 doctor 1026 passed、conformance 8/8,但 hosted CI 仍为 action_required / 需要 maintainer approval,不是 green 或 upstream merge。五个既有 contribution heads 保持原样。Fork tracking #10 已完成关闭;原广义 epic 和未解决的 V1 attribution / V2 unrestricted-session scope 不因此关闭。Live reasoning 仍 NOT VERIFIED。
tbrandenburg commentedon Oct 25, 2026
Fork 交付结案;停止扩展本 epic
#11 已合并到
37a49bbd3d81adb777b85d9616b883009d8719a4,新 1.4.1rc2+fork.v2exclusive prerelease 已发布。Main hosted CI 五个 jobs SUCCESS;doctor 1026 passed,conformance 8/8。旧 tag/release 保留,未发布 PyPI。Breaking configuration change:V2 默认选中,但 execution 仍 disabled;只有明确
OPENCODE_V2_EXECUTION=exclusive且满足 private deployment 前提才启用。V1 需OPENCODE_API_VERSION=v1。#4–#6 的 execution、interventions、acceptance 已在 restricted fork profile 内交付;其更广义目标仍 deferred,不能据此宣称原广义 DoD 完成或 upstream 已合并。
按用户最终 disposition,归档 #1 与 #4–#6(
not planned指不再在此继续扩大原广义 scope,不是否定已发布的 restricted 实现)。本 epic 不再追加工程任务。仅保留 #8(V1 external-writer attribution) 和 #9(V2 unrestricted sessions) 作为未来工程调查;upstream contribution PRs 保持供维护者后续审查。tbrandenburg commentedon Oct 25, 2026
已交付:restricted fork scope
受支持的 private-exclusive V2 实现已通过 #7、#11 交付到 fork main,并发布 1.4.1rc2+fork.v2exclusive;具备 pinned V1/V2 acceptance evidence 和 green hosted CI。
按用户最终确认,本 issue 以 completed:在 fork 明确受限范围内已交付 关闭,取代此前
not planned的归档原因。原始更广义验收要求并未全部实现:unrestricted shared-session execution 仍由 #9 跟踪(deferred);其他 unsupported capabilities 继续在兼容性文档明确列出,目前不作实施承诺。Upstream contribution PRs 独立保持 open,fork 交付不等于 upstream acceptance。#8(V1 attribution risk)与 #9 保持 open;本 issue 不再承担后续工程任务。