cjerzak commited on
Commit
8f895b0
·
1 Parent(s): dd0cabe

add files

Browse files
Files changed (4) hide show
  1. R/asa_api_helpers.R +44 -5
  2. R/plumber.R +3 -0
  3. Tests/api_contract_smoke.R +11 -0
  4. 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(asa::run_direct_task, direct)
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(asa::run_direct_task, args)
553
 
554
- result <- do.call(asa::run_direct_task, args)
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>