Skip to content

feat: Support connecting to external OpenCode server #193

@hsteude

Description

@hsteude

Context

Currently, the OpenCode UI and server run together in the same Kubeflow Notebook pod. For the new Kubernetes sandbox feature, we need the ability to connect the UI to an external OpenCode server running in a separate sandbox pod.

Use Case

┌─────────────────────┐         ┌─────────────────────┐
│  Kubeflow Notebook  │         │   Sandbox Pod       │
│  (UI only)          │ ──────► │   (OpenCode Server) │
│                     │   API   │                     │
└─────────────────────┘         └─────────────────────┘
  • User accesses UI from Kubeflow Notebook
  • UI connects to OpenCode server running in isolated sandbox
  • Enables better security isolation and resource management

Architecture: Proxy Approach

The Problem

The browser can only communicate with the Notebook pod through Kubeflow's ingress. The Sandbox pod has no external route:

Browser
   │
   ▼
┌─────────────────────────────────────────────────────────┐
│  Kubeflow Gateway / Ingress                             │
│  (only /notebook/ns/name/* is routed)                   │
└─────────────────────────────────────────────────────────┘
   │
   ▼
┌─────────────────────────┐     ┌─────────────────────────┐
│  Notebook Pod (UI)      │     │  Sandbox Pod            │
│  /notebook/ns/name/*    │  ✗  │  (not exposed!)         │
└─────────────────────────┘     └─────────────────────────┘

Solution: User-facing Server Picker + Backend Proxy

Frontend (User-facing):

  • User can add/edit/remove external server URLs in Settings
  • Server list with health indicators
  • Switch between servers

Backend (Proxy):

  • UI sends requests to local backend with target server info
  • Backend proxies to the user-specified external server
  • No direct browser-to-sandbox connection needed
Browser                    Notebook Pod                 Sandbox Pod
   │                           │                            │
   │  /api/proxy?target=...    │                            │
   │ ─────────────────────────►│                            │
   │                           │  http://sandbox-svc:4096   │
   │                           │ ──────────────────────────►│
   │                           │                            │

Reference: Upstream OpenCode Implementation

The upstream OpenCode Desktop app already has this feature! Key files:

  • packages/app/src/components/dialog-select-server.tsx - Server picker dialog
  • packages/app/src/context/server.tsx - Server state management

Key features we can adapt:

  • ServerConnection.Http type with URL + optional username/password
  • normalizeServerUrl() for URL validation
  • Health check polling (every 10 seconds)
  • Server list persistence in LocalStorage
  • Default server selection

Requirements

Phase 1: Basic Implementation

  • Add ServerContext for managing server connections (adapt from upstream)
  • Create DialogSelectServer component for adding/editing servers
  • Modify serve-ui.ts to proxy requests to user-selected server
  • Add health check endpoint and status indicator
  • Persist server list in LocalStorage

Phase 2: Polish

  • Server status indicator in header/sidebar
  • Graceful error handling (server unavailable, network issues)
  • Quick server switcher keyboard shortcut

Technical Implementation

Files to create/modify

New files:

  • app-prefixable/src/context/server.tsx - Server state management
  • app-prefixable/src/components/server-dialog.tsx - Server picker UI

Modify:

  • docker/serve-ui.ts - Add proxy endpoint for external servers
  • app-prefixable/src/utils/path.ts - Integrate with server context

Proxy endpoint

// serve-ui.ts
app.all("/api/proxy/*", async (req) => {
  const targetServer = req.headers.get("X-Target-Server")
  if (!targetServer) return new Response("Missing target server", { status: 400 })
  
  // Proxy request to target server
  const targetUrl = new URL(req.url.replace("/api/proxy", ""), targetServer)
  return fetch(targetUrl, {
    method: req.method,
    headers: req.headers,
    body: req.body,
  })
})

Security Considerations

  • Should we restrict allowed server URLs? (e.g., only *.svc.cluster.local)
  • Consider adding authentication between UI and sandbox server
  • Rate limiting on proxy endpoint?

Status

⚠️ EXPERIMENTAL - This feature is in early development for the sandbox integration project. PRs should NOT be merged to main until the sandbox infrastructure is ready.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions