feat(agents): add OpenCodeAgent for OpenCode integration#125
feat(agents): add OpenCodeAgent for OpenCode integration#1250xCaso wants to merge 3 commits intocodeplaneapp:mainfrom
Conversation
Preserve Smithers system prompts and per-call session resume semantics, parse OpenCode step_finish token usage into generate() results, and keep structured provider errors when the CLI exits nonzero.
feat(agents): add OpenCodeAgent for OpenCode CLI integration
roninjin10
left a comment
There was a problem hiding this comment.
Review by Codex, not fucory.
I found a few issues that should be fixed before landing:
-
OpenCodeAgent.buildCommand()can pass both--continueand--sessionwhen the agent is constructed withcontinueSession: trueand a call providesresumeSession. OpenCode'srun.tsresolves the base session withargs.continue ? lastSession : args.session, so the explicit SmithersresumeSessionis ignored in that case. This breaks the per-call resume precedence described in the PR. The--continueflag should not be emitted when an explicit per-call or constructor session id is being used, and the test should covercontinueSession: trueplusresumeSession. -
JSON
errorevents currently only emit anAgentCliEventwithok: false; they do not makegenerate()fail unless the process exits nonzero. The upstream OpenCoderun.tsrecordssession.errorin a localerrorvariable but does not appear to use it to set a nonzero exit, while these tests forceprocess.exit(1). If OpenCode exits 0 after a provider/session error, this adapter will resolve successfully, likely with raw NDJSON asresult.text. Please either have the interpreter/BaseCliAgent promote a completedok: falseevent to a failed result, or have the OpenCode adapter retain the structured error and fail on exit even when the exit code is 0. Add a fake-CLI test that emitserrorEvent(...)and exits 0. -
The new adapter is exported, but CLI capability discovery still omits it.
packages/agents/src/cli-capabilities/getCliAgentCapabilityReport.jsstill lists only claude/codex/gemini/kimi/pi, andCliAgentCapabilityAdapterId.tsdoes not includeopencode, sosmithers agents capabilitiesandsmithers agents doctorwill not report the new built-in adapter. -
The generic
extractTextFromJsonValue()fallback now descends into anyvalue.part. That means OpenCodereasoningevents withpart.textcan be included in the finalgenerate().textpath, even thoughOpenCodeAgent.createOutputInterpreter()deliberately surfaces reasoning as thought events and does not add it tofullText. This is easy to hit withextraArgs: ["--thinking"]. The fallback should only accumulate OpenCodetype === "text"parts, or otherwise skip reasoning/tool parts.
Verification notes: in a clean temp checkout, bun test packages/agents/tests/opencode-support.test.js initially failed before running tests because workspace deps/exports were incomplete (@mdx-js/esbuild, then effect, then @smithers/memory/metrics). bun run --cwd packages/agents typecheck passed after adding the missing temp-only dev deps needed by this checkout.
NOTE: im currently building a workflow based on this where i'll be able to test everything more thoroughly :))
In the meantime i open the PR also for discussion if needed 🤝
Summary
OpenCodeAgent, a new CLI agent wrapper for OpenCode — an open-source, terminal-native AI coding agentrun.ts+message-v2.ts)opencodeis not on PATH)What's included
New files
packages/agents/src/OpenCodeAgent.js— Agent implementation (~492 lines) withbuildCommand()andcreateOutputInterpreter()packages/agents/src/OpenCodeAgent.ts— Type declarations (OpenCodeAgentOptions)packages/agents/tests/opencode-support.test.js— 26 unit tests using fake binaries that emit real nd-JSONpackages/agents/tests/opencode-e2e.test.js— 5 E2E tests against the real CLI (skipped viadescribe.skipIfwhen not installed)Modified files
packages/agents/src/capability-registry/AgentCapabilityRegistry.ts— Added"opencode"to the engine union typepackages/agents/src/index.js/index.ts— Added OpenCodeAgent exportspackages/agents/src/BaseCliAgent/BaseCliAgent.js— AddedstripOscSequences()for OSC escape sequence handling,extractErrorFromJsonPayload()for structured error preservation,step_finish.part.tokensusage parsing for OpenCode token data, andtotalTokensfix to prefercliUsage.totalTokenswhen presentpackages/agents/src/BaseCliAgent/extractTextFromJsonValue.js— Addedvalue.parttraversal path for nd-JSON formats that nest text inside{ part: { text: "..." } }Bugs discovered and fixed (10 total)
Found and fixed with TDD across two commits:
Commit 1 (
5d35219) — initial implementation:-fflag consuming positional prompt — Fix: added--separator before prompt inbuildCommand()stdoutBannerPatternsstep_startparsing — Fix: added OSC stripping inparseLine()before JSON parsingOPENCODE_PERMISSIONformat wrong — Was'"allow"', should be'{"*":"allow"}'OPENCODE_SYSTEM_PROMPTdead code — Removed entirely (env var doesn't exist in OpenCode)Commit 2 (
adb5922) — contract alignment fixes:6. systemPrompt dropped by OpenCodeAgent — Fix: prepend
params.systemPromptto prompt text inbuildCommand()7. Per-call
resumeSessionignored — Fix: readparams.options.resumeSessionand map to--session, with per-call precedence over constructor default8.
generate().usagemissing OpenCode token data — Fix: taughtextractUsageFromOutput()to parsestep_finish.part.tokensincludingtotal,reasoning,cache.read,cache.write9. Structured errors overwritten by generic CLI failure — Fix:
extractErrorFromJsonPayload()preserves provider error messages on nonzero exit10. Capability registry incomplete — Fix: added
apply_patch,list,websearch,codesearch,questionBaseCliAgent changes are safe for other agents
All changes are format-gated and shape-gated:
stripOscSequences()— only affects raw nd-JSON parsing; agents without OSC sequences are unaffectedstep_finish.part.tokensusage extraction — only matches OpenCode's exact event shape; other agents usemessage_start,message_delta,turn.completedetc.extractErrorFromJsonPayload()— only runs whenoutputFormatisjsonorstream-json; falls back to old stderr/exit behavior otherwisetotalTokensfix — usescliUsage.totalTokensif present, otherwise falls back to sum calculation (same as before)How to test
Verified against OpenCode source
The nd-JSON format, event types, permission model, and CLI flags were all verified directly from OpenCode's source code:
packages/opencode/src/cli/cmd/run.ts— emit function, CLI flagspackages/opencode/src/tool/registry.ts— built-in tool listpackages/opencode/src/config/config.ts— OPENCODE_PERMISSION parsing