Skip to content

Commit 4ee7f54

Browse files
committed
allow the model to list and select hosts
1 parent 134c866 commit 4ee7f54

File tree

3 files changed

+89
-47
lines changed

3 files changed

+89
-47
lines changed

R/host.R

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ handle_message_from_server <- function(msg) {
8282

8383
if (data$method == "tools/call") {
8484
name <- data$params$name
85-
fn <- getNamespace("btw")[[name]]
85+
86+
# TODO: retrieve the function definitions directly from the configured tools
87+
# (#12)
88+
fn <- getNamespace("btw")[[name]] %||% getNamespace("acquaint")[[name]]
8689
args <- data$params$arguments
8790

8891
# HACK for btw_tool_env_describe_environment. In the JSON, it will have

R/server.R

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -163,10 +163,9 @@ capabilities <- function() {
163163
)
164164
}
165165

166-
# Hacky way of getting tools from btw
166+
# TODO: btw now surfaces a btw_tools() function that can be passed
167+
# directly to `tool_as_json()` along with .acquaint_tools (#12)
167168
get_all_btw_tools <- function() {
168-
dummy_provider <- ellmer::Provider("dummy", "dummy", "dummy")
169-
170169
.btw_tools <- getNamespace("btw")[[".btw_tools"]]
171170
tools <- lapply(unname(.btw_tools), function(tool_obj) {
172171
tool <- tool_obj$tool()
@@ -175,21 +174,29 @@ get_all_btw_tools <- function() {
175174
return(NULL)
176175
}
177176

178-
as_json <- getNamespace("ellmer")[["as_json"]]
179-
inputSchema <- compact(as_json(dummy_provider, tool@arguments))
180-
# This field is present but shouldn't be
181-
inputSchema$description <- NULL
182-
183-
list(
184-
name = tool@name,
185-
description = tool@description,
186-
inputSchema = inputSchema
187-
)
177+
tool_as_json(tool)
188178
})
189179

180+
tools <- c(tools, lapply(.acquaint_tools, tool_as_json))
181+
190182
compact(tools)
191183
}
192184

185+
tool_as_json <- function(tool) {
186+
dummy_provider <- ellmer::Provider("dummy", "dummy", "dummy")
187+
188+
as_json <- getNamespace("ellmer")[["as_json"]]
189+
inputSchema <- compact(as_json(dummy_provider, tool@arguments))
190+
# This field is present but shouldn't be
191+
inputSchema$description <- NULL
192+
193+
list(
194+
name = tool@name,
195+
description = tool@description,
196+
inputSchema = inputSchema
197+
)
198+
}
199+
193200
compact <- function(.x) {
194201
Filter(length, .x)
195202
}
@@ -206,36 +213,3 @@ check_not_interactive <- function(call = caller_env()) {
206213
)
207214
}
208215
}
209-
210-
mcp_discover <- function() {
211-
sock <- nanonext::socket("poly")
212-
on.exit(nanonext:::reap(sock))
213-
cv <- nanonext::cv()
214-
monitor <- nanonext::monitor(sock, cv)
215-
suppressWarnings(
216-
for (i in seq_len(1024L)) {
217-
nanonext::dial(
218-
sock,
219-
url = sprintf("%s%d", acquaint_socket, i),
220-
autostart = NA
221-
) &&
222-
break
223-
}
224-
)
225-
pipes <- nanonext::read_monitor(monitor)
226-
res <- lapply(seq_along(pipes), function(x) nanonext::recv_aio(sock))
227-
lapply(
228-
pipes,
229-
function(x) nanonext::send_aio(sock, "", mode = "raw", pipe = x)
230-
)
231-
nanonext::collect_aio_(res)
232-
}
233-
234-
select_host <- function(i) {
235-
lapply(the$server_socket[["dialer"]], nanonext::reap)
236-
attr(the$server_socket, "dialer") <- NULL
237-
nanonext::dial(
238-
the$server_socket,
239-
url = sprintf("%s%d", acquaint_socket, as.integer(i))
240-
)
241-
}

R/tools.R

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# These two functions are supplied to the client as tools and allow the client
2+
# to discover R sessions which have called `acquaint::mcp_host()`. They
3+
# are "model-facing" rather than user-facing.
4+
list_r_sessions <- function() {
5+
sock <- nanonext::socket("poly")
6+
on.exit(nanonext::reap(sock))
7+
cv <- nanonext::cv()
8+
monitor <- nanonext::monitor(sock, cv)
9+
suppressWarnings(
10+
for (i in seq_len(1024L)) {
11+
nanonext::dial(
12+
sock,
13+
url = sprintf("%s%d", acquaint_socket, i),
14+
autostart = NA
15+
) &&
16+
break
17+
}
18+
)
19+
pipes <- nanonext::read_monitor(monitor)
20+
res <- lapply(seq_along(pipes), function(x) nanonext::recv_aio(sock))
21+
lapply(
22+
pipes,
23+
function(x) nanonext::send_aio(sock, "", mode = "raw", pipe = x)
24+
)
25+
# Convert the list result from nanonext to string
26+
btw::btw_this(nanonext::collect_aio_(res))
27+
}
28+
29+
list_r_sessions_tool <-
30+
ellmer::tool(
31+
.fun = list_r_sessions,
32+
.description = paste(
33+
"List the R sessions that are available to access.",
34+
"R sessions which have run `acquaint::mcp_host()` will appear here.",
35+
"In general, do not use this tool unless asked to list or",
36+
"select a specific R session."
37+
)
38+
)
39+
40+
select_r_session <- function(i) {
41+
lapply(the$server_socket[["dialer"]], nanonext::reap)
42+
attr(the$server_socket, "dialer") <- NULL
43+
nanonext::dial(
44+
the$server_socket,
45+
url = sprintf("%s%d", acquaint_socket, as.integer(i))
46+
)
47+
paste0("Selected session ", i, "successfully.")
48+
}
49+
50+
select_r_session_tool <-
51+
ellmer::tool(
52+
.fun = select_r_session,
53+
.description = paste(
54+
"Choose the R session host of interest.",
55+
"Use the `list_r_sessions` tool to discover potential sessions.",
56+
"In general, do not use this tool unless asked to select a specific R",
57+
"session; the tools available to you have a default R session",
58+
"that is usually the one the user wants.",
59+
"Your choice of session will persist after the tool is called; only",
60+
"call this tool more than once if you need to switch between sessions."
61+
),
62+
i = ellmer::type_integer("The index of the host session to select.")
63+
)
64+
65+
.acquaint_tools <- list(list_r_sessions_tool, select_r_session_tool)

0 commit comments

Comments
 (0)