Skip to content

[feature] Add OpenCode and Hermes launchers (with Windows support) #448

Description

@hyeonggyu

Problem

Switchyard already ships switchyard launch subcommands for Claude Code, Codex CLI, and OpenClaw, but there is no equivalent for two widely-used coding agents:

  • OpenCode (opencode-ai) — no launcher, so users must hand-write a transient OPENCODE_CONFIG_DIR with an @ai-sdk/openai-compatible provider to point it at a Switchyard deployment.
  • Hermes (hermes-agent) — no launcher, so users must manually override OPENROUTER_BASE_URL / OPENROUTER_API_KEY plus --provider custom -m <route>.

Both are otherwise well supported by Switchyard's native, health-aware multi-endpoint serving path, but wiring them up is error-prone and undocumented. Additionally, none of the existing launchers run on Windows: shell_tui.py imports pty/fcntl/termios/tty at module load, banner_pause assumes POSIX raw mode, binary discovery relies on the executable bit, and *.cmd/*.bat npm shims cannot be exec'd directly by CreateProcess.

Proposed solution

Add two peer launchers in switchyard/cli/launchers/, mirroring the existing claude/codex/openclaw shape:

  • opencode_launcher.py — writes a transient opencode.json under a temp dir, declares a switchyard provider (@ai-sdk/openai-compatible, base URL → http://127.0.0.1:<port>/v1), selects the route via the config's model field, and launches opencode with the user's own command forwarded verbatim (no injected -m, which non-model subcommands like serve/debug reject). Workspace is cleaned up on success/error/interrupt.
  • hermes_launcher.py — sets OPENROUTER_BASE_URL/OPENROUTER_API_KEY to the local proxy and runs hermes --provider custom -m <route> (interactive chat when nothing is forwarded; otherwise the user's command verbatim). The user's ~/.hermes/config.yaml is never touched.

Both are registered in launch_command.py (cmd_launch_opencode, cmd_launch_hermes) and switchyard_cli.py (launcher_parsers), with --model and forwarded args surfacing exactly like the existing launchers.

Windows support is handled in the shared runtime rather than duplicated per-launcher:

  • run_command() — routes *.cmd/*.bat shims through cmd.exe via subprocess.run(..., shell=True); native executables run shell-less.
  • is_executable_file() — treats any existing file as runnable on Windows (no exec bit); POSIX keeps the os.X_OK check.
  • stdin_is_tty() returns False off-POSIX, so the interactive PTY footer is skipped and the launcher falls back to the plain subprocess path.
  • shell_tui.py imports pty/fcntl/termios/tty under an os.name guard so the module still imports on Windows.
  • banner_pause() no-ops off-POSIX.
  • Debug/state logging uses %LOCALAPPDATA% on Windows.

Alternatives considered

  • One launcher flag per routing algorithm — rejected; routing config belongs in the TOML deployment, not the launcher surface.
  • Inject -m <model> on the OpenCode CLI — rejected; opencode rejects -m on non-model subcommands (serve, debug, models, …), so model selection lives in the transient config instead.
  • A separate Windows-only launcher variant — rejected; a single shared runtime branch keeps one code path and one set of tests.

Scope notes

  • Extension of an existing component (launcher surface), not a new chain role; no changes to RequestProcessor/LLMBackend/etc.
  • Does not touch the public API in switchyard/__init__.py.__all__.
  • CLI-only (nemo-switchyard[cli]); no new runtime dependencies. @ai-sdk/openai-compatible is bundled in the opencode binary.
  • No backward-compatibility concerns: purely additive subcommands and a behavior-preserving shared-runtime change on POSIX.
  • Deploy/route wiring for a specific host (e.g. OpenRouter routes.toml) is intentionally out of scope for the launcher patch.

Additional context

  • Prior art: the existing claude_code_launcher.py, codex_cli_launcher.py, and openclaw_launcher.py establish the launcher contract (transient config, supervision, cleanup).
  • @ai-sdk/openai-compatible provider schema: https://opencode.ai/docs/providers/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions