add files
Browse files- R/asa_api_helpers.R +44 -5
- R/plumber.R +3 -0
- Tests/api_contract_smoke.R +11 -0
- www/index.html +40 -0
R/asa_api_helpers.R
CHANGED
|
@@ -131,6 +131,42 @@ asa_api_apply_env_defaults <- function() {
|
|
| 131 |
}
|
| 132 |
}
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
asa_api_parse_proxy_url <- function(proxy_url) {
|
| 135 |
proxy_url <- trimws(asa_api_scalar_chr(proxy_url, default = ""))
|
| 136 |
if (!nzchar(proxy_url)) {
|
|
@@ -383,12 +419,12 @@ asa_api_build_run_args <- function(payload) {
|
|
| 383 |
asa_api_filter_formals(asa::run_task, run)
|
| 384 |
}
|
| 385 |
|
| 386 |
-
asa_api_build_direct_args <- function(payload) {
|
| 387 |
direct <- asa_api_build_run_args(payload)
|
| 388 |
direct$config <- NULL
|
| 389 |
direct$agent <- NULL
|
| 390 |
|
| 391 |
-
asa_api_filter_formals(
|
| 392 |
}
|
| 393 |
|
| 394 |
asa_api_build_batch_args <- function(payload) {
|
|
@@ -544,14 +580,15 @@ asa_api_run_single_via_asa <- function(payload) {
|
|
| 544 |
|
| 545 |
asa_api_run_single_via_direct <- function(payload) {
|
| 546 |
asa_api_apply_env_defaults()
|
|
|
|
| 547 |
prompt <- asa_api_require_prompt(payload)
|
| 548 |
config <- asa_api_build_config(payload)
|
| 549 |
-
direct_args <- asa_api_build_direct_args(payload)
|
| 550 |
|
| 551 |
args <- c(list(prompt = prompt, config = config), direct_args)
|
| 552 |
-
args <- asa_api_filter_formals(
|
| 553 |
|
| 554 |
-
result <- do.call(
|
| 555 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 556 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 557 |
|
|
@@ -613,6 +650,7 @@ asa_api_run_batch <- function(payload) {
|
|
| 613 |
|
| 614 |
asa_api_health_payload <- function(boot_error = NULL) {
|
| 615 |
asa_installed <- requireNamespace("asa", quietly = TRUE)
|
|
|
|
| 616 |
has_boot_error <- is.character(boot_error) && nzchar(trimws(boot_error))
|
| 617 |
tor_health <- asa_api_tor_health()
|
| 618 |
healthy <- asa_installed && !has_boot_error && (!isTRUE(tor_health$tor_enabled) || isTRUE(tor_health$tor_ready))
|
|
@@ -622,6 +660,7 @@ asa_api_health_payload <- function(boot_error = NULL) {
|
|
| 622 |
service = "asa-api",
|
| 623 |
time_utc = format(Sys.time(), tz = "UTC", usetz = TRUE),
|
| 624 |
asa_installed = asa_installed,
|
|
|
|
| 625 |
boot_error = if (has_boot_error) boot_error else NULL,
|
| 626 |
defaults = list(
|
| 627 |
backend = getOption("asa.default_backend", NULL),
|
|
|
|
| 131 |
}
|
| 132 |
}
|
| 133 |
|
| 134 |
+
asa_api_get_asa_namespace_function <- function(name, optional = FALSE) {
|
| 135 |
+
if (!requireNamespace("asa", quietly = TRUE)) {
|
| 136 |
+
stop("Package `asa` is not installed in this environment.", call. = FALSE)
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
exports <- tryCatch(getNamespaceExports("asa"), error = function(e) character(0))
|
| 140 |
+
if (name %in% exports) {
|
| 141 |
+
return(getExportedValue("asa", name))
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
asa_ns <- asNamespace("asa")
|
| 145 |
+
if (exists(name, envir = asa_ns, inherits = FALSE)) {
|
| 146 |
+
return(get(name, envir = asa_ns, inherits = FALSE))
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
if (isTRUE(optional)) {
|
| 150 |
+
return(NULL)
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
stop(
|
| 154 |
+
sprintf(
|
| 155 |
+
"Package `asa` does not provide `%s`. Upgrade `asa` or disable direct-provider mode.",
|
| 156 |
+
name
|
| 157 |
+
),
|
| 158 |
+
call. = FALSE
|
| 159 |
+
)
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
asa_api_get_run_direct_task <- function(optional = FALSE) {
|
| 163 |
+
asa_api_get_asa_namespace_function("run_direct_task", optional = optional)
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
asa_api_has_run_direct_task <- function() {
|
| 167 |
+
!is.null(asa_api_get_run_direct_task(optional = TRUE))
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
asa_api_parse_proxy_url <- function(proxy_url) {
|
| 171 |
proxy_url <- trimws(asa_api_scalar_chr(proxy_url, default = ""))
|
| 172 |
if (!nzchar(proxy_url)) {
|
|
|
|
| 419 |
asa_api_filter_formals(asa::run_task, run)
|
| 420 |
}
|
| 421 |
|
| 422 |
+
asa_api_build_direct_args <- function(payload, run_direct_task_fun = asa_api_get_run_direct_task()) {
|
| 423 |
direct <- asa_api_build_run_args(payload)
|
| 424 |
direct$config <- NULL
|
| 425 |
direct$agent <- NULL
|
| 426 |
|
| 427 |
+
asa_api_filter_formals(run_direct_task_fun, direct)
|
| 428 |
}
|
| 429 |
|
| 430 |
asa_api_build_batch_args <- function(payload) {
|
|
|
|
| 580 |
|
| 581 |
asa_api_run_single_via_direct <- function(payload) {
|
| 582 |
asa_api_apply_env_defaults()
|
| 583 |
+
run_direct_task_fun <- asa_api_get_run_direct_task()
|
| 584 |
prompt <- asa_api_require_prompt(payload)
|
| 585 |
config <- asa_api_build_config(payload)
|
| 586 |
+
direct_args <- asa_api_build_direct_args(payload, run_direct_task_fun = run_direct_task_fun)
|
| 587 |
|
| 588 |
args <- c(list(prompt = prompt, config = config), direct_args)
|
| 589 |
+
args <- asa_api_filter_formals(run_direct_task_fun, args)
|
| 590 |
|
| 591 |
+
result <- do.call(run_direct_task_fun, args)
|
| 592 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 593 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 594 |
|
|
|
|
| 650 |
|
| 651 |
asa_api_health_payload <- function(boot_error = NULL) {
|
| 652 |
asa_installed <- requireNamespace("asa", quietly = TRUE)
|
| 653 |
+
direct_provider_available <- asa_installed && isTRUE(asa_api_has_run_direct_task())
|
| 654 |
has_boot_error <- is.character(boot_error) && nzchar(trimws(boot_error))
|
| 655 |
tor_health <- asa_api_tor_health()
|
| 656 |
healthy <- asa_installed && !has_boot_error && (!isTRUE(tor_health$tor_enabled) || isTRUE(tor_health$tor_ready))
|
|
|
|
| 660 |
service = "asa-api",
|
| 661 |
time_utc = format(Sys.time(), tz = "UTC", usetz = TRUE),
|
| 662 |
asa_installed = asa_installed,
|
| 663 |
+
direct_provider_available = direct_provider_available,
|
| 664 |
boot_error = if (has_boot_error) boot_error else NULL,
|
| 665 |
defaults = list(
|
| 666 |
backend = getOption("asa.default_backend", NULL),
|
R/plumber.R
CHANGED
|
@@ -45,6 +45,9 @@ asa_api_error_payload <- function(res, status, message) {
|
|
| 45 |
}
|
| 46 |
|
| 47 |
asa_api_error_status <- function(message) {
|
|
|
|
|
|
|
|
|
|
| 48 |
if (grepl("invalid json|required|must be|non-empty|unauthorized|password", message, ignore.case = TRUE)) {
|
| 49 |
return(400L)
|
| 50 |
}
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
asa_api_error_status <- function(message) {
|
| 48 |
+
if (grepl("direct-provider mode|does not provide `run_direct_task`", message, ignore.case = TRUE)) {
|
| 49 |
+
return(501L)
|
| 50 |
+
}
|
| 51 |
if (grepl("invalid json|required|must be|non-empty|unauthorized|password", message, ignore.case = TRUE)) {
|
| 52 |
return(400L)
|
| 53 |
}
|
Tests/api_contract_smoke.R
CHANGED
|
@@ -105,6 +105,17 @@ assert_true(
|
|
| 105 |
"Batch-compatible shared options should still flow into run_task_batch."
|
| 106 |
)
|
| 107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
{
|
| 109 |
original_asa <- asa_api_run_single_via_asa
|
| 110 |
original_direct <- asa_api_run_single_via_direct
|
|
|
|
| 105 |
"Batch-compatible shared options should still flow into run_task_batch."
|
| 106 |
)
|
| 107 |
|
| 108 |
+
assert_true(
|
| 109 |
+
identical(asa_api_has_run_direct_task(), !is.null(asa_api_get_run_direct_task(optional = TRUE))),
|
| 110 |
+
"Direct-provider capability checks should agree on run_direct_task availability."
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
health_payload <- asa_api_health_payload()
|
| 114 |
+
assert_true(
|
| 115 |
+
identical(isTRUE(health_payload$direct_provider_available), isTRUE(asa_api_has_run_direct_task())),
|
| 116 |
+
"Health payload should report direct-provider availability from the same capability check."
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
{
|
| 120 |
original_asa <- asa_api_run_single_via_asa
|
| 121 |
original_direct <- asa_api_run_single_via_direct
|
www/index.html
CHANGED
|
@@ -324,6 +324,7 @@
|
|
| 324 |
<input id="use-direct-provider" type="checkbox" />
|
| 325 |
<span>Use direct provider call (skip ASA)</span>
|
| 326 |
</label>
|
|
|
|
| 327 |
|
| 328 |
<div class="actions">
|
| 329 |
<button id="submit-btn" type="button">Run Query</button>
|
|
@@ -358,6 +359,7 @@
|
|
| 358 |
const promptInput = document.getElementById("prompt");
|
| 359 |
const outputFormatInput = document.getElementById("output-format");
|
| 360 |
const useDirectProviderInput = document.getElementById("use-direct-provider");
|
|
|
|
| 361 |
const outputPre = document.getElementById("output");
|
| 362 |
const statusEl = document.getElementById("status");
|
| 363 |
const modeBadgeEl = document.getElementById("mode-badge");
|
|
@@ -376,6 +378,42 @@
|
|
| 376 |
modeBadgeEl.textContent = label || "Mode: not run yet";
|
| 377 |
}
|
| 378 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
async function runQuery() {
|
| 380 |
const password = passwordInput.value || "";
|
| 381 |
const prompt = promptInput.value || "";
|
|
@@ -443,6 +481,8 @@
|
|
| 443 |
runQuery();
|
| 444 |
}
|
| 445 |
});
|
|
|
|
|
|
|
| 446 |
</script>
|
| 447 |
</body>
|
| 448 |
</html>
|
|
|
|
| 324 |
<input id="use-direct-provider" type="checkbox" />
|
| 325 |
<span>Use direct provider call (skip ASA)</span>
|
| 326 |
</label>
|
| 327 |
+
<p id="direct-provider-note" class="hint" hidden></p>
|
| 328 |
|
| 329 |
<div class="actions">
|
| 330 |
<button id="submit-btn" type="button">Run Query</button>
|
|
|
|
| 359 |
const promptInput = document.getElementById("prompt");
|
| 360 |
const outputFormatInput = document.getElementById("output-format");
|
| 361 |
const useDirectProviderInput = document.getElementById("use-direct-provider");
|
| 362 |
+
const directProviderNoteEl = document.getElementById("direct-provider-note");
|
| 363 |
const outputPre = document.getElementById("output");
|
| 364 |
const statusEl = document.getElementById("status");
|
| 365 |
const modeBadgeEl = document.getElementById("mode-badge");
|
|
|
|
| 378 |
modeBadgeEl.textContent = label || "Mode: not run yet";
|
| 379 |
}
|
| 380 |
|
| 381 |
+
function setDirectProviderAvailability(available, note) {
|
| 382 |
+
if (available) {
|
| 383 |
+
useDirectProviderInput.disabled = false;
|
| 384 |
+
directProviderNoteEl.hidden = true;
|
| 385 |
+
directProviderNoteEl.textContent = "";
|
| 386 |
+
return;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
useDirectProviderInput.checked = false;
|
| 390 |
+
useDirectProviderInput.disabled = true;
|
| 391 |
+
directProviderNoteEl.hidden = false;
|
| 392 |
+
directProviderNoteEl.textContent = note || "Direct provider mode requires an asa build that provides run_direct_task.";
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
async function loadCapabilities() {
|
| 396 |
+
try {
|
| 397 |
+
const response = await fetch("/healthz", {
|
| 398 |
+
headers: {
|
| 399 |
+
"Accept": "application/json"
|
| 400 |
+
}
|
| 401 |
+
});
|
| 402 |
+
if (!response.ok) {
|
| 403 |
+
return;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
const data = await response.json();
|
| 407 |
+
if (data && data.direct_provider_available === false) {
|
| 408 |
+
setDirectProviderAvailability(false);
|
| 409 |
+
} else if (data && data.direct_provider_available === true) {
|
| 410 |
+
setDirectProviderAvailability(true);
|
| 411 |
+
}
|
| 412 |
+
} catch (error) {
|
| 413 |
+
// Keep the GUI usable even if health probing fails.
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
async function runQuery() {
|
| 418 |
const password = passwordInput.value || "";
|
| 419 |
const prompt = promptInput.value || "";
|
|
|
|
| 481 |
runQuery();
|
| 482 |
}
|
| 483 |
});
|
| 484 |
+
|
| 485 |
+
loadCapabilities();
|
| 486 |
</script>
|
| 487 |
</body>
|
| 488 |
</html>
|