Skip to content

feat(opencode): add optional runtime overlay#3761

Open
Danigm-dev wants to merge 18 commits intosimstudioai:stagingfrom
Danigm-dev:feat/opencode-optional-runtime
Open

feat(opencode): add optional runtime overlay#3761
Danigm-dev wants to merge 18 commits intosimstudioai:stagingfrom
Danigm-dev:feat/opencode-optional-runtime

Conversation

@Danigm-dev
Copy link

@Danigm-dev Danigm-dev commented Mar 25, 2026

Summary

Adds OpenCode as an optional Sim capability without changing the default Sim deployment path or base UX.

This PR introduces:

  • the OpenCode workflow block, tools, API routes, and lib/opencode client/service wiring
  • async dropdown/combobox support needed by the OpenCode block selectors
  • an optional OpenCode runtime with local/prod Docker Compose overlays
  • minimal docs and env examples for opt-in setup
  • a configurable OPENCODE_REPOSITORY_ROOT contract so Sim can also target compatible external OpenCode deployments

The default Sim experience remains unchanged:

  • docker-compose.local.yml is unchanged
  • docker-compose.prod.yml is unchanged
  • the OpenCode block stays hidden unless NEXT_PUBLIC_OPENCODE_ENABLED=true

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

  • timeout 90s bunx vitest run blocks/blocks.test.ts from apps/sim
  • bunx vitest run lib/opencode/service.test.ts app/api/opencode/repos/route.test.ts app/api/tools/opencode/prompt/route.test.ts from apps/sim
  • docker compose -f docker-compose.local.yml -f docker-compose.opencode.local.yml config
  • env OPENCODE_SERVER_PASSWORD=change-me docker compose -f docker-compose.prod.yml -f docker-compose.opencode.yml config
  • Manual validation:
    • Sim only: base UX unchanged and OpenCode hidden by default
    • Sim + OpenCode local overlay: OpenCode service starts, healthcheck passes, repo catalog loads, and prompts execute from the OpenCode block

Reviewer focus:

  • Confirm the default Sim path is unchanged when OpenCode is not enabled
  • Review the OpenCode runtime contract and OPENCODE_REPOSITORY_ROOT handling for external deployments
  • Review shared dropdown/combobox async-loading changes for regressions

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Screenshots/Videos

Not included. The OpenCode block is hidden by default and only appears when NEXT_PUBLIC_OPENCODE_ENABLED=true. Manual validation covered both states and a successful prompt execution flow.

@cursor
Copy link

cursor bot commented Mar 25, 2026

PR Summary

Medium Risk
Adds new opt-in API surface and tool execution paths that talk to an external OpenCode runtime and stores session state, plus changes shared dropdown/combobox async fetching behavior that could affect other editor selectors.

Overview
Adds an opt-in OpenCode integration behind NEXT_PUBLIC_OPENCODE_ENABLED, including a new OpenCode workflow block wired to internal tools (opencode_prompt, opencode_list_repos, opencode_get_messages) and corresponding Next.js API routes for UI option loading and executor-only tool calls.

Introduces lib/opencode client/service code to talk to @opencode-ai/sdk, handle repo/provider/model/agent discovery, manage per-workflow session reuse via memory, and harden error handling to avoid leaking internal runtime URLs.

Updates shared editor dropdown/combobox components to support async options more safely (optional options, single-flight fetch, and dependency-change resets), and adds optional Docker Compose overlays plus a dedicated OpenCode runtime Dockerfile/scripts (repo sync, healthcheck, read-only permissions). Docs and .env.example are expanded with setup instructions, and Next/Vitest config adds @opencode-ai/sdk aliasing plus new dev:full:webpack script.

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

@vercel
Copy link

vercel bot commented Mar 25, 2026

@Danigm-dev is attempting to deploy a commit to the Sim Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 25, 2026

Greptile Summary

