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) |
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")
@github/copilot
npm package, no claude binary.inst/extdata/hal-bridge-X.Y.Z.vsix) and
installs via the Positron CLI in one call.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:
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.
Setup:
library(hal) hal_setup() # installs Copilot CLI + guides login hal_available() # TRUE when ready
hal_quota() shows the 5-hour window
status).$0.005
per-resume price tag.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
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")
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.
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:
type -- currently always "five_hour"status -- "allowed" or "rate_limited"resets_at -- POSIXct; when the window clearsoverage_status -- "allowed" or "rejected"overage_disabled_reason -- e.g. "out_of_credits" when relevantbackend -- "claude"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()
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 serverinst/mock-cli/mock_claude.R -- stream-json per-turnThe 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.
?hal_configure -- the backend argument and all other session settings?hal_quota -- structure of the returned objectvignette("getting-started") -- end-to-end walkthroughvignette("agent-tools") -- built-in tools, eval_r, custom MCP toolsAny scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.