Backends: vscode, Copilot, Claude

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

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:

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

Setup:

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:

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

Setup:

library(hal)
hal_setup()                           # installs Copilot CLI + guides login
hal_available()                       # TRUE when ready

Claude: when to pick it

Setup:

# Install Claude Code from https://claude.ai/download
claude               # run once interactively to complete OAuth
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:

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 <uuid> to create a session; every subsequent call in the same R session uses --resume <uuid>. 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:

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:

What Claude doesn't support

hal's Claude client stubs these with a warning rather than pretending:

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:

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:

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



Try the hal package in your browser

Any scripts or data that you put into this service are public.

hal documentation built on Oct. 6, 2026, 5:07 p.m.