This PR introduces an optional OpenCode workflow block and its supporting runtime (API routes, service layer, Docker Compose overlays, and a container image) without touching the default Sim deployment path. The feature is correctly gated behind NEXT_PUBLIC_OPENCODE_ENABLED=true, the base compose files are untouched, and all new routes carry proper auth and workspace-access checks. The async dropdown/combobox enhancements (hasAttemptedOptionsFetch flag, in-flight ref, dependency reset) are clean and non-breaking.

Key findings from the review:

  • Security – credential leak in git-askpass.sh: When GIT_TOKEN is not set, GITHUB_TOKEN is returned as the git password for any host, not just github.com. This can expose a GitHub PAT to arbitrary non-GitHub HTTPS git servers.
  • Logic – overly broad session-retry trigger: shouldRetryWithFreshOpenCodeSession checks normalized.includes('session'), which matches errors like "session limit exceeded" and causes unnecessary new-session creation. The existing 'not found' and 'does not exist' checks already cover the intended cases.
  • Reliability – unpinned opencode-ai in Dockerfile: npm install -g opencode-ai with no version produces non-deterministic builds and risks API mismatches with the pinned @opencode-ai/sdk@0.8.0 in package.json.
  • Performance – existsSync('/.dockerenv') per request: This filesystem call happens on every createOpenCodeClient() invocation and should be cached at module load time.

Confidence Score: 3/5

  • Safe to merge for default Sim deployments (OpenCode is fully opt-in), but the OpenCode runtime path has a credential-leak risk and non-deterministic builds that should be fixed before any production OpenCode rollout.
  • Three targeted P1 fixes are needed before this is production-ready for the OpenCode path: the git-askpass credential leak, the overly broad session-retry check, and the unpinned Docker image version. None of these affect the default Sim experience, so the base product is unimpacted. Once those three are addressed, the PR is on a clear path to merge.
  • docker/opencode/git-askpass.sh (credential leak), apps/sim/lib/opencode/service.ts (retry logic), docker/opencode.Dockerfile (version pin)

Important Files Changed

Filename Overview
apps/sim/lib/opencode/service.ts Core OpenCode service layer — session management, repository resolution, and DB persistence. Contains an overly broad shouldRetryWithFreshOpenCodeSession check (normalized.includes('session')) that triggers unnecessary retries on unrelated errors.
docker/opencode/git-askpass.sh Git credential helper for the OpenCode container. Has a credential leak: GITHUB_TOKEN is returned as a fallback password for non-GitHub hosts when GIT_TOKEN is unset, potentially exposing the token to arbitrary servers.
docker/opencode.Dockerfile Container definition for the optional OpenCode runtime. Uses npm install -g opencode-ai without a version pin, making builds non-deterministic and potentially incompatible with the pinned @opencode-ai/sdk@0.8.0.
apps/sim/lib/opencode/client.ts OpenCode HTTP client factory. Calls existsSync('/.dockerenv') on every client creation; should be cached at module level. Otherwise clean credential/URL handling.
apps/sim/app/api/tools/opencode/prompt/route.ts Main prompt execution route — handles session reuse, retry logic, and soft-error responses. The retry strategy delegates to shouldRetryWithFreshOpenCodeSession (which has a separate issue). Route structure and Zod validation are solid.
apps/sim/blocks/blocks/opencode.ts Block definition with async option fetching for repository, provider, model, and agent dropdowns. Correctly gated behind NEXT_PUBLIC_OPENCODE_ENABLED. Dynamic imports from stores avoid circular dependencies.
docker/opencode/entrypoint.sh Container entrypoint — writes runtime env with restricted permissions (umask 077), generates OpenCode config with deny-by-default permissions, installs cron for repo sync, and starts the server as a non-root user via gosu. Security posture is good.
docker-compose.opencode.yml Production overlay for the OpenCode service. Uses expose (not ports) to keep the service internal to the Docker network, requires OPENCODE_SERVER_PASSWORD, and gates simstudio on a healthy OpenCode service.

