Repository navigation
[OpenCode V2][1/5] Extract typed upstream adapter boundary without changing V1 behavior #2
Copy link
Copy link
Closed
Description
Parent epic: #1 · Planned contribution: upstream PR 1/5 · Dependency: none · Related upstream: Intelligent-Internet#550
Goal
Make the existing OpenCode V1 implementation a replaceable upstream protocol adapter behind a small, typed contract while producing exactly the same public A2A behavior. This is an independently useful maintainability PR: no V2 requests, endpoint changes, new configuration defaults, or cleanup campaign.
Background
src/opencode_a2a/opencode_upstream_client.py(currently ~928 lines) handles HTTP transport, V1 route/payload mapping, and interrupt-request storage concerns.execution/coordinator.pyassumessend_message()returns a generated assistant response, whileexecution/stream_runtime.pyandexecution/stream_events.pyconsume V1-shaped event dictionaries and interpretsession.idle.- The next V2 PRs must distinguish input admitted, execution update, and terminal outcome. Keep existing V1 behavior by translating V1 protocol results to these common concepts.
- Existing
tests/upstream/test_opencode_upstream_client_params.py,tests/execution/test_streaming_output_contract_*.py,tests/execution/test_session_ownership.pyandscripts/live_opencode_smoke.share the V1 regression baseline.
KISS (keep it simple)
- Introduce one Protocol/ABC plus a few small dataclasses/enums: e.g.
SessionRef,PromptReceipt,ExecutionUpdate,ExecutionOutcomeandIntervention. Use existing names/types if they fit. - Avoid a universal agent framework, a separate A2A gateway, a generic state machine for every OpenCode extension, or duplicate HTTP clients.
- Preserve
OpencodeUpstreamClientas a compatibility facade if necessary so callers and tests do not require mass rewrites. - Keep the V1 backend selected implicitly during PR 1; no public
OPENCODE_API_VERSIONswitch yet. - A V1 synchronous response can produce an acceptance/result transition in the adapter: do not make admission and completion the same type.
Detailed implementation plan (junior-friendly)
- Baseline first. Read
AGENTS.md,CONTRIBUTING.md,docs/maintainer-architecture.mdandscripts/README.md. Runbash ./scripts/doctor.sh. Record the tested OpenCode1.18.xCLI version and snapshot existing HTTP, streaming, ownership, cancellation and JSON-RPC contract tests. - Map call sites. Search for
OpencodeUpstreamClient/send_message/stream_events/session_prompt_async/permission_replyplus imports ofexecution/stream_events.py. Make a small list of what is protocol-specific vs shared. Do not move unrelated auth, persistence, A2A formatting or service modules. - Define minimal internal types under a narrowly named upstream package (e.g.
upstream/contracts.py). Session identifiers stay strings internally if sufficient. Admission receipt includes stable input/correlation identity when available; completion includes terminal state, output, usage/error; updates include session/message/part correlation and a normalized block type. Do not invent missing V1 identifiers: mark optional and rely on existing V1 sequencing behavior. - Isolate transport from mapping. Reuse existing
httpx.AsyncClientcreation, Basic-auth headers, timeouts, trace headers, error redaction and request/stream concurrency budgets. Move only versioned HTTP paths, request payloads and response parsing into aV1backend. Ensure204and non-JSON failures remain handled appropriately without changing today's V1 contract. - Normalize V1 events at the boundary. Extract just enough from
stream_events.pyfor the shared runtime to consume typed updates (text/reasoning/tool, usage, errors, idle, permissions/questions). Keep streaming deduplication, ordering, final snapshot and A2A artifact emission shared and unchanged. Avoid raw V1 event dictionaries escaping into code that V2 will also use. - Preserve extension behavior. Keep the current session, provider, workspace and interrupt JSON-RPC handler call surface working through the V1 adapter/facade. Do not create a new generalized extension interface for every endpoint.
- Wire the new boundary incrementally. Start with session creation and send + stream, then move only the necessary event parsing. Keep the diff limited; if a planned type causes widespread churn, simplify it.
- Prove future fit separately. In a scratch branch or throwaway script (not PR 1), sketch how a V2 inbox receipt and subsequent completion would use the types. Record the result in the PR rationale; do not merge unfinished V2 code with the preparatory refactor.
- Add focused adapter mapping/unit tests and run the existing suites; compare V1 HTTP request shapes and A2A output fixtures before and after. Update the internal architecture documentation only where changed.
Pitfalls to avoid
- Treating
session.idleas a universal (V1+V2) correlated terminal signal. - Defining
submit_prompt() -> final assistant messagein the new interface. - Moving authorization/session ownership/SQLite persistence into the adapter.
- Broad renames across every JSON-RPC handler, output renderer and A2A public schema.
- Making V1 smoke assertions weaker to get the refactor to pass.
Final manual E2E acceptance test (mandatory before merging PR 1)
Prerequisites: a real OpenCode V1 1.18.x binary, a real configured model for prompts, isolated test workspace, upstream Basic-auth password, inbound A2A auth, local ports and throwaway HOME/XDG config.
- Run
bash ./scripts/live_opencode_smoke.shwithoutLIVE_SMOKE_SKIP_PROMPT=1. Verify upstream auth is enforced and a streamed prompt returns expected text andTASK_STATE_COMPLETED. - Use the configured A2A endpoint to send a non-streaming message; verify terminal task, assistant artifact and no partial-only result.
- Send a second message with the same session-binding context and verify session continuity. Check one supported JSON-RPC read-only session/provider method still works.
- Start a long task, cancel it, and verify the expected cancelled state. Make an unauthorized request and a cross-owner session request; verify rejection.
- Inspect emitted text/reasoning/tool metadata (where the selected model produces it), ensure final artifacts are not duplicated, and check logs contain no credentials or full prompt bodies by default.
- Record actual CLI/server versions, exact commands and pass/fail evidence in the upstream PR. If a live model cannot run, do not mark manual E2E passed.
Acceptance criteria
- Small typed adapter contract has distinct admission, updates and completion concepts.
- V1 is the only active backend; config defaults and A2A/JSON-RPC/Agent Card contracts are unchanged.
- Raw V1 event shapes are confined to the V1 decoding path.
- V1 request/response semantics, permissions, auth, workspace, and stream output remain behaviorally identical.
- Existing unit, HTTP-boundary, output/ownership tests and
bash ./scripts/doctor.shpass. - Real V1 send/stream/follow-up/cancel smoke passes with evidence.
- Unrelated refactoring and speculative V2 abstractions are absent.
Definition of Done
- Targeted code review demonstrates a small, understandable diff and backwards-compatible facade where needed.
- No new default flags/dependencies or public behavior changes.
- Tests, lint, types, and manual V1 E2E completed; evidence linked from PR.
- PR explains the next V2 use case, limitations and why each interface method is necessary.
- A focused upstream PR referencing this issue, [OpenCode V2][Epic] Deliver opt-in server API V2 support in five reviewable upstream PRs #1 and upstream [Question] OpenCode V2 server API 兼容性与支持计划 Intelligent-Internet/opencode-a2a#550 has been submitted; issue closes only after merge/reconciliation (not simply because code exists in a branch).
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
tbrandenburg commentedon Oct 25, 2026
Preparation PR 更新
Typed boundary 的两个 HIGH regressions(非 completed outcome 被当 success;invalid submit result 导致 legacy resend)已以 append-only commit 修复并追加到上游 PR:Intelligent-Internet#551 。Exact head
65c15e9的 doctor:841 passed,92.87% coverage;4 个 safety regressions 通过。后续 restricted profile 不重写该基础。完整 scope / live 证据见本仓库 #1 的最新状态评论与 #7;原 issue 保持 open,等待 upstream review/merge 和正式 disposition。未 force-push 或自动 merge。
tbrandenburg commentedon Oct 25, 2026
Fork scope 完成:明确 merge/reconciliation
#7 已 normal merge 到本 fork
main19943955704bf05347619ca5b98addb94a683c97;experimental fork prerelease 已发布,未发布 PyPI。Typed admission/update/outcome boundary、兼容 V1 facade 与 outcome-safety fixes 已交付。Candidate 与 main 分别重新运行 doctor:1021 passed / 0 failed,conformance 分别 8/8;main 36 个 default/opt-in tests 通过。Recorded real V1 1.18.19 regression source digest 与 released source 一致;本轮没有冒称重跑完整 model matrix。Pinned evidence。
按本 issue “merge/reconciliation 后关闭”的约定,将本次获授权的 fork merge 明确记录为 fork delivery reconciliation,关闭本 typed-boundary implementation issue。这不代表 upstream merge:Intelligent-Internet#551 仍 open,贡献分支保持原样供审查。其 follow-up safety patch 在 Intelligent-Internet#552 中有 patch-equivalent commit;不为 graph 美观改写历史。完整 upstream/广义范围由 #1 继续跟踪。