Skip to content

feat: add OpenCode host support#86

Open
wenchanghan wants to merge 8 commits into
ReflexioAI:mainfrom
wenchanghan:codex/opencode-host-integration
Open

feat: add OpenCode host support#86
wenchanghan wants to merge 8 commits into
ReflexioAI:mainfrom
wenchanghan:codex/opencode-host-integration

Conversation

@wenchanghan

@wenchanghan wenchanghan commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add OpenCode host support so claude-smart can install into OpenCode, inject learned context before model requests, capture tool/assistant activity, and publish learning at turn completion.
  • Support OpenCode-only users by routing background extraction through the local opencode CLI instead of requiring Claude Code or Codex.
  • Package the OpenCode plugin server through npm exports/oc-plugin metadata with committed ESM dist files for OpenCode to load.

Changes

  • OpenCode adapter: adds plugin/opencode/server.mts, shared adapter helpers, payload normalization, assistant text buffering, and compiled dist files.
  • Installer/runtime: accepts opencode in hook/runtime dispatch, adds install/update/uninstall config patching, reuses backend/dashboard services, and routes OpenCode-started extraction through opencode-claude-compat.
  • Config handling: new project installs write root opencode.json; existing .opencode/opencode.json* files are honored when no root config exists; global install honors XDG_CONFIG_HOME; JSONC rewrites create .bak; invalid scalar plugin configs are rejected without overwrite.
  • Review hardening: pipes large extraction prompts through stdin, handles closed child stdin and hook stderr, drops the no-op pre-tool handler, preserves empty-start streamed text parts, avoids duplicate stop publishing, and preserves multi-segment assistant text for learning.
  • Docs: README top-level host banner and intro include OpenCode; OpenCode install docs now call out config precedence and opencode run --pure extraction model behavior.
  • Tests: expands OpenCode coverage for installer/config paths, JSONC parsing, provider detection, CLI bridge behavior, adapter payloads, assistant buffering, context injection, packaging, and simulated prompt-to-publish flow.

Test Plan

  • npm run build:opencode
  • node --check plugin/scripts/opencode-claude-compat.js
  • npx tsc -p plugin/opencode/tsconfig.json --noEmit
  • uv run --project plugin pytest tests/test_opencode_support.py -q (63 passed)
  • uv run --project plugin pytest -q (546 passed, 8 warnings)
  • uvx ruff check .
  • PYTHONPATH=plugin/src uvx pyright plugin/src/claude_smart tests/test_opencode_support.py
  • npm pack --dry-run --json verified OpenCode dist/internal.js, dist/server.mjs, scripts, and plugin files are packaged
  • uv build --project plugin --out-dir /tmp/claude-smart-uv-build-opencode-final3
  • git diff --check
  • Earlier live/sandbox validation on this PR covered OpenCode config install/uninstall/migration scenarios, opencode debug config, installed-package export loading, and OpenCode CLI bridge smoke with stdin prompt piping

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

OpenCode support is added across packaging, runtime plumbing, payload shaping, server bridging, CLI install/update/uninstall flows, and documentation. The PR also extends package exports, build steps, and test coverage for the new host.

Changes

OpenCode support rollout

Layer / File(s) Summary
Distribution and packaging
\.gitignore, Makefile, package.json, plugin/opencode/package.json, plugin/opencode/tsconfig.json, README.md, plugin/pyproject.toml, tests/test_opencode_support.py
OpenCode build outputs, package metadata, README content, and packaging checks are updated for the new host.
Payload and buffer
plugin/opencode/payload.ts, plugin/opencode/assistant-buffer.ts, tests/test_opencode_support.py
The new payload helpers normalize session, chat, tool, and stop fields, and AssistantBuffer accumulates assistant text from streamed message parts.
Server bridge and host runtime
bin/claude-smart.js, plugin/opencode/server.mts, plugin/scripts/backend-service.sh, plugin/scripts/hook_entry.sh, plugin/src/claude_smart/runtime.py, plugin/src/claude_smart/events/stop.py, tests/test_install_scripts.py, tests/test_opencode_support.py
OpenCode host detection is added to runtime and stop handling, bootstrap can skip Codex hook patching, and the server plus shell hooks route session events through backend, hook, and stop handlers.
Installer and config patching
bin/claude-smart.js, plugin/src/claude_smart/cli.py, scripts/setup-claude-smart.sh, tests/test_opencode_support.py
The CLI and managed setup flow accept opencode, patch OpenCode config files to add or remove claude-smart, and route install, update, and uninstall through host-specific commands.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • ReflexioAI/claude-smart#18: Shares host/runtime integration points in plugin/scripts/hook_entry.sh, plugin/src/claude_smart/runtime.py, and plugin/src/claude_smart/events/stop.py.
  • ReflexioAI/claude-smart#22: Modifies the same stop-event handling path in plugin/src/claude_smart/events/stop.py.
  • ReflexioAI/claude-smart#32: Overlaps with bin/claude-smart.js bootstrap and host-specific runtime wiring.

Poem

