Skip to content

feat: opencode sdk#1

Merged
kalil0321 merged 6 commits into
mainfrom
feat/opencode
Dec 25, 2025
Merged

feat: opencode sdk#1
kalil0321 merged 6 commits into
mainfrom
feat/opencode

Conversation

@kalil0321
Copy link
Copy Markdown
Owner

@kalil0321 kalil0321 commented Dec 25, 2025

Implement OpenCode AI SDK.

We didn't use the Python SDK as it is outdated.
(We = antigravity + claude opus + me)

Used the API.

Might need to add a warning to tell user to start opencode with a subprocess.


Note

Introduces an extensible engineering layer and adds an alternative OpenCode backend alongside the existing Claude flow.

  • New abstraction: BaseEngineer centralizes shared logic (prompt build, paths, UI, message store)
  • OpenCode backend: OpenCodeEngineer with httpx API calls, SSE event streaming, auto‑permission handling, usage tracking, and OpenCodeUI for live updates
  • SDK dispatch: run_reverse_engineering now selects OpenCodeEngineer or ClaudeEngineer based on sdk; keeps APIReverseEngineer alias
  • CLI updates: filtered slash‑command completion, history auto-suggest, new /settings option to choose sdk, and passes sdk into engineer run; improved help/outputs
  • Config: adds sdk setting (defaults to claude)
  • Browser UX: faster polling for window close and status while saving HAR

Written by Cursor Bugbot for commit 319f9d2. This will update automatically on new commits. Configure here.

@kalil0321
Copy link
Copy Markdown
Owner Author

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/reverse_api/opencode_engineer.py Outdated
@kalil0321
Copy link
Copy Markdown
Owner Author

@codex review

Repository owner deleted a comment from chatgpt-codex-connector Bot Dec 25, 2025
@kalil0321
Copy link
Copy Markdown
Owner Author

@cursor review

@cursor
Copy link
Copy Markdown

cursor Bot commented Dec 25, 2025

Skipping Bugbot: Bugbot is disabled for this repository

@kalil0321
Copy link
Copy Markdown
Owner Author

@cursor review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/reverse_api/engineer.py
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

This PR is being reviewed by Cursor Bugbot

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread src/reverse_api/opencode_engineer.py Outdated
try:
await asyncio.wait_for(event_task, timeout=600.0)
except asyncio.TimeoutError:
self.opencode_ui.error("Session timed out (10 min)")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Timeout error returns success result instead of None

