Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Imports:
cli,
clipr,
dplyr,
ellmer (>= 0.3.0),
ellmer (>= 0.4.2),
frontmatter,
fs,
jsonlite,
Expand Down
8 changes: 8 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

* `btw docs` and `btw pkg src` commands now auto-load an in-development package found in the current directory (or its `pkg-r/`, `R/` subfolder) with `pkgload::load_all()` before running, so results reflect uncommitted local changes. Pass `--no-dev` to disable this and use the installed package instead (#212).

## Bug fixes

* `btw --version` now works without a subcommand. Previously, Rapp's required top-level switch caused the CLI to print usage and exit before the version flag was ever checked.

## Other changes

* btw now requires ellmer (>= 0.4.2). The `set_model()` compatibility shim was removed, and `client_get_models()` now delegates to ellmer's `models_list()` generic, replacing a bespoke per-provider dispatch table (#214).

# btw 1.4.0

## New features
Expand Down
115 changes: 22 additions & 93 deletions R/utils-ellmer.R
Original file line number Diff line number Diff line change
@@ -1,78 +1,16 @@
client_get_models <- function(client) {
provider <- client$get_provider()
models_list <- asNamespace("ellmer")[["models_list"]]

models_fns <- list(
ProviderAnthropic = function(p) {
ellmer::models_anthropic(
base_url = p@base_url,
credentials = p@credentials
)
},
ProviderGoogleGemini = function(p) {
ellmer::models_google_gemini(
base_url = p@base_url,
credentials = p@credentials
)
},
ProviderAWSBedrock = function(p) {
base_url <- sub("bedrock-runtime", "bedrock", p@base_url)
ellmer::models_aws_bedrock(profile = p@profile, base_url = base_url)
},
ProviderOpenAI = function(p) {
ellmer::models_openai(base_url = p@base_url, credentials = p@credentials)
},
ProviderMistral = function(p) {
ellmer::models_mistral()
},
ProviderLMStudio = function(p) {
base_url <- sub("/v1$", "", p@base_url)
ellmer::models_lmstudio(base_url = base_url, credentials = p@credentials)
},
ProviderVllm = function(p) {
ellmer::models_vllm(base_url = p@base_url, credentials = p@credentials)
},
ProviderOllama = function(p) {
base_url <- sub("/v1$", "", p@base_url)
ellmer::models_ollama(base_url = base_url, credentials = p@credentials)
},
ProviderPortkeyAI = function(p) {
ellmer::models_portkey(base_url = p@base_url)
},
ProviderOpenAICompatible = function(p) {
base_url <- sub("/v1$", "", p@base_url)
ellmer::models_openai(base_url = p@base_url, credentials = p@credentials)
}
)

try_get_models <- function(fn, provider) {
tryCatch(fn(provider), error = function(e) {
tryCatch(
models_list(client),
error = function(e) {
cli::cli_warn(
"Failed to fetch models for provider {provider@name}",
"Failed to fetch models for provider {client$get_provider()@name}",
parent = e
)
NULL
})
}

if (provider@name == "LM Studio") {
return(try_get_models(models_fns$ProviderLMStudio, provider))
}

for (cls in names(models_fns)) {
if (inherits(provider, sprintf("ellmer::%s", cls))) {
return(
tryCatch(models_fns[[cls]](provider), error = function(e) {
cli::cli_warn(
"Failed to fetch models for provider {provider@name}",
parent = e
)
NULL
})
)
}
}

NULL
)
}

btw_client_config_name <- function(cfg) {
Expand All @@ -88,7 +26,9 @@ btw_client_config_name <- function(cfg) {
if (inherits(cfg, "Chat")) {
provider <- tryCatch(cfg$get_provider()@name, error = function(e) NULL)
model <- tryCatch(cfg$get_model(), error = function(e) NULL)
if (!is.null(provider) && !is.null(model)) return(paste0(provider, "/", model))
if (!is.null(provider) && !is.null(model)) {
return(paste0(provider, "/", model))
}
return(model %||% provider %||% "unknown")
}
"unknown"
Expand Down Expand Up @@ -234,30 +174,19 @@ chat_get_tokens <- function(client) {
cached_tokens <- 0

if (!is.null(tokens) && nrow(tokens) > 0) {
if (utils::packageVersion("ellmer") <= "0.3.0") {
last_user <- tokens[tokens$role == "user", ]
if (nrow(last_user) > 0) {
input_tokens <- as.integer(utils::tail(last_user$tokens_total, 1))
}
tokens_assistant <- tokens[tokens$role == "assistant", ]
if (nrow(tokens_assistant) > 0) {
output_tokens <- as.integer(sum(tokens_assistant$tokens))
}
} else {
# output tokens are by turn, so we sum them all
if ("output" %in% colnames(tokens)) {
output_tokens <- sum(tokens$output)
}
# input and cached tokens are accumulated in the last API call
if ("input" %in% colnames(tokens)) {
input_tokens <-
tokens$input[[length(tokens$input)]]
}
if ("cached_input" %in% colnames(tokens)) {
cached_tokens <- tokens$cached_input[[
length(tokens$cached_input)
]]
}
# output tokens are by turn, so we sum them all
if ("output" %in% colnames(tokens)) {
output_tokens <- sum(tokens$output)
}
# input and cached tokens are accumulated in the last API call
if ("input" %in% colnames(tokens)) {
input_tokens <-
tokens$input[[length(tokens$input)]]
}
if ("cached_input" %in% colnames(tokens)) {
cached_tokens <- tokens$cached_input[[
length(tokens$cached_input)
]]
}
}

Expand Down
12 changes: 0 additions & 12 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,6 @@
assign(tool_def@name, tool_def, envir = pkg_env)
}

# Patch ellmer:::Chat to add set_model() if it doesn't exist
ellmer_chat <- utils::getFromNamespace("Chat", "ellmer")
if (!is.null(ellmer_chat)) {
if (!"set_model" %in% names(ellmer_chat$public_methods)) {
ellmer_chat$set("public", "set_model", function(model) {
old <- private$provider@model
private$provider@model <- model
invisible(old)
})
}
}

rlang::run_on_load()
}

Expand Down
2 changes: 2 additions & 0 deletions exec/btw.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ suppressPackageStartupMessages({
# Global options --------------------------------------------------------------

#| description: Print btw version and exit.
#| negative_alias: false
version <- FALSE

if (version) {
Expand Down Expand Up @@ -816,6 +817,7 @@ btw_cran_info <- function(package, json = FALSE) {

# Subcommand dispatch ---------------------------------------------------------

#| required: false
switch(
group <- "",

Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/btw_client.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Code
print(chat)
Output
<Chat Anthropic/claude-sonnet-4-6 turns=1 input=0 output=0 cost=$0.00>
<Chat Anthropic/DEFAULT_MODEL turns=1 input=0 output=0 cost=$0.00>
-- system ----------------------------------------------------------------------
# System and Session Context

Expand Down Expand Up @@ -35,7 +35,7 @@
Code
print(chat)
Output
<Chat Anthropic/claude-sonnet-4-6 turns=1 input=0 output=0 cost=$0.00>
<Chat Anthropic/DEFAULT_MODEL turns=1 input=0 output=0 cost=$0.00>
-- system ----------------------------------------------------------------------
# System and Session Context

Expand Down
9 changes: 9 additions & 0 deletions tests/testthat/helpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ expect_btw_tool_result <- function(
}

scrub_system_info <- function(x) {
x <- sub(
sprintf(
"Anthropic/%s",
ellmer::chat_anthropic(credentials = \() "not-a-real-key")$get_model()
),
"Anthropic/DEFAULT_MODEL",
x,
fixed = TRUE
)
x <- sub(R.version.string, "R VERSION", x, fixed = TRUE)
x <- sub(sessioninfo::os_name(), "OPERATING SYSTEM", x, fixed = TRUE)
x <- sub(version$system, "SYSTEM VERSION", x, fixed = TRUE)
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-tool-agent-subagent.R
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ test_that("subagent_client() consults btw.md if options are unset", {

agent_client <- subagent_client()
expect_equal(agent_client$get_provider()@name, "OpenRouter")
expect_equal(agent_client$get_provider()@model, "super-cool-model")
expect_equal(agent_client$get_model(), "super-cool-model")

expect_equal(
names(agent_client$get_tools()),
Expand Down
3 changes: 3 additions & 0 deletions tests/testthat/test-tool-pkg-src.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
duckdb_extensions <- withr::local_tempdir()
local_options(duckdb.home = duckdb_extensions)

# Test btw_tool_pkg_src_list_impl ----------------------------------------------

test_that("btw_tool_pkg_src_list_impl returns exported objects by default", {
Expand Down