--- title: "Backends: vscode, Copilot, Claude" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Backends: vscode, Copilot, Claude} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` hal speaks to three different agent transports behind a common R interface: the Positron `vscode.lm` API (via the bundled hal-bridge extension), GitHub Copilot CLI, and Anthropic Claude Code. You pick per session; everything else -- pipe verbs, custom tools, `use_env`, governance -- works the same. The default is resolved at call time: `vscode` if you're inside Positron (`POSITRON_VERSION` is set), `copilot` otherwise. Set `options(hal.backend = "...")` to override globally. | | vscode | Copilot | Claude | |---|---|---|---| | Transport | Localhost HTTP to hal-bridge extension | ACP server (long-lived, JSON-RPC) | `claude -p` per turn, resumes via uuid | | Auth | Your Positron Copilot sign-in (reused) | GitHub Copilot subscription | Claude.ai subscription (OAuth) | | Install | `hal_install_bridge()` (bundled VSIX, no download) | `hal_setup()` installs Copilot CLI | Download Claude Code, run `claude` once | | Models | Whatever `vscode.lm` exposes to your session | 17 (GPT, Claude, Gemini) | 3 (Haiku, Sonnet, Opus) | | Mid-session model switch | Yes (via `vscode.lm` model id) | Yes, preserves context | No (must reset) | | Session modes | Agent only | Agent / Plan / Autopilot | Agent only (others warn) | | Quota visibility | -- (inherits Copilot) | -- (flat subscription) | `hal_quota()` (5-hour window) | | `eval_r` transport | In-process (no MCP subprocess) | MCP stdio subprocess | MCP stdio subprocess | | Where it runs | Positron only | Anywhere (RStudio, VS Code, terminal R) | Anywhere | | Typical cost | Flat (subscription) | Flat monthly | ~$0.005–0.03 per call (Haiku) | ## Pick a backend ```r library(hal) hal_configure(backend = "vscode") # default in Positron hal_configure(backend = "copilot") # default elsewhere hal_configure(backend = "claude") hal_config()$backend ``` The setting takes effect on the next session init -- call `hal_reset()` if you already have an active session and want to swap backends immediately. Everything after that point is identical: ```r hal("explain this error") mtcars |> hal_ask("summarize in 3 bullets") iris |> hal_do("add petal_area = Petal.Length * Petal.Width") ``` ## vscode: when to pick it - You're using Positron (it's the smart default there). - You want zero extra CLI installs -- no Node.js, no `@github/copilot` npm package, no `claude` binary. - You want to reuse your existing Positron Copilot sign-in -- no separate OAuth flow, no API keys. - You're sensitive to install friction on teammate machines: the bridge ships inside hal itself (`inst/extdata/hal-bridge-X.Y.Z.vsix`) and installs via the Positron CLI in one call. - You want `eval_r` to round-trip through R directly without an MCP subprocess -- the bridge speaks HTTP to your R session, so tool calls don't fork another process. Setup: ```r library(hal) hal_install_bridge() # one-shot: installs bundled VSIX # Fully quit Positron and reopen -- "Reload Window" is not enough on a # fresh install; the extension host only loads new extensions cold. hal_bridge_status() # confirms the extension is live hal_available() # TRUE when port file + bridge are up ``` `hal_install_bridge()` requires only the Positron CLI on `PATH` (or `POSITRON_BIN` env var). No GitHub auth, no network call. Models surface whatever `vscode.lm` exposes to your Positron Copilot session: ```r hal_models() # lists what vscode.lm has registered hal("explain this error", model = "claude-3-5-sonnet") ``` If models you expect are missing, that's a Positron / Copilot extension state issue -- restart Positron or sign in again from the Copilot pane. ## Copilot: when to pick it - You already have a Copilot subscription -- nothing extra to install beyond the CLI. - You want model variety (GPT-5, Gemini, multiple Claude versions) all through one endpoint. - You want mid-session model switching -- start with a cheap 0x model for exploration, hot-swap to Opus for hard reasoning, no context loss. - You rely on Plan or Autopilot mode. Setup: ```r library(hal) hal_setup() # installs Copilot CLI + guides login hal_available() # TRUE when ready ``` ## Claude: when to pick it - You have a Claude.ai subscription and want to drive it from R without an API key. - You care about the Claude Code ecosystem -- skills, hooks, subagents, MCP servers plug in directly. - You want live quota visibility (`hal_quota()` shows the 5-hour window status). - You're doing many disposable pipe-verb calls and want Haiku's `$0.005` per-resume price tag. Setup: ```bash # Install Claude Code from https://claude.ai/download claude # run once interactively to complete OAuth ``` ```r hal_configure(backend = "claude") hal_available() # TRUE when `claude` is on PATH hal("hello") # Haiku 4.5 by default ``` ### Per-entry-point model defaults The Claude backend tunes its default model per entry point: | Entry point | Default | Why | |---|---|---| | `hal()` | `claude-sonnet-4-5-20250929` | Multi-turn amortizes cost | | `hal_ask()`, `hal_do()` | `claude-haiku-4-5-20251001` | Disposable, cost-sensitive | Override globally or per call: ```r hal_configure(default_model = "claude-opus-4-5-20250902") hal("deep reasoning", model = "claude-opus-4-5-20250902") ``` ### Session lifecycle The Claude backend spawns a fresh `claude -p` subprocess per turn. The first call uses `--session-id ` to create a session; every subsequent call in the same R session uses `--resume `. hal stores the uuid for you. Cost tiers we measured on Haiku 4.5: | Tier | When | Cost | |---|---|---| | Cold | Empty account cache | $0.05–0.07 | | Warm | Recent Claude Code activity | $0.02–0.03 | | Resumed | Successive calls in the same session | $0.005 | Extrapolated per-model (first call / resumed call): | Model | First | Resumed | |---|---|---| | Haiku 4.5 | $0.02–0.03 | $0.005 | | Sonnet 4.6 | $0.05–0.07 | $0.01–0.015 | | Opus 4.7 | $0.12–0.17 | $0.03–0.04 | Note: `total_cost_usd` is notional on subscription auth -- you pay in tokens against the 5-hour window, not dollars. It's still a useful burn-rate proxy. ## Quota: `hal_quota()` Every Claude response stream carries a `rate_limit_event` with the 5-hour window status. `hal_quota()` surfaces it: ```r hal("hello") hal_quota() #> -- hal quota (claude) ---------------------------------------- #> i Window: "five_hour" #> v Status: "allowed" #> i Resets: 2026-04-23 17:30:00 PDT #> i Overage: "allowed" ``` `hal_quota()` returns `NULL` on the Copilot backend (flat subscription -- there's nothing to show). Fields returned as a `hal_quota` list: - `type` -- currently always `"five_hour"` - `status` -- `"allowed"` or `"rate_limited"` - `resets_at` -- POSIXct; when the window clears - `overage_status` -- `"allowed"` or `"rejected"` - `overage_disabled_reason` -- e.g. `"out_of_credits"` when relevant - `backend` -- `"claude"` ## What Claude doesn't support hal's Claude client stubs these with a warning rather than pretending: ```r chat <- hal_chat() chat$switch_model("claude-opus-4-5-20250902") #> ! Mid-session model switching not available on Claude backend. #> i Start a new session with `hal_reset()` and a different default model. chat$set_mode("plan") #> ! Session modes (plan/autopilot) not available on Claude backend. ``` To switch models on Claude, reset the session: ```r hal_configure(default_model = "claude-opus-4-5-20250902") hal_reset() ``` ## Mock CLI for offline testing The Copilot and Claude backends each ship with a mock CLI under `inst/mock-cli/` so CI and local unit tests don't need network, login, or credits: - `inst/mock-cli/mock_copilot.R` -- NDJSON ACP server - `inst/mock-cli/mock_claude.R` -- stream-json per-turn The vscode backend has no mock equivalent -- its transport is a real localhost HTTP server inside Positron. vscode-backend tests stub the bridge at the R level (port file + handler shim) rather than running a fake extension. Scenarios are passed as the first positional arg (`basic`, `echo`, `thinking`, `tool_use`, `tool_roundtrip`, `multi_turn`, `rate_limit`, `error`, `slow`). See `tests/testthat/helper-mock-client.R` for the harness pattern. ## See also - `?hal_configure` -- the `backend` argument and all other session settings - `?hal_quota` -- structure of the returned object - `vignette("getting-started")` -- end-to-end walkthrough - `vignette("agent-tools")` -- built-in tools, `eval_r`, custom MCP tools