When asyncio.TimeoutError is caught at line 97, an error message is displayed but _last_error is never set. The code then continues to line 103 where the check if self._last_error passes (since it's None), causing execution to fall through to line 108-118 which returns a success result. This means a timed-out session incorrectly returns a result with a script_path that may not exist instead of returning None to indicate failure.

Additional Locations (1)

Fix in Cursor Fix in Web

event_sid = properties.get("sessionID")
if event_sid != self._session_id:
debug_log(f"session.error for other session {event_sid}, ignoring")
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Session errors silently ignored when sessionID is missing

The comparison if event_sid != self._session_id at line 261 will evaluate to True when sessionID is absent from the error event (making event_sid equal to None). This causes the error to be silently ignored with a debug log saying it's for "other session". If the OpenCode API ever sends error events without a sessionID field, actual errors affecting this session could be missed entirely.

Fix in Cursor Fix in Web

Comment thread src/reverse_api/opencode_engineer.py Outdated

except httpx.ReadError as e:
debug_log(f"ReadError (normal on session end): {e}")
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unexpected disconnection treated as success instead of error

When an httpx.ReadError occurs (e.g., the connection drops unexpectedly before session.idle is received), the exception is caught and silently ignored with pass, but _last_error is never set. This differs from the generic Exception handler at line 295-296 which does set _last_error. Since _last_error remains None, the check at line 103 passes and execution continues to the success path, returning a result with a script_path that may not exist. The comment suggests ReadError is "normal on session end," but normal session completion returns via the session.idle event handler, not via exception.

Fix in Cursor Fix in Web

@kalil0321 kalil0321 merged commit 563f512 into main Dec 25, 2025
1 check passed
@kalil0321 kalil0321 deleted the feat/opencode branch December 25, 2025 17:12
shaneholloman pushed a commit to shaneholloman/reverse-api-engineer that referenced this pull request May 6, 2026
Implements the first two items of the agent-friendliness backlog (kalil0321#62):

kalil0321#1 TTY-detect at REPL entry
  Without a TTY and no subcommand, the prompt_toolkit REPL would block on
  stdin forever. Now detects `not sys.stdin.isatty()`, prints `--help` to
  stderr, and exits 2 (misuse). Subcommands are unaffected.

kalil0321#2 engineer --json / --no-interactive
  Mirrors the agent-mode contract (PR kalil0321#61): --json redirects Rich output
  to stderr and emits a stable JSON payload on stdout. New helper
  `_build_engineer_payload` produces the schema {schema_version, status,
  run_id, prompt, fresh, script_path, usage, error}. KeyboardInterrupt
  surfaces as `error: "interrupted"`. Exit codes: 0 ok, 1 runtime error.
  --no-interactive is reserved for symmetry (engineer mode has no
  questionary prompts internally).

Also surfaces `--json` / `--no-interactive` in the root --help (item kalil0321#6
partial).

Adds tests/test_cli_followups.py (14 tests) covering TTY-detect end-to-
end via subprocess, _build_engineer_payload edge cases, and the click
wiring for engineer --json (success, not-found, KeyboardInterrupt,
exception, prompt-vs-fresh threading).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
kalil0321 added a commit that referenced this pull request May 20, 2026
The "Safari can't connect to the server" failure mode after an
abrupt rae exit (force-quit, kill, crash, kernel panic) was a clean
data loss: the snapshot of the user's pre-rae proxy settings lived
only in `AppState.proxySnapshot` (in-memory). When the process died
without reaching `restoreProxyBeforeExit`, the system proxy stayed
pointing at 127.0.0.1:<our port> with nothing listening — Safari /
Chrome / Atlas all hard-fail on every request — and the user's real
proxy config (Wi-Fi defaults, corporate proxy, VPN settings) was
gone with no way for the next launch to bring it back.

Two complementary fixes:

1. **Persist the snapshot to disk.** Inside applySystemProxy, write
   the snapshot to <Application Support>/ReverseAPI/proxy-snapshot.json
   *before* flipping networksetup, so even a crash between
   `enable()` and the in-memory assignment leaves enough state for
   the next launch to recover. On `restoreSystemProxy` /
   `disableCurrentRaeProxy` / `restoreProxyBeforeExit` we delete the
   sentinel file alongside the in-memory clear.

   AppState.init now also reads the sentinel back into proxySnapshot
   at boot, so `recoverStaleSystemProxyOnLaunch` can do a real
   restore (preserving the user's previous corporate / Wi-Fi proxy
   settings) instead of a blanket disable that dropped everything.
   The recovery message has two variants now: "Restored previous
   proxy settings from a stale session." when the snapshot survived,
   "Recovered stale device proxy from a previous session." when
   only a disable was possible.

   ProxyServiceSnapshot grows a Codable conformance + explicit
   public init (the synthesized memberwise was internal so the
   ReverseAPI target couldn't construct one). The shape stays
   identical.

2. **Catch SIGTERM / SIGINT / SIGHUP.** AppDelegate.installSignalHandlers
   wires DispatchSource.makeSignalSource on the main queue for each
   of the three signals — Activity Monitor's "Quit"/"Force Quit",
   `kill <pid>`, terminal Ctrl-C from `swift run`, and shutdown all
   hit one of these. The handler flips the existing isTerminating
   guard, calls AppLifecycle.shared.restoreProxyBeforeExit()
   synchronously (we have no async budget once the OS decided to
   kill us), then re-raises the signal with SIG_DFL so the process
   exits with the correct termination status.

   SIGKILL (kill -9) and a true crash still bypass everything —
   the kernel doesn't let userspace catch those — but the disk
   sentinel from fix #1 covers that path too: next launch restores
   the snapshot, no manual `networksetup -setwebproxystate Wi-Fi off`
   gymnastics needed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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