I hopped through builds with whiskers bright,
And found OpenCode tucked in light.
The hooks went thump, the buffers hummed,
New configs stirred, old paths got glummed.
Now every session lands with glee—
A carrot-coded victory!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding OpenCode host support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bin/claude-smart.js`:
- Around line 1829-1853: The install flow in runInstallOpenCode starts the
backend/dashboard before patchOpenCodePluginConfig, so a config failure can
leave services running after the command exits. Move the config patch step
earlier in runInstallOpenCode, or add cleanup in the patch failure path to stop
any services started by
bootstrapPluginRuntime/startBackendService/refreshDashboardService before
exiting. Keep the error handling around opencodeConfigPath(args) and
patchOpenCodePluginConfig so a failed install cannot leave dangling processes.

In `@plugin/opencode/assistant-buffer.ts`:
- Around line 31-40: The empty-string check in textFromPart is incorrectly
treating valid text parts as non-text, which causes later delta updates to be
ignored in the assistant buffer. Update textFromPart and the ignoredPartIDs
handling in assistant-buffer to accept empty text parts as text, so
message.part.delta can continue appending content and assistant.text() is not
left blank when the stream starts empty.

In `@plugin/src/claude_smart/cli.py`:
- Around line 1075-1077: The CLI availability probe in `cli.py` currently
accepts any existing file for `CLAUDE_SMART_CLI_PATH`, which can mark a
non-executable path as usable and fail later. Update the check in the path
handling logic to require both an existing file and executable permission by
adding an `os.access(..., os.X_OK)` validation alongside the current
`Path(...).is_file()` test. Keep the change localized to the
`CLAUDE_SMART_CLI_PATH` lookup path used by the probe.

In `@tests/test_opencode_support.py`:
- Around line 53-57: The tests around runtime host handling are mutating global
state and leaving `claude_smart.runtime` set to `opencode`, which can make later
tests order-dependent. Update the affected test cases in
`test_opencode_support.py` to restore the previous host/environment after each
test, preferably using a fixture or `monkeypatch` cleanup. Make sure the cleanup
covers the runtime helpers involved here, especially `runtime.set_host`,
`runtime.host`, `runtime.is_opencode`, and `runtime.agent_version`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57daa469-9cf8-413d-aa9a-33c588308d8f

📥 Commits

Reviewing files that changed from the base of the PR and between 14d2ac0 and cda9dab.

⛔ Files ignored due to path filters (4)
  • package-lock.json is excluded by !**/package-lock.json
  • plugin/opencode/dist/assistant-buffer.js is excluded by !**/dist/**
  • plugin/opencode/dist/payload.js is excluded by !**/dist/**
  • plugin/opencode/dist/server.mjs is excluded by !**/dist/**
📒 Files selected for processing (19)
  • .gitignore
  • Makefile
  • README.md
  • bin/claude-smart.js
  • package.json
  • plugin/opencode/assistant-buffer.ts
  • plugin/opencode/package.json
  • plugin/opencode/payload.ts
  • plugin/opencode/server.mts
  • plugin/opencode/tsconfig.json
  • plugin/scripts/backend-service.sh
  • plugin/scripts/hook_entry.sh
  • plugin/src/claude_smart/cli.py
  • plugin/src/claude_smart/events/stop.py
  • plugin/src/claude_smart/runtime.py
  • scripts/setup-claude-smart.sh
  • tests/test_install_scripts.py
  • tests/test_opencode_support.py
  • tests/test_stall_banner.py
💤 Files with no reviewable changes (1)
  • tests/test_stall_banner.py

Comment thread bin/claude-smart.js Outdated
Comment thread plugin/opencode/assistant-buffer.ts
Comment thread plugin/src/claude_smart/cli.py Outdated
Comment thread tests/test_opencode_support.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_opencode_support.py (1)

26-26: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer direct assignment over setattr with a constant attribute.

Ruff flags this (B010); a plain assignment is equivalent and clearer, and avoids a lint failure if the rule is enforced in CI.

♻️ Proposed change
-    setattr(runtime, "_current_host", previous_host)
+    runtime._current_host = previous_host
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_opencode_support.py` at line 26, Replace the use of setattr in the
test cleanup with a direct assignment to the _current_host attribute on runtime;
this is a simple B010 cleanup. Update the existing restore logic in the test
helper around the runtime object so the previous_host value is assigned
directly, keeping the behavior the same while satisfying Ruff.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_opencode_support.py`:
- Around line 459-469: The new Node-based test in
test_node_jsonc_parser_skips_comments_after_trailing_commas is missing the same
availability guard used by the other Node tests. Add the shutil_which_node()
None check before calling _run_node_script so the test returns early when Node
is unavailable, and keep the existing result assertions only in the Node-present
path.

---

Nitpick comments:
In `@tests/test_opencode_support.py`:
- Line 26: Replace the use of setattr in the test cleanup with a direct
assignment to the _current_host attribute on runtime; this is a simple B010
cleanup. Update the existing restore logic in the test helper around the runtime
object so the previous_host value is assigned directly, keeping the behavior the
same while satisfying Ruff.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 262eb244-aa90-4f0e-a61d-c10d1722d6f4

📥 Commits

Reviewing files that changed from the base of the PR and between cda9dab and a2e1a89.

⛔ Files ignored due to path filters (2)
  • plugin/opencode/dist/assistant-buffer.js is excluded by !**/dist/**
  • plugin/opencode/dist/server.mjs is excluded by !**/dist/**
📒 Files selected for processing (6)
  • bin/claude-smart.js
  • plugin/opencode/assistant-buffer.ts
  • plugin/opencode/server.mts
  • plugin/src/claude_smart/cli.py
  • scripts/setup-claude-smart.sh
  • tests/test_opencode_support.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • scripts/setup-claude-smart.sh
  • plugin/opencode/assistant-buffer.ts
  • plugin/opencode/server.mts
  • plugin/src/claude_smart/cli.py
  • bin/claude-smart.js

Comment thread tests/test_opencode_support.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
tests/test_opencode_support.py (1)

676-702: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add the missing Node-availability guard to this test.

This is still missing the shutil_which_node() early return used by the sibling Node-backed tests, so _run_node_script(...) can return None and make the suite fail instead of skip on Node-less environments.

Suggested fix
 def test_node_opencode_payload_normalizes_tool_contracts() -> None:
+    if shutil_which_node() is None:
+        return
     script = f"""
       import({json.dumps(str(REPO_ROOT / "plugin" / "opencode" / "dist" / "payload.js"))}).then((payload) => {{
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_opencode_support.py` around lines 676 - 702, The test
test_node_opencode_payload_normalizes_tool_contracts is missing the Node
availability skip used by the other Node-backed tests. Add the same early return
guard with shutil_which_node() before calling _run_node_script(...), so the test
skips cleanly when Node is unavailable instead of asserting on a None result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@tests/test_opencode_support.py`:
- Around line 676-702: The test
test_node_opencode_payload_normalizes_tool_contracts is missing the Node
availability skip used by the other Node-backed tests. Add the same early return
guard with shutil_which_node() before calling _run_node_script(...), so the test
skips cleanly when Node is unavailable instead of asserting on a None result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71763641-d59d-4a2a-8f08-014b5194690f

📥 Commits

Reviewing files that changed from the base of the PR and between a2e1a89 and c4ce000.

⛔ Files ignored due to path filters (2)
  • plugin/opencode/dist/payload.js is excluded by !**/dist/**
  • plugin/opencode/dist/server.mjs is excluded by !**/dist/**
📒 Files selected for processing (8)
  • README.md
  • bin/claude-smart.js
  • package.json
  • plugin/opencode/payload.ts
  • plugin/opencode/server.mts
  • plugin/pyproject.toml
  • plugin/src/claude_smart/cli.py
  • tests/test_opencode_support.py
💤 Files with no reviewable changes (1)
  • plugin/opencode/payload.ts
✅ Files skipped from review due to trivial changes (2)
  • plugin/pyproject.toml
  • README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • plugin/opencode/server.mts
  • bin/claude-smart.js
  • plugin/src/claude_smart/cli.py

@wenchanghan wenchanghan force-pushed the codex/opencode-host-integration branch from 85d1cd3 to 8cf914e Compare June 27, 2026 19:13

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_opencode_support.py (1)

196-289: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add a fresh-config case for the install path.

_patch_opencode_plugin_config() still falls back to the legacy "plugin" field when neither key exists. Current coverage misses that path, so a new config can be written with the deprecated key without a test failing. Add a no-key install test and assert it writes "plugins" only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_opencode_support.py` around lines 196 - 289, The install-path
coverage for _patch_opencode_plugin_config() is missing the fresh-config case
where neither "plugin" nor "plugins" exists, so add a test that starts from an
empty opencode config and verifies install writes only the modern "plugins"
field with claude-smart. Place it alongside the existing tests in
test_opencode_support.py and assert the legacy "plugin" key is not introduced
when no plugin field is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tests/test_opencode_support.py`:
- Around line 196-289: The install-path coverage for
_patch_opencode_plugin_config() is missing the fresh-config case where neither
"plugin" nor "plugins" exists, so add a test that starts from an empty opencode
config and verifies install writes only the modern "plugins" field with
claude-smart. Place it alongside the existing tests in test_opencode_support.py
and assert the legacy "plugin" key is not introduced when no plugin field is
present.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bddeea1-3ecf-4249-afa7-154a66704188

📥 Commits

Reviewing files that changed from the base of the PR and between 85d1cd3 and 8cf914e.

📒 Files selected for processing (4)
  • README.md
  • bin/claude-smart.js
  • plugin/src/claude_smart/cli.py
  • tests/test_opencode_support.py
✅ Files skipped from review due to trivial changes (1)
  • README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugin/src/claude_smart/cli.py
  • bin/claude-smart.js

@wenchanghan wenchanghan force-pushed the codex/opencode-host-integration branch from 8cf914e to edb0b71 Compare June 27, 2026 19:18
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