Sequence Diagram

sequenceDiagram
    participant UI as OpenCode Block (UI)
    participant API as /api/opencode/* (Next.js)
    participant Svc as lib/opencode/service
    participant OC as OpenCode Server
    participant DB as Sim DB (memory table)

    UI->>API: GET /api/opencode/repos?workspaceId=…
    API->>Svc: listOpenCodeRepositories()
    Svc->>OC: client.project.list()
    OC-->>Svc: Project[]
    Svc-->>API: OpenCodeRepositoryOption[]
    API-->>UI: { data: [...] }

    UI->>API: GET /api/opencode/providers?workspaceId=…&repository=…
    API->>Svc: listOpenCodeProviders(repository)
    Svc->>OC: client.config.providers({ directory })
    OC-->>Svc: Provider[]
    Svc-->>API: OpenCodeProviderOption[]
    API-->>UI: { data: [...] }

    Note over UI,DB: Workflow execution (tool call)
    UI->>API: POST /api/tools/opencode/prompt
    API->>Svc: resolveOpenCodeRepositoryOption(repo)
    API->>DB: getStoredOpenCodeSession(wsId, memKey)
    DB-->>API: null (no stored session)
    API->>OC: createOpenCodeSession(repo, title)
    OC-->>API: { id: sessionId }
    API->>OC: promptOpenCodeSession(…)
    OC-->>API: { content, threadId, cost }
    API->>DB: storeOpenCodeSession(wsId, memKey, session)
    API-->>UI: { success: true, output: { content, threadId, cost } }
Loading

Comments Outside Diff (1)

  1. apps/sim/lib/opencode/service.ts, line 2431-2436 (link)

    P1 Overly broad session retry trigger

    normalized.includes('session') will match any error message that contains the word "session" — for example "session limit exceeded", "rate limit per session", or "session token invalid". This causes the route to spin up a brand-new OpenCode session unnecessarily, wasting resources and likely failing again with the same error.

    The intent appears to be detecting stale / not-found sessions. The checks for '404', 'not found', and 'does not exist' already cover the common cases. The standalone 'session' check should be removed or replaced with a more specific pattern:

Reviews (1): Last reviewed commit: "test(opencode): cover route contracts" | Re-trigger Greptile

@Danigm-dev
Copy link
Author

Addressed the actionable Greptile findings in the latest branch state.

  • shouldRetryWithFreshOpenCodeSession() was already narrowed in 6160d1c16, so the broad includes("session") case is no longer present.
  • docker/opencode/git-askpass.sh no longer falls back to GITHUB_TOKEN for non-GitHub hosts.
  • docker/opencode.Dockerfile now pins opencode-ai@0.8.0 to keep the runtime aligned with the pinned SDK version.
  • apps/sim/lib/opencode/client.ts now caches Docker runtime detection at module load instead of checking /.dockerenv on every client creation.

Validation run after these changes:

  • bunx vitest run lib/opencode/service.test.ts app/api/opencode/repos/route.test.ts app/api/tools/opencode/prompt/route.test.ts
  • manual shell check for docker/opencode/git-askpass.sh host-specific token behavior

Latest fix commit: 8ec0c5aea.

Copy link

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

Fix All in Cursor

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


- `apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/combobox/combobox.tsx`
- `apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/dropdown/dropdown.tsx`
- `apps/sim/app/api/tools/opencode/prompt/route.ts`
Copy link

Choose a reason for hiding this comment

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

Internal branch tracking document accidentally committed

Low Severity

PR-3761-BRANCH-STATUS.md is a personal development tracking document describing branch management, worktree state, overlap boundaries, and reading guides for the PR author. It contains internal process notes like "at the end of this session" and "local/remote divergence: none" that are not relevant to the repository. This file appears to have been accidentally included in the commit.

Fix in Cursor Fix in Web

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