| Title: | Agentic Coding Assistants |
|---|---|
| Description: | A native R interface to agentic coding assistants. Talks directly to local command line interfaces ('GitHub Copilot', 'Anthropic' 'Claude Code') over standard input/output and to a local 'vscode.lm' bridge inside 'Positron', providing multi-model chat, tool calling, and in-session R evaluation. Supports a single stateful hal() session as well as disposable hal_ask() / hal_do() pipeline verbs, plot capture so models can see graphics, verified data transforms, and user-defined tools. |
| Authors: | Mark Dippold [aut, cre] |
| Maintainer: | Mark Dippold <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.1.4 |
| Built: | 2026-10-12 23:24:15 UTC |
| Source: | https://github.com/arclite-red/hal |
The main entry point for conversational interaction with Copilot models. Maintains a persistent session across calls. The SDK agent has built-in tools for file operations, code search, and shell commands.
hal(..., model = NULL, use_env = NULL, reset = FALSE, inspect = FALSE)hal(..., model = NULL, use_env = NULL, reset = FALSE, inspect = FALSE)
... |
Character strings, concatenated as the user message. |
model |
Model identifier. If NULL, uses the session/configured default. |
use_env |
Logical or NULL; if TRUE, snapshots the caller's environment
and injects it into the prompt so the model naturally uses eval_r to work
with your R objects. If NULL (default), auto-detects: scans the prompt
for R identifiers and injects the snapshot only when the prompt mentions
an object that exists in the caller's environment. When auto-detect
fires, a |
reset |
Logical; if TRUE, starts a fresh session before prompting. |
inspect |
Logical; if TRUE, returns session info instead of prompting. |
The assistant's text response (invisibly when displayed).
In an interactive session a transport failure prints an alert and
returns invisible(NULL). In non-interactive contexts (scripts, R
Markdown) it aborts with a classed condition instead, so programmatic
callers can handle hal failures specifically:
tryCatch(hal("..."), hal_transport_error = function(e) ...). All hal
conditions inherit from hal_error / hal_warning; hal_do() signals
hal_do_error / hal_do_warning, and hal_ask() signals
hal_ask_warning.
## Not run: # Simple conversation hal("What is R?") hal("Can you explain more about data frames?") # Environment access (auto-detected when objects exist) df <- mtcars hal("What's the mean mpg?") # auto-detects df, injects env snapshot # Reset and start fresh hal("Hello", reset = TRUE) # Inspect session hal(inspect = TRUE) ## End(Not run)## Not run: # Simple conversation hal("What is R?") hal("Can you explain more about data frames?") # Environment access (auto-detected when objects exist) df <- mtcars hal("What's the mean mpg?") # auto-detects df, injects env snapshot # Reset and start fresh hal("Hello", reset = TRUE) # Inspect session hal(inspect = TRUE) ## End(Not run)
Pipe mode: a pipe-terminal verb that sends a prompt to Copilot augmented
with the piped data context. The piped object is described via str() and
attached to the prompt. Returns .data invisibly (pipe passthrough).
hal_ask(.data, ...)hal_ask(.data, ...)
.data |
In pipe mode: an R object (data frame, model, etc.) piped in. In standalone mode: the first prompt string. |
... |
Character strings forming the prompt. |
Standalone mode: called directly with a string prompt to ask a question without data context. Returns the response text invisibly.
Spawns a disposable session – does NOT pollute the main hal() conversation.
Pipe mode: invisibly returns .data (pipe passthrough).
Standalone: invisibly returns the response text.
## Not run: # Pipe mode (data-aware analysis) mtcars |> hal_ask("what are the key patterns?") lm(mpg ~ wt, data = mtcars) |> hal_ask("interpret these coefficients") # Standalone mode (general question) hal_ask("what does the pipe operator do in R?") hal_ask("explain", "the difference between <- and =") ## End(Not run)## Not run: # Pipe mode (data-aware analysis) mtcars |> hal_ask("what are the key patterns?") lm(mpg ~ wt, data = mtcars) |> hal_ask("interpret these coefficients") # Standalone mode (general question) hal_ask("what does the pipe operator do in R?") hal_ask("explain", "the difference between <- and =") ## End(Not run)
Dispatches by backend:
vscode: bridge port file exists and /version responds.
copilot: Copilot CLI binary found and --version succeeds.
claude: Claude CLI binary found and --version succeeds.
hal_available(backend = NULL)hal_available(backend = NULL)
backend |
Optional backend id ( |
Honors the hal.backend option / explicit argument so callers can probe
a specific backend without switching the session default.
TRUE if the backend's transport is reachable, FALSE otherwise.
HalChat, hal_models(), hal_bridge_status() for richer
vscode-backend diagnostics.
hal_available() ## Not run: hal_available(backend = "vscode") hal_available(backend = "claude") ## End(Not run)hal_available() ## Not run: hal_available(backend = "vscode") hal_available(backend = "claude") ## End(Not run)
Reads the bridge port file, pings the /version endpoint, and warns on
version drift between the running bridge and the version this hal release
was pinned against.
hal_bridge_status()hal_bridge_status()
Invisibly returns TRUE if the bridge is reachable, FALSE
otherwise. Always prints a status line as a side effect.
## Not run: hal_bridge_status() ## End(Not run)## Not run: hal_bridge_status() ## End(Not run)
Creates a new HalChat instance for conversing with GitHub Copilot
models. This is the recommended entry point – equivalent to
HalChat$new() but follows tidyverse naming conventions.
hal_chat( model = NULL, system_prompt = NULL, client = NULL, echo = NULL, on_text = NULL, on_tool_call = NULL, on_thought = NULL, mode = NULL, permission_policy = "auto-allow", quiet = FALSE )hal_chat( model = NULL, system_prompt = NULL, client = NULL, echo = NULL, on_text = NULL, on_tool_call = NULL, on_thought = NULL, mode = NULL, permission_policy = "auto-allow", quiet = FALSE )
model |
Model identifier (e.g., |
system_prompt |
System prompt string. Defaults to hal's built-in R
assistant prompt. Pass a string to override, or set via
|
client |
An existing HalClient instance, or |
echo |
Echo mode: |
on_text |
Callback for streaming text chunks: |
on_tool_call |
Callback for tool call events: |
on_thought |
Callback for thought chunks: |
mode |
Session mode: |
permission_policy |
Permission policy for tool calls. One of
|
quiet |
Logical; suppress informational messages (default |
A HalChat object.
hal_models(), hal_client(), hal_available()
## Not run: # Default model chat <- hal_chat() chat$chat("Explain the pipe operator in R") # Specific model chat <- hal_chat(model = "gpt-5.2") chat$chat("Hello!") # With system prompt chat <- hal_chat( model = "claude-haiku-4.5", system_prompt = "Reply concisely in one sentence." ) chat$chat("What is R?") # Plan mode chat <- hal_chat(mode = "plan") chat$chat("Create an analysis plan for this dataset") # With streaming callbacks chat <- hal_chat( on_text = function(chunk) cat(chunk), on_tool_call = function(tc) message("Tool: ", tc$title) ) # Read-only mode (deny file writes and shell commands) chat <- hal_chat(permission_policy = "auto-deny") chat$chat("What files are in R/?") ## End(Not run)## Not run: # Default model chat <- hal_chat() chat$chat("Explain the pipe operator in R") # Specific model chat <- hal_chat(model = "gpt-5.2") chat$chat("Hello!") # With system prompt chat <- hal_chat( model = "claude-haiku-4.5", system_prompt = "Reply concisely in one sentence." ) chat$chat("What is R?") # Plan mode chat <- hal_chat(mode = "plan") chat$chat("Create an analysis plan for this dataset") # With streaming callbacks chat <- hal_chat( on_text = function(chunk) cat(chunk), on_tool_call = function(tc) message("Tool: ", tc$title) ) # Read-only mode (deny file writes and shell commands) chat <- hal_chat(permission_policy = "auto-deny") chat$chat("What files are in R/?") ## End(Not run)
Creates a new HalClient instance for low-level ACP communication.
Most users should use hal_chat() instead.
hal_client( model = NULL, cli_path = NULL, permission_policy = "auto-allow", on_text = NULL, on_tool_call = NULL, on_thought = NULL, quiet = FALSE )hal_client( model = NULL, cli_path = NULL, permission_policy = "auto-allow", on_text = NULL, on_tool_call = NULL, on_thought = NULL, quiet = FALSE )
model |
Model identifier (e.g., |
cli_path |
Path to the Copilot CLI binary. If |
permission_policy |
How to handle tool permission requests:
|
on_text |
Callback for streaming text chunks: |
on_tool_call |
Callback for tool call events: |
on_thought |
Callback for thought chunks: |
quiet |
Logical; suppress informational messages (default |
A HalClient object.
## Not run: client <- hal_client() client$start() client$handshake() session <- client$new_session() client$stop() ## End(Not run)## Not run: client <- hal_client() client$start() client$handshake() session <- client$new_session() client$stop() ## End(Not run)
Returns a named list of all active settings.
hal_config()hal_config()
A named list of configuration values.
cfg <- hal_config() cfg$backend cfg$eval_timeoutcfg <- hal_config() cfg$backend cfg$eval_timeout
Set display, governance, and session defaults. Settings persist for the
duration of the R session via options().
hal_configure( use_colors = NULL, stream = NULL, stream_speed = NULL, default_model = NULL, system_prompt = NULL, eval_denylist = NULL, credential_action = NULL, eval_timeout = NULL, prompt_timeout = NULL, prompt_timeout_cold = NULL, edit_in_place = NULL, do_retries = NULL, do_on_fail = NULL, verify = NULL, plot_vision = NULL, permission_policy = NULL, session_quiet = NULL, init_quiet = NULL, show_thoughts = NULL, backend = NULL, quiet = FALSE )hal_configure( use_colors = NULL, stream = NULL, stream_speed = NULL, default_model = NULL, system_prompt = NULL, eval_denylist = NULL, credential_action = NULL, eval_timeout = NULL, prompt_timeout = NULL, prompt_timeout_cold = NULL, edit_in_place = NULL, do_retries = NULL, do_on_fail = NULL, verify = NULL, plot_vision = NULL, permission_policy = NULL, session_quiet = NULL, init_quiet = NULL, show_thoughts = NULL, backend = NULL, quiet = FALSE )
use_colors |
Logical; enable ANSI color output (default: TRUE). |
stream |
Logical; enable streaming typewriter output (default: TRUE). |
stream_speed |
Character; |
default_model |
Character; default model identifier. |
system_prompt |
Character; custom system prompt. Use |
eval_denylist |
Character vector of function names blocked from
|
credential_action |
Character; action on credential detection:
|
eval_timeout |
Numeric; seconds for eval_r time limit (default: 30). |
prompt_timeout |
Numeric; seconds before aborting a resumed prompt on the Claude backend (default: 60). Has no effect on Copilot. |
prompt_timeout_cold |
Numeric; seconds before aborting the first (session-creating) prompt on the Claude backend (default: 180). The cold path pays for Node.js startup, AV scanning, and session provisioning on Windows and can exceed the warm timeout. |
edit_in_place |
Logical; when TRUE, |
do_retries |
Integer; max retry attempts for |
do_on_fail |
Character; what |
verify |
Logical; when TRUE (default), |
plot_vision |
Logical; when TRUE (default), plots drawn by |
permission_policy |
Character or function; how to handle tool
permission requests. |
session_quiet |
Logical; suppress informational messages from the
transport layer (CLI startup, handshake). Takes effect on next session
init (call |
init_quiet |
Logical; when TRUE, suppress the data transmission notice shown on first session init (default: FALSE). |
show_thoughts |
Logical; when TRUE, display the model's reasoning/thought
process before the response (default: FALSE). Takes effect on next session
init (call |
backend |
Character; transport backend: |
quiet |
Logical; suppress confirmation messages (default: FALSE). |
Invisibly returns a list of changes made.
hal_configure(stream = FALSE, do_retries = 1, quiet = TRUE) hal_config()$stream # unset again to restore the defaults options(hal.stream = NULL, hal.do_retries = NULL)hal_configure(stream = FALSE, do_retries = 1, quiet = TRUE) hal_config()$stream # unset again to restore the defaults options(hal.stream = NULL, hal.do_retries = NULL)
Pipe mode: a mid-pipe transformation verb that generates R code, executes it, and returns the result.
hal_do(.data, ..., .model = NULL, .retries = NULL, .verify = NULL)hal_do(.data, ..., .model = NULL, .retries = NULL, .verify = NULL)
.data |
In pipe mode: an R object piped in. In standalone mode: the first prompt string. |
... |
Character strings describing the transformation/task. |
.model |
Model to use for code generation (default: session model). |
.retries |
Integer; max retry attempts when generated code fails to
parse or execute (default: 2, configurable via |
.verify |
Logical; in pipe mode with a data-frame result, compare the
input and output and display a one-line structural report (row/column
deltas, class changes, introduced NAs). The full report is attached as
|
Standalone mode: called directly with a string prompt to generate and execute R code in the caller's environment.
Spawns a disposable session – does NOT pollute the main conversation. Governance controls (denylist, credential scanner, timeout) are enforced.
Pipe mode: transformed data. Standalone: result of generated code (invisible).
In an interactive session, a failure (after retries) warns and returns
.data unchanged (pipe) or invisible(NULL) (standalone) – forgiving
for console exploration. In non-interactive contexts (scripts, R
Markdown, targets), it aborts instead: a pipeline silently continuing
with untransformed data is worse than an error. Override either way with
options(hal.do_on_fail = "warn") or "abort". All failures signal
classed conditions (hal_do_error / hal_do_warning), so you can
tryCatch(..., hal_do_error = function(e) ...).
## Not run: # Pipe mode mtcars |> hal_do("keep only cars with mpg > 25") iris |> hal_do("add a column for petal area") |> head() # Standalone mode hal_do("write a function called greet that takes a name") greet("world") # Disable retries mtcars |> hal_do("normalize mpg", .retries = 0) ## End(Not run)## Not run: # Pipe mode mtcars |> hal_do("keep only cars with mpg > 25") iris |> hal_do("add a column for petal area") |> head() # Standalone mode hal_do("write a function called greet that takes a name") greet("world") # Disable retries mtcars |> hal_do("normalize mpg", .retries = 0) ## End(Not run)
Reads an .xlsx, treats columns without formulas as data, and translates
each formula column into a tidyverse expression via the active hal backend.
Returns a runnable R script – a line that reads the input columns from the
workbook plus a mutate() pipeline that recreates the formula columns – so
you can replace the spreadsheet (and the hal_excel() call) with the code.
hal_excel(path, sheet = 1, model = NULL, tolerance = NULL, retries = NULL) ## S3 method for class 'hal_excel_code' print(x, ...)hal_excel(path, sheet = 1, model = NULL, tolerance = NULL, retries = NULL) ## S3 method for class 'hal_excel_code' print(x, ...)
path |
Path to an |
sheet |
Sheet name, or 1-based index (default |
model |
Optional model id; defaults to the session/configured model. |
tolerance |
Numeric tolerance for float comparison (default
|
retries |
Max self-correction attempts per column on mismatch (default
|
x |
A |
... |
Ignored. |
Works like hal_do() under the hood (disposable worker, code extraction,
denylist, timed eval, retry loop), with one addition: each translation is run
and checked against the values Excel cached, so only columns that match Excel
row-for-row go into the live pipeline; the rest are emitted as commented
stubs to review.
Each formula column is assumed to hold one formula filled down the column
(a vectorised expression, e.g. =A2*B2), so its first formula is translated
once. A workbook with no cached values is still translated but reported
unverifiable.
A hal_excel_code object (a character string of R code) that prints as
the generated script. The per-column report is attached as
attr(result, "hal_excel"). Save it with writeLines(result, "out.R").
## Not run: hal_excel("model.xlsx") # prints the read + mutate script code <- hal_excel("model.xlsx") writeLines(code, "model.R") # or paste it in place of the call attr(code, "hal_excel") # per-column verification report ## End(Not run)## Not run: hal_excel("model.xlsx") # prints the read + mutate script code <- hal_excel("model.xlsx") writeLines(code, "model.R") # or paste it in place of the call attr(code, "hal_excel") # per-column verification report ## End(Not run)
Display or return the conversation turns from the active session.
hal_history( role = NULL, pattern = NULL, format = c("console", "data.frame", "text"), n = NULL )hal_history( role = NULL, pattern = NULL, format = c("console", "data.frame", "text"), n = NULL )
role |
Filter by role: |
pattern |
Optional regex to filter turn content. |
format |
Output format: |
n |
Maximum number of turns to return (most recent). |
Depends on format: invisible NULL for console, a data.frame, or
a character vector.
# with no active session this returns an empty data frame hal_history(format = "data.frame") ## Not run: hal_history() # print all turns hal_history(role = "user", n = 5) # last five user prompts hal_history(pattern = "ggplot") # turns mentioning ggplot ## End(Not run)# with no active session this returns an empty data frame hal_history(format = "data.frame") ## Not run: hal_history() # print all turns hal_history(role = "user", n = 5) # last five user prompts hal_history(pattern = "ggplot") # turns mentioning ggplot ## End(Not run)
Installs the hal-bridge VSIX shipped with this hal release into Positron.
No download, no GitHub auth, no SHA verification: the bytes installed are
the bytes that shipped in inst/extdata/. Reload Positron after
installing to activate the bridge.
hal_install_bridge(local_path = NULL, force = FALSE)hal_install_bridge(local_path = NULL, force = FALSE)
local_path |
Optional path to a |
force |
If |
The vscode backend (hal_configure(backend = "vscode")) requires this
extension. Other backends (copilot, claude) do not.
Invisibly returns the installed VSIX path.
hal_bridge_status() to check whether it's actually running.
## Not run: hal_install_bridge() hal_install_bridge(local_path = "~/Downloads/hal-bridge-dev.vsix") ## End(Not run)## Not run: hal_install_bridge() hal_install_bridge(local_path = "~/Downloads/hal-bridge-dev.vsix") ## End(Not run)
Returns the full per-model record, including the raw _meta payload from
the ACP server. Useful for debugging or accessing fields that
hal_models() doesn't surface as columns.
hal_model_info(id = NULL, client = NULL)hal_model_info(id = NULL, client = NULL)
id |
Model identifier. If |
client |
A HalClient instance, or |
A list with id, name, description, provider, family,
usage, multiplier, is_default, context_window, release_date,
and meta (raw _meta list from the server, or NULL for Claude).
## Not run: ids <- hal_model_info() # print available ids hal_model_info(ids[[1]]) # inspect the first one ## End(Not run)## Not run: ids <- hal_model_info() # print available ids hal_model_info(ids[[1]]) # inspect the first one ## End(Not run)
Returns a data frame describing the models exposed by the active backend.
hal_models(client = NULL)hal_models(client = NULL)
client |
A HalClient instance, or |
Copilot: queries the ACP server (session/new) to fetch live
model metadata.
Claude: returns the alias selectors (opus/sonnet/haiku), which
Anthropic resolves to the latest model of each tier – the Claude CLI
exposes no live model list. Pass a concrete id (e.g. "claude-opus-5")
to model= if you need to pin a version.
vscode: queries the hal-bridge /models endpoint, which returns
whatever models vscode.lm exposes to your Copilot session.
A data frame with columns:
id — model identifier (matches --model flag)
name — display name
description — vendor-supplied prose (may be NA)
provider — "openai", "anthropic", "google", or "unknown"
family — coarse tier: "light", "standard", "heavy"
usage — billing tier label (e.g. "1x", "0.33x", "0x")
multiplier — numeric form of usage
is_default — TRUE if currently selected as session default
context_window — token limit when known (NA otherwise)
release_date — ISO date when known (NA otherwise)
hal_model_info() for full per-model detail (incl. raw _meta).
## Not run: hal_models() subset(hal_models(), provider == "anthropic") ## End(Not run)## Not run: hal_models() subset(hal_models(), provider == "anthropic") ## End(Not run)
Plan mode (and occasionally Agent mode) asks the model to commit to a
structured plan, written to
~/.copilot/session-state/{sessionId}/plan.md. hal_plan() returns
the path so you can view or edit it directly.
hal_plan()hal_plan()
Character path to plan.md, or NULL invisibly if no plan is
available.
## Not run: hal_reset(mode = "plan") hal("Investigate the Q4 revenue drop in `sales`") hal_plan() # path to plan.md file.edit(hal_plan()) # open in editor readLines(hal_plan()) # read programmatically ## End(Not run)## Not run: hal_reset(mode = "plan") hal("Investigate the Q4 revenue drop in `sales`") hal_plan() # path to plan.md file.edit(hal_plan()) # open in editor readLines(hal_plan()) # read programmatically ## End(Not run)
Returns the most recent rate_limit_event payload observed on the Claude
backend during this session. Only populated after at least one hal(),
hal_ask(), or hal_do() call – the event is embedded in the prompt
response stream.
hal_quota()hal_quota()
A hal_quota object (list) with fields type, status,
resets_at (POSIXct), overage_status, overage_disabled_reason,
backend. Returns NULL invisibly if no rate-limit info has been
observed, or if the active backend is Copilot.
## Not run: hal("hello") hal_quota() ## End(Not run)## Not run: hal("hello") hal_quota() ## End(Not run)
Discovers exported functions from a package and registers them as LLM-callable tools.
hal_register_package_tools(pkg, fns = NULL, exclude = NULL, prefix = NULL)hal_register_package_tools(pkg, fns = NULL, exclude = NULL, prefix = NULL)
pkg |
Character; package name. |
fns |
Character vector of function names. If NULL, registers all exported functions. |
exclude |
Character vector of function names to skip. |
prefix |
Optional prefix for tool names (e.g., |
Invisibly returns a list of registered tool definitions.
## Not run: # expose a few dplyr verbs to the model hal_register_package_tools("dplyr", fns = c("filter", "select", "arrange"), prefix = "dplyr_" ) ## End(Not run)## Not run: # expose a few dplyr verbs to the model hal_register_package_tools("dplyr", fns = c("filter", "select", "arrange"), prefix = "dplyr_" ) ## End(Not run)
Shorthand that builds a tool definition and registers it on the active session's chat object.
hal_register_tool(fn, name = NULL, description = NULL, types = NULL)hal_register_tool(fn, name = NULL, description = NULL, types = NULL)
fn |
The R function to expose as a tool. |
name |
Tool name (defaults to the symbol name of |
description |
Description shown to the LLM (when to call this tool). |
types |
Optional named list mapping argument names to JSON Schema type
strings: |
Invisibly returns the tool definition.
## Not run: get_time <- function(tz = "UTC") format(Sys.time(), tz = tz) hal_register_tool(get_time, description = "Current time in a given timezone" ) hal("what time is it in Tokyo?") ## End(Not run)## Not run: get_time <- function(tz = "UTC") format(Sys.time(), tz = tz) hal_register_tool(get_time, description = "Current time in a given timezone" ) hal("what time is it in Tokyo?") ## End(Not run)
Registers a list of tool specifications (as returned by e.g.
winston::timelog_tool_specs()) on the active session.
hal_register_tool_specs(specs)hal_register_tool_specs(specs)
specs |
A list of tool spec lists. |
Each spec must be a list with at minimum name, description, and
handler (a function). Optional: parameters (JSON Schema).
Invisibly returns the number of tools registered.
## Not run: specs <- list( list( name = "row_count", description = "Count the rows of a data frame in the global environment", handler = function(name) nrow(get(name, envir = globalenv())) ) ) hal_register_tool_specs(specs) ## End(Not run)## Not run: specs <- list( list( name = "row_count", description = "Count the rows of a data frame in the global environment", handler = function(name) nrow(get(name, envir = globalenv())) ) ) hal_register_tool_specs(specs) ## End(Not run)
Registers a list of tools on the active hal() session so the model can
call them. Each element may be an ellmer::ToolDef (e.g. from
daisy::daisy_tools()) or a plain hal tool list with name / description
/ fun / parameters. Unlike hal_register_tool(), which builds a tool
from a bare function's formals, this passes each element through the chat's
tool normalizer, so pre-built ToolDefs keep their declared argument schema.
hal_register_tools(tools)hal_register_tools(tools)
tools |
A list of tool definitions ( |
Invisibly, the number of tools registered.
hal_register_tool(), hal_register_tool_specs()
## Not run: tools <- list( hal_tool(function(x, y) x + y, "add", description = "Add two numbers", types = list(x = "number", y = "number") ), hal_tool(function(path) readLines(path), "read_file", description = "Read a text file" ) ) hal_register_tools(tools) ## End(Not run)## Not run: tools <- list( hal_tool(function(x, y) x + y, "add", description = "Add two numbers", types = list(x = "number", y = "number") ), hal_tool(function(path) readLines(path), "read_file", description = "Read a text file" ) ) hal_register_tools(tools) ## End(Not run)
Destroys the current session and starts fresh.
hal_reset(mode = NULL)hal_reset(mode = NULL)
mode |
Session mode: |
Invisibly returns NULL, called for its side effect.
## Not run: hal_reset() # fresh session on the same backend hal_reset(mode = "plan") # restart in plan mode (copilot backend) ## End(Not run)## Not run: hal_reset() # fresh session on the same backend hal_reset(mode = "plan") # restart in plan mode (copilot backend) ## End(Not run)
An S3 class representing the full response from a single prompt. Returned
internally by HalClient's prompt() method and used by HalChat.
## S3 method for class 'hal_response' print(x, ...)## S3 method for class 'hal_response' print(x, ...)
x |
A |
... |
Ignored. |
The print() method returns x invisibly. hal_response
objects are created internally by the transport clients.
textCharacter: the full response text.
stop_reasonCharacter: why the model stopped (e.g., "end_turn", "interrupted").
tool_callsList of hal_tool_call objects.
thoughtsCharacter vector of agent thought text.
eventsList of raw session/update events.
Interactive helper that picks the right backend for the host and walks through prerequisites. Two paths:
hal_setup(force = FALSE, quiet = FALSE, backend = NULL)hal_setup(force = FALSE, quiet = FALSE, backend = NULL)
force |
Logical; re-run setup even if the backend is already ready. |
quiet |
Logical; suppress progress messages. |
backend |
Optional backend override ( |
Positron (recommended): installs the hal-bridge extension from the
VSIX bundled with hal, so hal talks to vscode.lm directly. No
Node.js, no Copilot CLI, no GitHub CLI, no download – the only
requirement is that you are signed in to GitHub Copilot inside
Positron itself (the account menu in the lower left).
Other hosts (RStudio, VS Code, command-line R): installs the
GitHub Copilot CLI (gh copilot extension preferred, npm
@github/copilot fallback).
Claude Code: verifies the claude CLI and points at the download
and one-time sign-in. hal does not install this one for you: Claude
Code's sign-in is an interactive browser flow, and installing it via
npm yields a shim whose output is unreliable when driven from R.
Detection is via POSITRON_VERSION. Pass backend = "copilot" to force
the Copilot CLI path inside Positron, or backend = "claude" to check the
Claude Code CLI instead.
Invisibly TRUE if the backend transport is available after
setup, FALSE otherwise.
hal_available(), hal_install_bridge(),
hal_bridge_status(), hal_models()
## Not run: hal_setup() # auto: vscode in Positron, copilot elsewhere hal_setup(backend = "copilot") # force the Copilot CLI path hal_setup(backend = "claude") # check the Claude Code CLI ## End(Not run)## Not run: hal_setup() # auto: vscode in Positron, copilot elsewhere hal_setup(backend = "copilot") # force the Copilot CLI path hal_setup(backend = "claude") # check the Claude Code CLI ## End(Not run)
Prints a traffic-light report covering everything between you and a
working hal() call: which backend is selected (and why), whether its
transport is reachable, what the active session looks like, and – when
something is wrong – the single next step to fix it.
hal_status()hal_status()
This is the first thing to run when hal misbehaves, and the thing to paste into a bug report.
Invisibly, a list with elements backend, backend_source,
available, detail, session_active, model, and next_step
(NULL when everything is ready).
hal_setup(), hal_available(), hal_bridge_status(),
hal_config()
## Not run: hal_status() ## End(Not run)## Not run: hal_status() ## End(Not run)
Converts an R function into a hal tool definition suitable for
$register_tool(). Infers parameter types from function formals;
override with the types argument for non-string parameters.
hal_tool(fn, name = NULL, description = NULL, types = NULL)hal_tool(fn, name = NULL, description = NULL, types = NULL)
fn |
The R function to expose as a tool. |
name |
Tool name (defaults to the symbol name of |
description |
Description shown to the LLM (when to call this tool). |
types |
Optional named list mapping argument names to JSON Schema type
strings: |
A list with name, description, fun, and parameters – ready
for chat$register_tool().
my_sum <- function(x, y) x + y tool <- hal_tool(my_sum, "add_numbers", description = "Add two numbers", types = list(x = "number", y = "number") ) str(tool) ## Not run: # register on the active session so the model can call it hal_register_tools(list(tool)) ## End(Not run)my_sum <- function(x, y) x + y tool <- hal_tool(my_sum, "add_numbers", description = "Add two numbers", types = list(x = "number", y = "number") ) str(tool) ## Not run: # register on the active session so the model can call it hal_register_tools(list(tool)) ## End(Not run)
An S3 class representing a single tool invocation by the agent. Tool calls appear inside hal_turn objects and are visible when printing conversation history.
## S3 method for class 'hal_tool_call' format(x, ...) ## S3 method for class 'hal_tool_call' print(x, ...)## S3 method for class 'hal_tool_call' format(x, ...) ## S3 method for class 'hal_tool_call' print(x, ...)
x |
A |
... |
Ignored. |
The print() method returns x invisibly; the format()
method returns a character scalar. hal_tool_call objects are
created internally as tool activity streams in.
tool_call_idCharacter: unique ID for this tool call.
titleCharacter: human-readable description (e.g., "Viewing DESCRIPTION").
kindCharacter: tool kind ("read", "write", "command", etc.).
statusCharacter: "pending", "completed", or "failed".
inputNamed list of input arguments.
outputCharacter: output text from the tool.
List Registered Tools
hal_tools()hal_tools()
A character vector of tool names registered on the active session.
hal_tools() # character(0) when no session is activehal_tools() # character(0) when no session is active
An S3 class representing one turn in a hal conversation. Turns are
returned by hal_history() and tracked internally by HalChat.
## S3 method for class 'hal_turn' print(x, ...)## S3 method for class 'hal_turn' print(x, ...)
x |
A |
... |
Ignored. |
The print() method returns x invisibly. hal_turn objects
themselves are created internally and returned by hal_history().
roleCharacter: "user", "assistant", or "system".
contentCharacter: text content of the turn.
tool_callsList of hal_tool_call objects, or NULL.
thoughtsCharacter vector of agent thought chunks, or NULL.
hal_history(), hal_response, hal_tool_call
Tallies hal() turns by model and, when tier info is available, computes
cumulative Copilot billing units. Usage is tracked per session in memory
and cleared by hal_reset().
hal_usage(tiers = NULL)hal_usage(tiers = NULL)
tiers |
Optional named character vector mapping |
A hal_usage data frame with columns model, turns, tier,
units (numeric tier × turns, NA if tier unknown). Prints a compact
summary including the session total.
## Not run: hal("hello") hal("follow-up", model = "gpt-4.1") hal_usage() ## End(Not run)## Not run: hal("hello") hal("follow-up", model = "gpt-4.1") hal_usage() ## End(Not run)
High-level chat interface to GitHub Copilot models. Manages conversation history, tool registration, and streaming. API surface mirrors ellmer::Chat for familiarity.
Uses HalClient for JSON-RPC transport to the Copilot SDK CLI, which handles model-specific format translation internally – no proxy translation bugs.
The ACP server maintains conversation history server-side. Turns are
also tracked locally so you can inspect them with $get_turns().
An R6 object of class HalChat. Methods return
values as documented per method; construct with HalChat$new().
HalChat$new()Create a new Copilot chat session.
HalChat$new( model = NULL, system_prompt = NULL, client = NULL, echo = NULL, on_text = NULL, on_tool_call = NULL, on_thought = NULL, mode = NULL, permission_policy = "auto-allow", quiet = FALSE )
modelModel identifier (e.g., "claude-sonnet-5", "gpt-5.2").
If NULL, uses the server default.
system_promptSystem prompt string.
clientA HalClient instance, or NULL to create one.
echoEcho mode: "none", "output", or "all".
on_textCallback for streaming text chunks: function(chunk).
on_tool_callCallback for tool call events: function(tool_call).
on_thoughtCallback for thought chunks: function(chunk).
modeSession mode: "agent" (default), "plan", or "autopilot".
Applied after the session is created on the first prompt.
permission_policyPermission policy for the agent: "auto-allow",
"auto-deny", or a custom function. Ignored if client is provided.
quietLogical; suppress informational messages (default: FALSE).
HalChat$chat()Send a message and get a response.
HalChat$chat(..., timeout = NULL)
...Character strings, concatenated as the user message.
timeoutTimeout in seconds for the response.
Assistant's text response (invisibly if echo != "none").
HalChat$register_tool()Register a tool for the model to call.
HalChat$register_tool(tool)
toolA tool definition. Can be an ellmer::ToolDef or a list
with name, description, parameters, and fun fields.
HalChat$register_tools()Register multiple tools.
HalChat$register_tools(tools)
toolsA list of tool definitions.
HalChat$get_turns()Get conversation turns.
HalChat$get_turns(include_system_prompt = FALSE)
include_system_promptInclude the system prompt turn.
List of turn objects.
HalChat$last_response()Get the full response from the last prompt.
HalChat$last_response()
A hal_response object, or NULL if no responses yet.
HalChat$last_turn()Get the last assistant turn.
HalChat$last_turn()
A hal_turn object, or NULL if no turns yet.
HalChat$last_tool_calls()Get tool calls from the last assistant turn.
HalChat$last_tool_calls()
List of hal_tool_call objects, or NULL if none.
HalChat$switch_model()Switch models mid-session without losing context.
HalChat$switch_model(model)
modelModel identifier (e.g., "gpt-4.1", "claude-haiku-4.5").
Invisibly returns self.
HalChat$set_mode()Set the session mode.
HalChat$set_mode(mode = c("agent", "plan", "autopilot"))
mode"agent" (default), "plan", or "autopilot".
Invisibly returns self.
HalChat$cancel()Cancel the current in-flight prompt.
Stops the streaming loop and returns a partial response with
stop_reason = "interrupted". For interactive use, Ctrl+C (ESC in
RStudio) during $chat() achieves the same effect automatically.
Useful from Shiny observers, callbacks, or a second R session.
HalChat$cancel()
Invisibly returns self.
HalChat$get_model()Get the model identifier.
HalChat$get_model()
Character string.
HalChat$get_client()Get the underlying client.
HalChat$get_client()
A HalClient instance.
HalChat$clone()The objects of this class are cloneable with this method.
HalChat$clone(deep = FALSE)
deepWhether to make a deep clone.
HalClient for the low-level transport layer,
hal_models() for available models.
## Not run: # Preferred: use hal_chat() chat <- hal_chat() chat$chat("Explain the pipe operator in R") # Or use R6 constructor directly chat <- HalChat$new(model = "gpt-5.2") chat$chat("Hello!") ## End(Not run)## Not run: # Preferred: use hal_chat() chat <- hal_chat() chat$chat("Explain the pipe operator in R") # Or use R6 constructor directly chat <- HalChat$new(model = "gpt-5.2") chat$chat("Hello!") ## End(Not run)
Manages the Copilot CLI subprocess in ACP (Agent Client Protocol) mode and handles JSON-RPC 2.0 communication over NDJSON stdio. This is the low-level transport layer – most users should use HalChat instead.
The client spawns the Copilot CLI with --acp, which starts it as a
JSON-RPC server. Authentication uses ambient Copilot credentials from
any editor (VS Code, Positron, JetBrains).
An R6 object of class HalClient. Methods return
values as documented per method; construct with HalClient$new().
HalClient$new()Create a new Copilot SDK client.
HalClient$new( model = NULL, cli_path = NULL, permission_policy = "auto-allow", on_text = NULL, on_tool_call = NULL, on_thought = NULL, quiet = FALSE )
modelModel identifier (e.g., "claude-sonnet-5", "gpt-5.2").
Passed as --model to the CLI at startup. If NULL, falls back to the
COPILOT_MODEL environment variable, then the server default.
cli_pathPath to the Copilot CLI binary. If NULL,
searches PATH and common install locations.
permission_policyHow to handle tool permission requests:
"auto-allow" (default) auto-approves all, "auto-deny" auto-denies,
or a function receiving the permission params and returning an option ID.
on_textCallback function receiving each text chunk as it streams.
Signature: function(chunk). Called for each agent_message_chunk.
on_tool_callCallback function receiving tool call events.
Signature: function(tool_call). Called on tool_call and
tool_call_update events with a hal_tool_call object.
on_thoughtCallback function receiving thought chunks.
Signature: function(chunk). Called for each agent_thought_chunk.
quietLogical; suppress informational messages during init, handshake, and session creation (default: FALSE).
HalClient$start()Start the Copilot CLI subprocess in ACP mode.
HalClient$start()
Invisibly returns self.
HalClient$stop()Stop the Copilot CLI subprocess.
HalClient$stop()
HalClient$register_tools()Register tools to expose via MCP server.
Tools must be registered before the first $prompt() call (before the
CLI subprocess starts). The tools are passed via --additional-mcp-config
at startup. Registering tools after the CLI is running triggers a warning.
Tools are merged into the existing set by name. Call multiple times to accumulate tools from different sources.
HalClient$register_tools(tools)
toolsNamed list of tool definitions.
Invisibly returns self.
HalClient$handshake()Perform the ACP initialize handshake.
Sends the initialize request and initialized notification.
Called automatically on first use if needed.
HalClient$handshake(timeout = 15)
timeoutTimeout in seconds.
The initialize result (agent info and capabilities).
HalClient$new_session()Create a new ACP session.
Sends session/new to create a session. The session holds conversation
state server-side. Automatically performs the handshake if needed.
Custom tools are configured via --additional-mcp-config at CLI startup
(not via mcpServers in this call, which is broken per CLI issue #1040).
HalClient$new_session(cwd = getwd(), timeout = 15)
cwdWorking directory to report to the server.
timeoutTimeout in seconds.
The session result (session ID, available models, modes).
HalClient$prompt()Send a prompt and collect the streamed response.
Sends session/prompt and reads session/update notifications until
the final response arrives. Automatically creates a session if needed.
HalClient$prompt(text, timeout = NULL)
textThe prompt text.
timeoutTimeout in seconds.
A hal_response with text, stop_reason, tool_calls,
thoughts, and events.
HalClient$request()Send a JSON-RPC request and wait for a response.
HalClient$request(method, params = list(), timeout = 60)
methodJSON-RPC method name.
paramsNamed list of parameters.
timeoutTimeout in seconds.
Parsed JSON response result.
HalClient$notify()Send a JSON-RPC notification (no response expected).
HalClient$notify(method, params = list())
methodJSON-RPC method name.
paramsNamed list of parameters.
HalClient$switch_model()Switch models mid-session.
Changes the active model without losing conversation context.
Use hal_models() to see available model IDs.
HalClient$switch_model(model, timeout = 10)
modelModel identifier (e.g., "gpt-4.1", "claude-haiku-4.5").
timeoutTimeout in seconds.
Invisibly returns self.
HalClient$set_mode()Set the session mode.
Switches between Agent, Plan, and Autopilot modes.
Agent: Default conversational mode.
Plan: Multi-step planning mode with structured output.
Autopilot: Autonomous mode that runs until task completion without user interaction (experimental).
HalClient$set_mode(mode = c("agent", "plan", "autopilot"), timeout = 10)
mode"agent", "plan", or "autopilot".
timeoutTimeout in seconds.
Invisibly returns self.
HalClient$get_session_id()Get the current session ID.
HalClient$get_session_id()
Character string, or NULL if no session is active.
HalClient$swap_session()Create a temporary new session, saving the current one.
Used internally by disposable verbs (hal_ask) to get history isolation
on the same CLI process. Call restore_session() to switch back.
HalClient$swap_session()
The saved (previous) session ID (invisibly).
HalClient$restore_session()Restore a previously saved session ID.
HalClient$restore_session(session_id)
session_idThe session ID returned by swap_session().
Invisibly returns self.
HalClient$cancel()Cancel the current in-flight prompt.
Signals the streaming loop to stop and return a partial response with
stop_reason = "interrupted". Safe to call from callbacks, Shiny
observers, or a second thread. Does nothing if no prompt is active.
For interactive use, pressing Ctrl+C (ESC in RStudio) during a prompt achieves the same effect automatically.
HalClient$cancel()
Invisibly returns self.
HalClient$set_ipc()Configure IPC for live eval_r execution.
When set, the polling loop checks for eval_r requests from the MCP
subprocess and executes them in the user's R session via eval_fn.
HalClient$set_ipc(ipc_dir, eval_fn = NULL, permission_fn = NULL)
ipc_dirPath to the IPC directory for request/response files.
eval_fnFunction taking a code string and returning
list(result = "...", error = NULL) or list(result = NULL, error = "...").
permission_fnOptional handler for permission requests (used by
the Claude permission_prompt bridge). Takes the parsed request object
and returns the same list(result, error) shape as eval_fn.
Invisibly returns self.
HalClient$is_alive()Check if the client subprocess is running.
HalClient$is_alive()
Logical.
HalClient$read_stderr()Read any available stderr output (for debugging).
HalClient$read_stderr()
Character string.
HalClient$clone()The objects of this class are cloneable with this method.
HalClient$clone(deep = FALSE)
deepWhether to make a deep clone.
HalChat for the high-level chat interface,
hal_available() to check CLI availability.
## Not run: # Preferred: use hal_client() client <- hal_client() client$handshake() session <- client$new_session() client$stop() ## End(Not run)## Not run: # Preferred: use hal_client() client <- hal_client() client$handshake() session <- client$new_session() client$stop() ## End(Not run)