chopratejas--headroom
0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
134 行
5.1 KiB
Python
134 行
5.1 KiB
Python
"""Runtime helpers for OpenCode integrations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
from collections.abc import Mapping
|
|
from pathlib import Path
|
|
|
|
from headroom.mcp_registry.install import DEFAULT_PROXY_URL
|
|
|
|
from .config import HEADROOM_OPENCODE_PLUGIN, headroom_provider_entry
|
|
|
|
|
|
def proxy_base_url(port: int) -> str:
|
|
"""Return the local proxy base URL used by OpenCode integrations."""
|
|
return f"http://127.0.0.1:{port}/v1"
|
|
|
|
|
|
def headroom_opencode_plugin_path() -> str | None:
|
|
"""Return the absolute path to the built OpenCode transport plugin, or None.
|
|
|
|
OpenCode loads a plugin from an absolute file path (verified against
|
|
opencode 1.17). The plugin's loader entry exports ONLY the plugin function
|
|
(``plugins/opencode/dist/entry.opencode.js``) — the library barrel cannot
|
|
be loaded directly ("Plugin export is not a function"). Returns ``None``
|
|
when the plugin has not been built (e.g. a pip-only install that does not
|
|
ship ``plugins/``), in which case wrap falls back to the native-provider
|
|
baseURL override, which already covers Anthropic/OpenAI.
|
|
|
|
``HEADROOM_OPENCODE_PLUGIN_PATH`` overrides the resolved path.
|
|
"""
|
|
override = os.environ.get("HEADROOM_OPENCODE_PLUGIN_PATH", "").strip()
|
|
if override:
|
|
return override if Path(override).is_file() else None
|
|
# runtime.py → opencode → providers → headroom → <repo root>
|
|
candidate = (
|
|
Path(__file__).resolve().parents[3] / "plugins" / "opencode" / "dist" / "entry.opencode.js"
|
|
)
|
|
return str(candidate) if candidate.is_file() else None
|
|
|
|
|
|
def build_opencode_config_content(
|
|
*,
|
|
port: int,
|
|
include_mcp: bool = True,
|
|
include_plugin: bool = True,
|
|
) -> dict[str, object]:
|
|
"""Build JSON payload for ``OPENCODE_CONFIG_CONTENT``.
|
|
|
|
Two complementary routing layers (both verified against opencode 1.17):
|
|
|
|
1. **Native-provider baseURL override** — points OpenCode's built-in
|
|
``anthropic`` / ``openai`` providers at the proxy. Keeps native provider
|
|
identity (model metadata, output-token limits) and reuses the user's own
|
|
API keys (env / ``opencode auth``); the proxy forwards upstream by path
|
|
(``/v1/messages`` → Anthropic, ``/v1/chat/completions`` → OpenAI). This
|
|
is the reliable always-on layer and the only one shipped pip-only
|
|
installs need.
|
|
|
|
2. **Transparent transport plugin** — when the local plugin is built, it is
|
|
loaded by absolute path and patches ``fetch``/``http`` to reroute *every*
|
|
provider's traffic through the proxy, tagging the real upstream via
|
|
``x-headroom-base-url``. This covers providers we don't name (Gemini,
|
|
Copilot, custom gateways) and providers added mid-session. The plugin
|
|
self-configures from ``HEADROOM_PROXY_URL`` (set in :func:`build_launch_env`).
|
|
Loopback URLs are not double-routed, so it coexists with layer 1.
|
|
|
|
ponytail: config-level ``options.baseURL`` is reliable where the env-var
|
|
override (``ANTHROPIC_BASE_URL``) is not — verified against opencode 1.17.
|
|
"""
|
|
base_url = proxy_base_url(port)
|
|
config: dict[str, object] = {
|
|
"provider": {
|
|
"anthropic": {"options": {"baseURL": base_url}},
|
|
"openai": {"options": {"baseURL": base_url}},
|
|
"headroom": headroom_provider_entry(port),
|
|
}
|
|
}
|
|
if include_mcp:
|
|
proxy_url = f"http://127.0.0.1:{port}"
|
|
mcp_entry: dict[str, object] = {
|
|
"type": "local",
|
|
"command": ["headroom", "mcp", "serve"],
|
|
"enabled": True,
|
|
}
|
|
if proxy_url != DEFAULT_PROXY_URL:
|
|
mcp_entry["environment"] = {"HEADROOM_PROXY_URL": proxy_url}
|
|
config["mcp"] = {
|
|
"headroom": mcp_entry,
|
|
}
|
|
if include_plugin:
|
|
plugin_path = headroom_opencode_plugin_path()
|
|
if plugin_path:
|
|
# Plain absolute-path string; the plugin reads HEADROOM_PROXY_URL
|
|
# from the launch env (build_launch_env sets it).
|
|
config["plugin"] = [plugin_path]
|
|
return config
|
|
|
|
|
|
def build_launch_env(
|
|
port: int,
|
|
environ: Mapping[str, str] | None = None,
|
|
project: str | None = None,
|
|
*,
|
|
include_mcp: bool = True,
|
|
include_plugin: bool = True,
|
|
) -> tuple[dict[str, str], list[str]]:
|
|
"""Build environment variables for launching OpenCode through Headroom.
|
|
|
|
``OPENCODE_CONFIG_CONTENT`` carries Headroom provider/MCP/plugin config.
|
|
Existing provider/base URL environment variables are preserved. When the
|
|
transport plugin is loaded, ``HEADROOM_PROXY_URL`` tells it which proxy to
|
|
route to.
|
|
"""
|
|
env = dict(environ or os.environ)
|
|
|
|
config_content = build_opencode_config_content(
|
|
port=port,
|
|
include_mcp=include_mcp,
|
|
include_plugin=include_plugin,
|
|
)
|
|
env["OPENCODE_CONFIG_CONTENT"] = json.dumps(config_content, separators=(",", ":"))
|
|
|
|
display = ["OPENCODE_CONFIG_CONTENT={provider: headroom}"]
|
|
if "plugin" in config_content:
|
|
env["HEADROOM_PROXY_URL"] = f"http://127.0.0.1:{port}"
|
|
display.append(f"plugin={HEADROOM_OPENCODE_PLUGIN}")
|
|
|
|
if project and "HEADROOM_PROJECT" not in env:
|
|
env["HEADROOM_PROJECT"] = project
|
|
|
|
return env, display
|