Skip to content

feat: add opencode provider support#1758

Open
nexxeln wants to merge 11 commits intopingdotgg:mainfrom
nexxeln:nxl/opencode-adapter
Open

feat: add opencode provider support#1758
nexxeln wants to merge 11 commits intopingdotgg:mainfrom
nexxeln:nxl/opencode-adapter

Conversation

@nexxeln
Copy link
Copy Markdown

@nexxeln nexxeln commented Apr 5, 2026

summary

  • add OpenCode as a first-class provider across contracts, server runtime wiring, and the web model picker/settings flow
  • implement the OpenCode adapter with SDK-based session streaming, approvals, question handling, and assistant/tool event projection fixes
  • add OpenCode-backed git text generation for thread titles, branch names, commit messages, and PR content

testing

  • bun fmt
  • bun lint
  • PATH="/Users/nxl/.local/state/fnm_multishells/11922_1773576539803/bin:$PATH" bun run typecheck

Note

High Risk
Introduces a new end-to-end provider (server process spawning/connection, session streaming, and provider registry wiring) and updates model-selection handling across the web app, increasing risk of lifecycle/streaming bugs or subtle option serialization differences.

Overview
Adds first-class opencode provider support across server and web, including provider discovery/health probing, provider registry wiring, settings persistence, and UI selection.

Implements an OpenCode runtime + adapter using @opencode-ai/sdk to start/connect to an OpenCode server, create/abort sessions, stream events, and map permissions/questions/tool lifecycle into the app’s runtime event model.

Adds OpenCode-backed git text generation (commit, PR, branch, thread title) with a shared server reuse mechanism (idle TTL) and structured JSON-schema decoding, plus updates the web composer/settings to support OpenCode-specific model traits (variant/agent) and hides the Build/Plan toggle where unsupported.

Reviewed by Cursor Bugbot for commit c69f377. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add OpenCode as a supported AI provider across server, composer, and settings UI

  • Registers opencode as a valid ProviderKind in contracts, settings, and session logic; adds default model openai/gpt-5 and display name 'OpenCode'.
  • Introduces OpenCodeProvider that probes either a local CLI binary or a configured server URL, returning human-readable status messages for missing CLI, auth failures, and connection errors.
  • Adds OpenCodeAdapter and wires it into the provider adapter registry and the routing text-generation layer.
  • Adds OpenCodeTextGeneration which reuses a single OpenCode server process across requests and shuts it down after 30s of inactivity.
  • Exposes server URL and server password fields in the settings UI and adds 'Variant' and 'Agent' trait pickers in the composer for OpenCode models.
  • Hides the 'Build'/'Plan' interaction mode toggle in the composer when the active provider is opencode.
  • Risk: the shared server process lifecycle (reuse, idle TTL, binaryPath mismatch warnings) introduces new stateful behavior that could affect concurrent requests or provider switches.

Macroscope summarized c69f377.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 5, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 966b4598-8f49-4b45-9015-c1fee9ff569e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ 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 and usage tips.

@github-actions github-actions bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Apr 5, 2026
Comment thread apps/server/src/git/Layers/OpenCodeTextGeneration.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 5, 2026

Approvability

Verdict: Needs human review

This PR adds a complete new provider integration (OpenCode) with ~4000 lines of new code including adapters, runtime management, UI integration, and settings. New features of this scope require human review. Additionally, there are unresolved medium-severity review comments about server process management and text streaming logic that should be addressed.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
Comment thread apps/server/src/provider/opencodeRuntime.ts
- preserve OpenCode variant and agent selections
- hide unsupported interaction mode controls
- add provider snapshot and UI coverage
Comment thread apps/server/src/provider/Layers/OpenCodeProvider.ts Outdated
}),
}),
(server) => Effect.sync(() => server.close()),
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Each text generation spawns and tears down server process

Medium Severity

Every call to runOpenCodeJson (commit messages, PR content, branch names, thread titles) spawns a brand new OpenCode server child process via startOpenCodeServerProcess, waits for it to become ready, creates a session, sends a single prompt, then immediately shuts down the server. This involves finding a free port, spawning a process, waiting up to 5 seconds for startup, and then killing it — all for a single text generation request. The adapter (OpenCodeAdapter) maintains a long-lived server per session, but the text generation layer does not reuse any server instance.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5ebbf95. Configure here.

- Keep a warm OpenCode process alive across back-to-back requests
- Close the shared server after the idle TTL and add coverage for reuse
- Reuse an existing OpenCode server for provider sessions and text generation
- Add `serverUrl` to settings, UI, and runtime connection handling
- Update tests for configured-server behavior
Comment thread apps/server/src/provider/Layers/OpenCodeProvider.ts Outdated
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts Outdated
Comment thread apps/web/src/components/chat/composerProviderRegistry.tsx Outdated
- Plumb OpenCode server passwords through settings and contracts
- Send basic auth to external OpenCode servers when configured
- Surface the password field in the web settings UI
Comment thread apps/server/src/git/Layers/OpenCodeTextGeneration.ts Outdated
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/web/src/components/chat/TraitsPicker.tsx Outdated
@Coder-soft
Copy link
Copy Markdown

is it going to be implemented?

Co-authored-by: codex <codex@users.noreply.github.com>

# Conflicts:
#	apps/web/src/components/ChatView.tsx
#	apps/web/src/components/chat/TraitsPicker.browser.tsx
#	apps/web/src/components/chat/composerProviderRegistry.test.tsx
#	apps/web/src/components/chat/composerProviderRegistry.tsx
#	apps/web/src/composerDraftStore.ts
#	packages/contracts/src/orchestration.ts
#	packages/contracts/src/settings.ts
Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/web/src/components/settings/SettingsPanels.tsx Outdated
Comment thread apps/web/src/components/chat/ChatComposer.tsx
Comment thread apps/server/src/git/Layers/OpenCodeTextGeneration.ts
- Reuse active shared servers safely under binary-path races
- Emit assistant text updates from both message and part events
- Make OpenCode permissions default to ask and disable password autofill
- Fix settings panel test mounting and composer layout
const makeRoutingTextGeneration = Effect.gen(function* () {
const codex = yield* CodexTextGen;
const claude = yield* ClaudeTextGen;
const openCode = yield* OpenCodeTextGen;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inconsistent indentation suggests wrong scope level

Low Severity

The openCode variable declaration uses 2-space indentation while the adjacent codex and claude declarations use 4-space indentation. The same pattern appears in ProviderRegistry.ts line 42 (openCodeProvider), and in several lines of ProviderAdapterRegistry.test.ts. While JavaScript doesn't use indentation for scoping, this inconsistency is misleading and suggests the line may be at a different scope level than its siblings.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8a3159c. Configure here.

Comment thread apps/server/src/provider/Layers/OpenCodeAdapter.ts
Comment thread apps/server/src/git/Layers/OpenCodeTextGeneration.ts
- Fall back to default OpenCode settings when provider config is missing
- Abort newly started remote SDK sessions when a concurrent winner already exists
- Co-authored-by: codex <codex@users.noreply.github.com>
sessions.delete(context.session.threadId);
context.server.close();
const turnId = context.activeTurnId;
void emitPromise({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Low Layers/OpenCodeAdapter.ts:349

At lines 349 and 357, void emitPromise(...) creates promises that are never awaited or caught. If Effect.runPromiseWith throws, the rejection becomes unhandled and Node.js will emit an unhandledRejection event. Consider awaiting these promises or adding .catch() handlers to ensure errors are handled explicitly.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/provider/Layers/OpenCodeAdapter.ts around line 349:

At lines 349 and 357, `void emitPromise(...)` creates promises that are never awaited or caught. If `Effect.runPromiseWith` throws, the rejection becomes unhandled and Node.js will emit an `unhandledRejection` event. Consider awaiting these promises or adding `.catch()` handlers to ensure errors are handled explicitly.

Evidence trail:
apps/server/src/provider/Layers/OpenCodeAdapter.ts lines 336-339 (definition of emit and emitPromise), lines 349 and 357 (void emitPromise calls), lines 382, 407, 483, 544, 551, 573, 591, 615, 630, 649, 667, 691, 704 (awaited emitPromise calls showing the expected pattern)

Copy link
Copy Markdown
Contributor

@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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c69f377. Configure here.

context.emittedTextLengthByPartId.set(
event.properties.partID,
previousLength + delta.length,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Inconsistent text length tracking between delta and update handlers

Medium Severity

The message.part.delta handler tracks emitted text length by adding delta.length to emittedTextLengthByPartId, while emitAssistantTextDelta (called from message.part.updated) sets it to text.length (absolute). If both event types fire for overlapping content on the same part, the tracked length becomes the sum of both, exceeding the actual text length. Subsequent emitAssistantTextDelta calls would then skip legitimate new text (because text.length > inflatedPreviousLength is false), causing lost streaming output.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c69f377. Configure here.

Pipyakas added a commit to Pipyakas/t3code that referenced this pull request Apr 15, 2026
- VSCode launch.json and tasks.json
- Windows-only release workflow (release-fork.yml)
- Android Capacitor build setup
- Standalone Linux server build with systemd service install
- VCS mode setting (git/disabled)

Deferred: OpenCode and Gemini-cli implementation (PR pingdotgg#1758, pingdotgg#1983)
Pipyakas added a commit to Pipyakas/t3code that referenced this pull request Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants