add files
Browse files- R/asa_api_helpers.R +501 -40
- R/plumber.R +17 -19
- Tests/api_contract_smoke.R +82 -2
R/asa_api_helpers.R
CHANGED
|
@@ -318,6 +318,364 @@ asa_api_error_fields <- function(message, error_code = NULL, details = NULL) {
|
|
| 318 |
payload
|
| 319 |
}
|
| 320 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
asa_api_missing_auth_env_vars <- function() {
|
| 322 |
required <- c("ASA_API_BEARER_TOKEN", "GUI_PASSWORD")
|
| 323 |
required[!nzchar(vapply(required, asa_api_secret_env_value, character(1)))]
|
|
@@ -760,93 +1118,196 @@ asa_api_is_result_like <- function(value) {
|
|
| 760 |
(is.list(value) && !is.null(value$status) && !is.null(value$message))
|
| 761 |
}
|
| 762 |
|
| 763 |
-
asa_api_run_single_via_asa <- function(payload) {
|
| 764 |
asa_api_apply_env_defaults()
|
| 765 |
prompt <- asa_api_require_prompt(payload)
|
| 766 |
config <- asa_api_build_config(payload)
|
| 767 |
run_args <- asa_api_build_run_args(payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 768 |
|
| 769 |
args <- c(list(prompt = prompt, config = config), run_args)
|
| 770 |
args <- asa_api_filter_formals(asa::run_task, args)
|
| 771 |
|
| 772 |
-
result <-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 774 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
)
|
| 781 |
}
|
| 782 |
|
| 783 |
-
asa_api_run_single_via_direct <- function(payload) {
|
| 784 |
asa_api_apply_env_defaults()
|
| 785 |
run_direct_task_fun <- asa_api_get_run_direct_task()
|
| 786 |
prompt <- asa_api_require_prompt(payload)
|
| 787 |
config <- asa_api_build_config(payload)
|
| 788 |
direct_args <- asa_api_build_direct_args(payload, run_direct_task_fun = run_direct_task_fun)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
|
| 790 |
args <- c(list(prompt = prompt, config = config), direct_args)
|
| 791 |
args <- asa_api_filter_formals(run_direct_task_fun, args)
|
| 792 |
|
| 793 |
-
result <-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 794 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 795 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
)
|
| 802 |
}
|
| 803 |
|
| 804 |
-
asa_api_run_single <- function(payload, allow_direct_provider = FALSE) {
|
| 805 |
use_direct_provider <- isTRUE(allow_direct_provider) &&
|
| 806 |
asa_api_to_bool(payload$use_direct_provider, default = FALSE)
|
| 807 |
|
| 808 |
if (use_direct_provider) {
|
| 809 |
-
return(asa_api_run_single_via_direct(payload))
|
| 810 |
}
|
| 811 |
|
| 812 |
-
asa_api_run_single_via_asa(payload)
|
| 813 |
}
|
| 814 |
|
| 815 |
-
asa_api_run_batch <- function(payload) {
|
| 816 |
asa_api_apply_env_defaults()
|
| 817 |
prompts <- asa_api_require_prompts(payload)
|
| 818 |
asa_api_validate_batch_supported_fields(payload)
|
| 819 |
config <- asa_api_build_config(payload)
|
| 820 |
batch_args <- asa_api_build_batch_args(payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 821 |
|
| 822 |
args <- c(list(prompts = prompts, config = config), batch_args)
|
| 823 |
args <- asa_api_filter_formals(asa::run_task_batch, args)
|
| 824 |
|
| 825 |
-
raw <-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 827 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 828 |
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
|
| 836 |
-
|
| 837 |
-
|
| 838 |
-
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
list(
|
| 846 |
-
status = "success",
|
| 847 |
-
results = items,
|
| 848 |
-
count = length(items),
|
| 849 |
-
circuit_breaker_aborted = isTRUE(attr(raw, "circuit_breaker_aborted"))
|
| 850 |
)
|
| 851 |
}
|
| 852 |
|
|
|
|
| 318 |
payload
|
| 319 |
}
|
| 320 |
|
| 321 |
+
asa_api_error_status <- function(message) {
|
| 322 |
+
if (grepl("direct-provider mode|does not provide `run_direct_task`", message, ignore.case = TRUE)) {
|
| 323 |
+
return(501L)
|
| 324 |
+
}
|
| 325 |
+
if (grepl("invalid json|required|must be|non-empty|unauthorized|password", message, ignore.case = TRUE)) {
|
| 326 |
+
return(400L)
|
| 327 |
+
}
|
| 328 |
+
500L
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
asa_api_drop_nulls <- function(value) {
|
| 332 |
+
if (!is.list(value)) {
|
| 333 |
+
return(value)
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
value[!vapply(value, is.null, logical(1))]
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
asa_api_make_diagnostic_id <- function(prefix = "asaapi") {
|
| 340 |
+
sprintf(
|
| 341 |
+
"%s-%s-%06d",
|
| 342 |
+
asa_api_scalar_chr(prefix, default = "asaapi"),
|
| 343 |
+
format(Sys.time(), "%Y%m%dT%H%M%SZ", tz = "UTC"),
|
| 344 |
+
as.integer(stats::runif(1, min = 0, max = 999999))
|
| 345 |
+
)
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
asa_api_redact_text_excerpt <- function(text, max_chars = 120L) {
|
| 349 |
+
text <- asa_api_scalar_chr(text, default = "")
|
| 350 |
+
if (!nzchar(text)) {
|
| 351 |
+
return("")
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
text <- gsub("[[:cntrl:]]+", " ", text)
|
| 355 |
+
text <- gsub("[[:space:]]+", " ", text)
|
| 356 |
+
text <- trimws(text)
|
| 357 |
+
if (!nzchar(text)) {
|
| 358 |
+
return("")
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
max_chars <- asa_api_scalar_int(max_chars, default = 120L)
|
| 362 |
+
if (is.na(max_chars) || max_chars < 16L) {
|
| 363 |
+
max_chars <- 120L
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
if (nchar(text, type = "chars") > max_chars) {
|
| 367 |
+
paste0(substr(text, 1L, max_chars), "...")
|
| 368 |
+
} else {
|
| 369 |
+
text
|
| 370 |
+
}
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
asa_api_redact_call_text <- function(call_text, max_chars = 240L) {
|
| 374 |
+
call_text <- asa_api_scalar_chr(call_text, default = "")
|
| 375 |
+
if (!nzchar(call_text)) {
|
| 376 |
+
return("")
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
call_text <- gsub('"[^"]*"', '"<redacted>"', call_text)
|
| 380 |
+
call_text <- gsub("'[^']*'", "'<redacted>'", call_text)
|
| 381 |
+
asa_api_redact_text_excerpt(call_text, max_chars = max_chars)
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
asa_api_prompt_summary <- function(prompt) {
|
| 385 |
+
prompt <- asa_api_scalar_chr(prompt, default = "")
|
| 386 |
+
|
| 387 |
+
list(
|
| 388 |
+
chars = nchar(prompt, type = "chars"),
|
| 389 |
+
excerpt = asa_api_redact_text_excerpt(prompt)
|
| 390 |
+
)
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
asa_api_prompts_summary <- function(prompts) {
|
| 394 |
+
prompts <- prompts %||% character(0)
|
| 395 |
+
prompts <- as.character(prompts)
|
| 396 |
+
prompt_lengths <- nchar(prompts, type = "chars")
|
| 397 |
+
prompt_lengths[is.na(prompt_lengths)] <- 0L
|
| 398 |
+
|
| 399 |
+
list(
|
| 400 |
+
count = length(prompts),
|
| 401 |
+
total_chars = sum(prompt_lengths),
|
| 402 |
+
max_chars = if (length(prompt_lengths)) max(prompt_lengths) else 0L,
|
| 403 |
+
first_prompt = if (length(prompts)) asa_api_prompt_summary(prompts[[1]]) else NULL
|
| 404 |
+
)
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
asa_api_runtime_config_summary <- function(config) {
|
| 408 |
+
if (!is.list(config) || !length(config)) {
|
| 409 |
+
return(list())
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
+
asa_api_drop_nulls(list(
|
| 413 |
+
backend = asa_api_scalar_chr(config$backend, default = ""),
|
| 414 |
+
model = asa_api_scalar_chr(config$model, default = ""),
|
| 415 |
+
conda_env = asa_api_scalar_chr(config$conda_env, default = ""),
|
| 416 |
+
use_browser = if (!is.null(config$use_browser)) isTRUE(config$use_browser) else NULL,
|
| 417 |
+
timeout = asa_api_scalar_int(config$timeout, default = NA_integer_),
|
| 418 |
+
rate_limit = asa_api_scalar_num(config$rate_limit, default = NA_real_),
|
| 419 |
+
workers = asa_api_scalar_int(config$workers, default = NA_integer_),
|
| 420 |
+
memory_folding = if (!is.null(config$memory_folding)) isTRUE(config$memory_folding) else NULL,
|
| 421 |
+
recursion_limit = asa_api_scalar_int(config$recursion_limit, default = NA_integer_)
|
| 422 |
+
))
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
asa_api_runtime_defaults_summary <- function() {
|
| 426 |
+
asa_api_drop_nulls(list(
|
| 427 |
+
backend = getOption("asa.default_backend", NULL),
|
| 428 |
+
model = getOption("asa.default_model", NULL),
|
| 429 |
+
conda_env = getOption("asa.default_conda_env", "asa_env"),
|
| 430 |
+
use_browser = asa_api_to_bool(Sys.getenv("ASA_USE_BROWSER_DEFAULT", unset = "false"), default = FALSE)
|
| 431 |
+
))
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
asa_api_runtime_tor_summary <- function() {
|
| 435 |
+
tor <- asa_api_tor_health()
|
| 436 |
+
|
| 437 |
+
asa_api_drop_nulls(list(
|
| 438 |
+
tor_enabled = isTRUE(tor$tor_enabled),
|
| 439 |
+
tor_ready = isTRUE(tor$tor_ready),
|
| 440 |
+
tor_proxy_host = tor$tor_proxy_host %||% NULL,
|
| 441 |
+
tor_proxy_port = tor$tor_proxy_port %||% NULL,
|
| 442 |
+
tor_proxy_port_open = isTRUE(tor$tor_proxy_port_open),
|
| 443 |
+
tor_control_port = tor$tor_control_port %||% NULL,
|
| 444 |
+
tor_control_port_open = isTRUE(tor$tor_control_port_open),
|
| 445 |
+
tor_cookie_present = isTRUE(tor$tor_cookie_present),
|
| 446 |
+
tor_cookie_readable = isTRUE(tor$tor_cookie_readable)
|
| 447 |
+
))
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
asa_api_runtime_error_summary <- function(error) {
|
| 451 |
+
if (is.null(error)) {
|
| 452 |
+
return(list())
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
err_call <- conditionCall(error)
|
| 456 |
+
call_text <- if (is.null(err_call)) "" else paste(deparse(err_call, width.cutoff = 120L), collapse = " ")
|
| 457 |
+
|
| 458 |
+
asa_api_drop_nulls(list(
|
| 459 |
+
message = conditionMessage(error),
|
| 460 |
+
class = as.character(class(error)),
|
| 461 |
+
call = if (nzchar(call_text)) asa_api_redact_call_text(call_text) else NULL
|
| 462 |
+
))
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
asa_api_runtime_stage <- function(stage_ref, default = "invoke") {
|
| 466 |
+
if (is.environment(stage_ref)) {
|
| 467 |
+
return(asa_api_scalar_chr(stage_ref$value, default = default))
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
asa_api_scalar_chr(stage_ref, default = default)
|
| 471 |
+
}
|
| 472 |
+
|
| 473 |
+
asa_api_build_runtime_diagnostic <- function(mode,
|
| 474 |
+
route,
|
| 475 |
+
payload = NULL,
|
| 476 |
+
prompt = NULL,
|
| 477 |
+
prompts = NULL,
|
| 478 |
+
config = NULL,
|
| 479 |
+
forwarded_arg_names = character(0),
|
| 480 |
+
request_shape = list(),
|
| 481 |
+
stage = "invoke",
|
| 482 |
+
error = NULL,
|
| 483 |
+
diagnostic_id = asa_api_make_diagnostic_id()) {
|
| 484 |
+
request_shape <- asa_api_named_list(request_shape)
|
| 485 |
+
request_shape$include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 486 |
+
request_shape$include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 487 |
+
if (!is.null(payload$use_direct_provider)) {
|
| 488 |
+
request_shape$use_direct_provider <- asa_api_to_bool(payload$use_direct_provider, default = FALSE)
|
| 489 |
+
}
|
| 490 |
+
request_shape$forwarded_arg_names <- sort(unique(as.character(forwarded_arg_names)))
|
| 491 |
+
|
| 492 |
+
if (!is.null(prompt)) {
|
| 493 |
+
request_shape$prompt <- asa_api_prompt_summary(prompt)
|
| 494 |
+
}
|
| 495 |
+
if (!is.null(prompts)) {
|
| 496 |
+
request_shape$prompts <- asa_api_prompts_summary(prompts)
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
asa_api_drop_nulls(list(
|
| 500 |
+
diagnostic_id = asa_api_scalar_chr(diagnostic_id, default = asa_api_make_diagnostic_id()),
|
| 501 |
+
timestamp_utc = format(Sys.time(), "%Y-%m-%dT%H:%M:%SZ", tz = "UTC"),
|
| 502 |
+
mode = asa_api_scalar_chr(mode, default = "unknown"),
|
| 503 |
+
route = asa_api_scalar_chr(route, default = "unknown"),
|
| 504 |
+
stage = asa_api_scalar_chr(stage, default = "invoke"),
|
| 505 |
+
request = asa_api_drop_nulls(request_shape),
|
| 506 |
+
config = asa_api_runtime_config_summary(config),
|
| 507 |
+
runtime = list(
|
| 508 |
+
asa_version = tryCatch(as.character(utils::packageVersion("asa")), error = function(e) NA_character_),
|
| 509 |
+
defaults = asa_api_runtime_defaults_summary(),
|
| 510 |
+
direct_provider_available = tryCatch(isTRUE(asa_api_has_run_direct_task()), error = function(e) FALSE),
|
| 511 |
+
tor = asa_api_runtime_tor_summary()
|
| 512 |
+
),
|
| 513 |
+
error = asa_api_runtime_error_summary(error)
|
| 514 |
+
))
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
asa_api_emit_runtime_diagnostic <- function(diagnostic, log_con = stderr()) {
|
| 518 |
+
if (requireNamespace("reticulate", quietly = TRUE)) {
|
| 519 |
+
diagnostic <- tryCatch(reticulate::py_to_r(diagnostic), error = function(e) diagnostic)
|
| 520 |
+
}
|
| 521 |
+
diagnostic <- asa_api_named_list(diagnostic)
|
| 522 |
+
diagnostic_id <- asa_api_scalar_chr(diagnostic$diagnostic_id, default = "unknown")
|
| 523 |
+
mode <- asa_api_scalar_chr(diagnostic$mode, default = "unknown")
|
| 524 |
+
route <- asa_api_scalar_chr(diagnostic$route, default = "unknown")
|
| 525 |
+
stage <- asa_api_scalar_chr(diagnostic$stage, default = "invoke")
|
| 526 |
+
error_message <- asa_api_scalar_chr((diagnostic$error %||% list())$message, default = "Request failed.")
|
| 527 |
+
|
| 528 |
+
cat(
|
| 529 |
+
sprintf(
|
| 530 |
+
"[asa-api] runtime failure diagnostic_id=%s mode=%s route=%s stage=%s error=%s\n",
|
| 531 |
+
diagnostic_id,
|
| 532 |
+
mode,
|
| 533 |
+
route,
|
| 534 |
+
stage,
|
| 535 |
+
error_message
|
| 536 |
+
),
|
| 537 |
+
file = log_con
|
| 538 |
+
)
|
| 539 |
+
|
| 540 |
+
diagnostic_json <- tryCatch(
|
| 541 |
+
jsonlite::toJSON(diagnostic, auto_unbox = TRUE, null = "null", force = TRUE, digits = NA),
|
| 542 |
+
error = function(e) {
|
| 543 |
+
jsonlite::toJSON(
|
| 544 |
+
list(
|
| 545 |
+
diagnostic_id = diagnostic_id,
|
| 546 |
+
serialization_error = conditionMessage(e)
|
| 547 |
+
),
|
| 548 |
+
auto_unbox = TRUE,
|
| 549 |
+
null = "null",
|
| 550 |
+
force = TRUE
|
| 551 |
+
)
|
| 552 |
+
}
|
| 553 |
+
)
|
| 554 |
+
cat(
|
| 555 |
+
sprintf("[asa-api][diagnostic] %s\n", paste(diagnostic_json, collapse = "")),
|
| 556 |
+
file = log_con
|
| 557 |
+
)
|
| 558 |
+
|
| 559 |
+
invisible(diagnostic)
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
asa_api_runtime_error_code <- function(mode) {
|
| 563 |
+
mode <- asa_api_scalar_chr(mode, default = "")
|
| 564 |
+
if (identical(mode, "asa_agent_batch")) {
|
| 565 |
+
return("agent_batch_failure")
|
| 566 |
+
}
|
| 567 |
+
if (identical(mode, "provider_direct_single")) {
|
| 568 |
+
return("direct_provider_failure")
|
| 569 |
+
}
|
| 570 |
+
"agent_pipeline_failure"
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
asa_api_raise_runtime_failure <- function(error,
|
| 574 |
+
diagnostic,
|
| 575 |
+
mode,
|
| 576 |
+
status_code = 500L,
|
| 577 |
+
log_con = stderr()) {
|
| 578 |
+
diagnostic <- asa_api_emit_runtime_diagnostic(diagnostic, log_con = log_con)
|
| 579 |
+
diagnostic_id <- asa_api_scalar_chr(diagnostic$diagnostic_id, default = "unknown")
|
| 580 |
+
message <- sprintf("%s [diagnostic_id=%s]", conditionMessage(error), diagnostic_id)
|
| 581 |
+
|
| 582 |
+
condition <- simpleError(message)
|
| 583 |
+
condition$error_code <- asa_api_runtime_error_code(mode)
|
| 584 |
+
condition$details <- asa_api_drop_nulls(list(
|
| 585 |
+
diagnostic_id = diagnostic_id,
|
| 586 |
+
mode = diagnostic$mode %||% NULL,
|
| 587 |
+
route = diagnostic$route %||% NULL,
|
| 588 |
+
stage = diagnostic$stage %||% NULL,
|
| 589 |
+
request = diagnostic$request %||% NULL,
|
| 590 |
+
config = diagnostic$config %||% NULL,
|
| 591 |
+
runtime = diagnostic$runtime %||% NULL,
|
| 592 |
+
error = diagnostic$error %||% NULL
|
| 593 |
+
))
|
| 594 |
+
condition$status_code <- asa_api_scalar_int(status_code, default = 500L)
|
| 595 |
+
class(condition) <- c("asa_api_runtime_error", class(condition))
|
| 596 |
+
|
| 597 |
+
stop(condition)
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
asa_api_invoke_with_runtime_diagnostics <- function(fn,
|
| 601 |
+
mode,
|
| 602 |
+
route,
|
| 603 |
+
payload = NULL,
|
| 604 |
+
prompt = NULL,
|
| 605 |
+
prompts = NULL,
|
| 606 |
+
config = NULL,
|
| 607 |
+
forwarded_arg_names = character(0),
|
| 608 |
+
request_shape = list(),
|
| 609 |
+
stage_ref = NULL,
|
| 610 |
+
status_code = 500L,
|
| 611 |
+
log_con = stderr()) {
|
| 612 |
+
tryCatch(
|
| 613 |
+
fn(),
|
| 614 |
+
error = function(e) {
|
| 615 |
+
diagnostic <- asa_api_build_runtime_diagnostic(
|
| 616 |
+
mode = mode,
|
| 617 |
+
route = route,
|
| 618 |
+
payload = payload,
|
| 619 |
+
prompt = prompt,
|
| 620 |
+
prompts = prompts,
|
| 621 |
+
config = config,
|
| 622 |
+
forwarded_arg_names = forwarded_arg_names,
|
| 623 |
+
request_shape = request_shape,
|
| 624 |
+
stage = asa_api_runtime_stage(stage_ref, default = "invoke"),
|
| 625 |
+
error = e
|
| 626 |
+
)
|
| 627 |
+
asa_api_raise_runtime_failure(
|
| 628 |
+
error = e,
|
| 629 |
+
diagnostic = diagnostic,
|
| 630 |
+
mode = mode,
|
| 631 |
+
status_code = status_code,
|
| 632 |
+
log_con = log_con
|
| 633 |
+
)
|
| 634 |
+
}
|
| 635 |
+
)
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
asa_api_error_failure <- function(error) {
|
| 639 |
+
message <- conditionMessage(error)
|
| 640 |
+
failure <- list(
|
| 641 |
+
status_code = asa_api_error_status(message),
|
| 642 |
+
message = message
|
| 643 |
+
)
|
| 644 |
+
|
| 645 |
+
code <- asa_api_scalar_chr(error$error_code %||% "", default = "")
|
| 646 |
+
if (nzchar(code)) {
|
| 647 |
+
failure$error_code <- code
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
details <- error$details %||% NULL
|
| 651 |
+
if (requireNamespace("reticulate", quietly = TRUE)) {
|
| 652 |
+
details <- tryCatch(reticulate::py_to_r(details), error = function(e) details)
|
| 653 |
+
}
|
| 654 |
+
if (!is.null(details) && !is.list(details)) {
|
| 655 |
+
details <- asa_api_to_plain(details)
|
| 656 |
+
}
|
| 657 |
+
if (is.list(details) && length(details)) {
|
| 658 |
+
failure$details <- details
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
explicit_status <- asa_api_scalar_int(error$status_code %||% NA_integer_, default = NA_integer_)
|
| 662 |
+
if (!is.na(explicit_status)) {
|
| 663 |
+
failure$status_code <- explicit_status
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
failure
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
asa_api_request_context_route <- function(request_context, default) {
|
| 670 |
+
context <- asa_api_named_list(request_context)
|
| 671 |
+
asa_api_scalar_chr(context$route, default = default)
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
asa_api_request_context_log_con <- function(request_context) {
|
| 675 |
+
context <- asa_api_named_list(request_context)
|
| 676 |
+
context$log_con %||% stderr()
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
asa_api_missing_auth_env_vars <- function() {
|
| 680 |
required <- c("ASA_API_BEARER_TOKEN", "GUI_PASSWORD")
|
| 681 |
required[!nzchar(vapply(required, asa_api_secret_env_value, character(1)))]
|
|
|
|
| 1118 |
(is.list(value) && !is.null(value$status) && !is.null(value$message))
|
| 1119 |
}
|
| 1120 |
|
| 1121 |
+
asa_api_run_single_via_asa <- function(payload, request_context = NULL) {
|
| 1122 |
asa_api_apply_env_defaults()
|
| 1123 |
prompt <- asa_api_require_prompt(payload)
|
| 1124 |
config <- asa_api_build_config(payload)
|
| 1125 |
run_args <- asa_api_build_run_args(payload)
|
| 1126 |
+
route <- asa_api_request_context_route(request_context, "/v1/run")
|
| 1127 |
+
log_con <- asa_api_request_context_log_con(request_context)
|
| 1128 |
+
stage_ref <- new.env(parent = emptyenv())
|
| 1129 |
+
stage_ref$value <- "invoke"
|
| 1130 |
+
request_shape <- asa_api_drop_nulls(list(
|
| 1131 |
+
output_format = asa_api_scalar_chr(run_args$output_format, default = "text"),
|
| 1132 |
+
performance_profile = asa_api_scalar_chr(run_args$performance_profile, default = ""),
|
| 1133 |
+
use_plan_mode = if (!is.null(run_args$use_plan_mode)) isTRUE(run_args$use_plan_mode) else NULL
|
| 1134 |
+
))
|
| 1135 |
|
| 1136 |
args <- c(list(prompt = prompt, config = config), run_args)
|
| 1137 |
args <- asa_api_filter_formals(asa::run_task, args)
|
| 1138 |
|
| 1139 |
+
result <- asa_api_invoke_with_runtime_diagnostics(
|
| 1140 |
+
fn = function() do.call(asa::run_task, args),
|
| 1141 |
+
mode = "asa_agent_single",
|
| 1142 |
+
route = route,
|
| 1143 |
+
payload = payload,
|
| 1144 |
+
prompt = prompt,
|
| 1145 |
+
config = config,
|
| 1146 |
+
forwarded_arg_names = names(run_args),
|
| 1147 |
+
request_shape = request_shape,
|
| 1148 |
+
stage_ref = stage_ref,
|
| 1149 |
+
log_con = log_con
|
| 1150 |
+
)
|
| 1151 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 1152 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 1153 |
+
stage_ref$value <- "normalize_result"
|
| 1154 |
+
|
| 1155 |
+
asa_api_invoke_with_runtime_diagnostics(
|
| 1156 |
+
fn = function() {
|
| 1157 |
+
asa_api_normalize_result(
|
| 1158 |
+
result,
|
| 1159 |
+
include_raw_output = include_raw_output,
|
| 1160 |
+
include_trace_json = include_trace_json
|
| 1161 |
+
)
|
| 1162 |
+
},
|
| 1163 |
+
mode = "asa_agent_single",
|
| 1164 |
+
route = route,
|
| 1165 |
+
payload = payload,
|
| 1166 |
+
prompt = prompt,
|
| 1167 |
+
config = config,
|
| 1168 |
+
forwarded_arg_names = names(run_args),
|
| 1169 |
+
request_shape = request_shape,
|
| 1170 |
+
stage_ref = stage_ref,
|
| 1171 |
+
log_con = log_con
|
| 1172 |
)
|
| 1173 |
}
|
| 1174 |
|
| 1175 |
+
asa_api_run_single_via_direct <- function(payload, request_context = NULL) {
|
| 1176 |
asa_api_apply_env_defaults()
|
| 1177 |
run_direct_task_fun <- asa_api_get_run_direct_task()
|
| 1178 |
prompt <- asa_api_require_prompt(payload)
|
| 1179 |
config <- asa_api_build_config(payload)
|
| 1180 |
direct_args <- asa_api_build_direct_args(payload, run_direct_task_fun = run_direct_task_fun)
|
| 1181 |
+
route <- asa_api_request_context_route(request_context, "/gui/query")
|
| 1182 |
+
log_con <- asa_api_request_context_log_con(request_context)
|
| 1183 |
+
stage_ref <- new.env(parent = emptyenv())
|
| 1184 |
+
stage_ref$value <- "invoke"
|
| 1185 |
+
request_shape <- asa_api_drop_nulls(list(
|
| 1186 |
+
output_format = asa_api_scalar_chr(direct_args$output_format, default = "text"),
|
| 1187 |
+
performance_profile = asa_api_scalar_chr(direct_args$performance_profile, default = ""),
|
| 1188 |
+
use_plan_mode = if (!is.null(direct_args$use_plan_mode)) isTRUE(direct_args$use_plan_mode) else NULL
|
| 1189 |
+
))
|
| 1190 |
|
| 1191 |
args <- c(list(prompt = prompt, config = config), direct_args)
|
| 1192 |
args <- asa_api_filter_formals(run_direct_task_fun, args)
|
| 1193 |
|
| 1194 |
+
result <- asa_api_invoke_with_runtime_diagnostics(
|
| 1195 |
+
fn = function() do.call(run_direct_task_fun, args),
|
| 1196 |
+
mode = "provider_direct_single",
|
| 1197 |
+
route = route,
|
| 1198 |
+
payload = payload,
|
| 1199 |
+
prompt = prompt,
|
| 1200 |
+
config = config,
|
| 1201 |
+
forwarded_arg_names = names(direct_args),
|
| 1202 |
+
request_shape = request_shape,
|
| 1203 |
+
stage_ref = stage_ref,
|
| 1204 |
+
log_con = log_con
|
| 1205 |
+
)
|
| 1206 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 1207 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 1208 |
+
stage_ref$value <- "normalize_result"
|
| 1209 |
+
|
| 1210 |
+
asa_api_invoke_with_runtime_diagnostics(
|
| 1211 |
+
fn = function() {
|
| 1212 |
+
asa_api_normalize_result(
|
| 1213 |
+
result,
|
| 1214 |
+
include_raw_output = include_raw_output,
|
| 1215 |
+
include_trace_json = include_trace_json
|
| 1216 |
+
)
|
| 1217 |
+
},
|
| 1218 |
+
mode = "provider_direct_single",
|
| 1219 |
+
route = route,
|
| 1220 |
+
payload = payload,
|
| 1221 |
+
prompt = prompt,
|
| 1222 |
+
config = config,
|
| 1223 |
+
forwarded_arg_names = names(direct_args),
|
| 1224 |
+
request_shape = request_shape,
|
| 1225 |
+
stage_ref = stage_ref,
|
| 1226 |
+
log_con = log_con
|
| 1227 |
)
|
| 1228 |
}
|
| 1229 |
|
| 1230 |
+
asa_api_run_single <- function(payload, allow_direct_provider = FALSE, request_context = NULL) {
|
| 1231 |
use_direct_provider <- isTRUE(allow_direct_provider) &&
|
| 1232 |
asa_api_to_bool(payload$use_direct_provider, default = FALSE)
|
| 1233 |
|
| 1234 |
if (use_direct_provider) {
|
| 1235 |
+
return(asa_api_run_single_via_direct(payload, request_context = request_context))
|
| 1236 |
}
|
| 1237 |
|
| 1238 |
+
asa_api_run_single_via_asa(payload, request_context = request_context)
|
| 1239 |
}
|
| 1240 |
|
| 1241 |
+
asa_api_run_batch <- function(payload, request_context = NULL) {
|
| 1242 |
asa_api_apply_env_defaults()
|
| 1243 |
prompts <- asa_api_require_prompts(payload)
|
| 1244 |
asa_api_validate_batch_supported_fields(payload)
|
| 1245 |
config <- asa_api_build_config(payload)
|
| 1246 |
batch_args <- asa_api_build_batch_args(payload)
|
| 1247 |
+
route <- asa_api_request_context_route(request_context, "/v1/batch")
|
| 1248 |
+
log_con <- asa_api_request_context_log_con(request_context)
|
| 1249 |
+
stage_ref <- new.env(parent = emptyenv())
|
| 1250 |
+
stage_ref$value <- "invoke"
|
| 1251 |
+
request_shape <- asa_api_drop_nulls(list(
|
| 1252 |
+
output_format = asa_api_scalar_chr(batch_args$output_format, default = "text"),
|
| 1253 |
+
parallel = if (!is.null(batch_args$parallel)) isTRUE(batch_args$parallel) else NULL,
|
| 1254 |
+
progress = if (!is.null(batch_args$progress)) isTRUE(batch_args$progress) else NULL,
|
| 1255 |
+
performance_profile = asa_api_scalar_chr(batch_args$performance_profile, default = "")
|
| 1256 |
+
))
|
| 1257 |
|
| 1258 |
args <- c(list(prompts = prompts, config = config), batch_args)
|
| 1259 |
args <- asa_api_filter_formals(asa::run_task_batch, args)
|
| 1260 |
|
| 1261 |
+
raw <- asa_api_invoke_with_runtime_diagnostics(
|
| 1262 |
+
fn = function() do.call(asa::run_task_batch, args),
|
| 1263 |
+
mode = "asa_agent_batch",
|
| 1264 |
+
route = route,
|
| 1265 |
+
payload = payload,
|
| 1266 |
+
prompts = prompts,
|
| 1267 |
+
config = config,
|
| 1268 |
+
forwarded_arg_names = names(batch_args),
|
| 1269 |
+
request_shape = request_shape,
|
| 1270 |
+
stage_ref = stage_ref,
|
| 1271 |
+
log_con = log_con
|
| 1272 |
+
)
|
| 1273 |
include_raw_output <- asa_api_to_bool(payload$include_raw_output, default = FALSE)
|
| 1274 |
include_trace_json <- asa_api_to_bool(payload$include_trace_json, default = FALSE)
|
| 1275 |
+
stage_ref$value <- "normalize_batch_result"
|
| 1276 |
+
|
| 1277 |
+
asa_api_invoke_with_runtime_diagnostics(
|
| 1278 |
+
fn = function() {
|
| 1279 |
+
if (is.data.frame(raw) && "asa_result" %in% names(raw)) {
|
| 1280 |
+
items <- lapply(raw$asa_result, asa_api_normalize_result,
|
| 1281 |
+
include_raw_output = include_raw_output,
|
| 1282 |
+
include_trace_json = include_trace_json
|
| 1283 |
+
)
|
| 1284 |
+
} else if (is.list(raw) && length(raw) && all(vapply(raw, asa_api_is_result_like, logical(1)))) {
|
| 1285 |
+
items <- lapply(raw, asa_api_normalize_result,
|
| 1286 |
+
include_raw_output = include_raw_output,
|
| 1287 |
+
include_trace_json = include_trace_json
|
| 1288 |
+
)
|
| 1289 |
+
} else if (is.list(raw) && length(raw) == 0L) {
|
| 1290 |
+
items <- list()
|
| 1291 |
+
} else {
|
| 1292 |
+
items <- list(asa_api_to_plain(raw))
|
| 1293 |
+
}
|
| 1294 |
|
| 1295 |
+
list(
|
| 1296 |
+
status = "success",
|
| 1297 |
+
results = items,
|
| 1298 |
+
count = length(items),
|
| 1299 |
+
circuit_breaker_aborted = isTRUE(attr(raw, "circuit_breaker_aborted"))
|
| 1300 |
+
)
|
| 1301 |
+
},
|
| 1302 |
+
mode = "asa_agent_batch",
|
| 1303 |
+
route = route,
|
| 1304 |
+
payload = payload,
|
| 1305 |
+
prompts = prompts,
|
| 1306 |
+
config = config,
|
| 1307 |
+
forwarded_arg_names = names(batch_args),
|
| 1308 |
+
request_shape = request_shape,
|
| 1309 |
+
stage_ref = stage_ref,
|
| 1310 |
+
log_con = log_con
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1311 |
)
|
| 1312 |
}
|
| 1313 |
|
R/plumber.R
CHANGED
|
@@ -58,16 +58,6 @@ asa_api_failure_payload <- function(res, failure) {
|
|
| 58 |
)
|
| 59 |
}
|
| 60 |
|
| 61 |
-
asa_api_error_status <- function(message) {
|
| 62 |
-
if (grepl("direct-provider mode|does not provide `run_direct_task`", message, ignore.case = TRUE)) {
|
| 63 |
-
return(501L)
|
| 64 |
-
}
|
| 65 |
-
if (grepl("invalid json|required|must be|non-empty|unauthorized|password", message, ignore.case = TRUE)) {
|
| 66 |
-
return(400L)
|
| 67 |
-
}
|
| 68 |
-
500L
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
#* @filter cors
|
| 72 |
function(req, res) {
|
| 73 |
allow_origin <- trimws(Sys.getenv("CORS_ALLOW_ORIGIN", unset = "*"))
|
|
@@ -142,10 +132,13 @@ function(req, res) {
|
|
| 142 |
}
|
| 143 |
|
| 144 |
tryCatch(
|
| 145 |
-
asa_api_run_single(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
error = function(e) {
|
| 147 |
-
|
| 148 |
-
asa_api_error_payload(res, asa_api_error_status(msg), msg)
|
| 149 |
}
|
| 150 |
)
|
| 151 |
}
|
|
@@ -172,10 +165,12 @@ function(req, res) {
|
|
| 172 |
}
|
| 173 |
|
| 174 |
tryCatch(
|
| 175 |
-
asa_api_run_batch(
|
|
|
|
|
|
|
|
|
|
| 176 |
error = function(e) {
|
| 177 |
-
|
| 178 |
-
asa_api_error_payload(res, asa_api_error_status(msg), msg)
|
| 179 |
}
|
| 180 |
)
|
| 181 |
}
|
|
@@ -211,11 +206,14 @@ function(req, res) {
|
|
| 211 |
|
| 212 |
tryCatch(
|
| 213 |
asa_api_sanitize_gui_result(
|
| 214 |
-
asa_api_run_single(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
),
|
| 216 |
error = function(e) {
|
| 217 |
-
|
| 218 |
-
asa_api_error_payload(res, asa_api_error_status(msg), msg)
|
| 219 |
}
|
| 220 |
)
|
| 221 |
}
|
|
|
|
| 58 |
)
|
| 59 |
}
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
#* @filter cors
|
| 62 |
function(req, res) {
|
| 63 |
allow_origin <- trimws(Sys.getenv("CORS_ALLOW_ORIGIN", unset = "*"))
|
|
|
|
| 132 |
}
|
| 133 |
|
| 134 |
tryCatch(
|
| 135 |
+
asa_api_run_single(
|
| 136 |
+
payload,
|
| 137 |
+
allow_direct_provider = FALSE,
|
| 138 |
+
request_context = list(route = "/v1/run")
|
| 139 |
+
),
|
| 140 |
error = function(e) {
|
| 141 |
+
asa_api_failure_payload(res, asa_api_error_failure(e))
|
|
|
|
| 142 |
}
|
| 143 |
)
|
| 144 |
}
|
|
|
|
| 165 |
}
|
| 166 |
|
| 167 |
tryCatch(
|
| 168 |
+
asa_api_run_batch(
|
| 169 |
+
payload,
|
| 170 |
+
request_context = list(route = "/v1/batch")
|
| 171 |
+
),
|
| 172 |
error = function(e) {
|
| 173 |
+
asa_api_failure_payload(res, asa_api_error_failure(e))
|
|
|
|
| 174 |
}
|
| 175 |
)
|
| 176 |
}
|
|
|
|
| 206 |
|
| 207 |
tryCatch(
|
| 208 |
asa_api_sanitize_gui_result(
|
| 209 |
+
asa_api_run_single(
|
| 210 |
+
payload,
|
| 211 |
+
allow_direct_provider = TRUE,
|
| 212 |
+
request_context = list(route = "/gui/query")
|
| 213 |
+
)
|
| 214 |
),
|
| 215 |
error = function(e) {
|
| 216 |
+
asa_api_failure_payload(res, asa_api_error_failure(e))
|
|
|
|
| 217 |
}
|
| 218 |
)
|
| 219 |
}
|
Tests/api_contract_smoke.R
CHANGED
|
@@ -322,6 +322,86 @@ assert_true(
|
|
| 322 |
"Route error rendering should preserve structured auth diagnostics."
|
| 323 |
)
|
| 324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
health_payload <- asa_api_health_payload()
|
| 326 |
assert_true(
|
| 327 |
identical(isTRUE(health_payload$direct_provider_available), isTRUE(asa_api_has_run_direct_task())),
|
|
@@ -364,11 +444,11 @@ asa_api_refresh_auth_cache(force = TRUE)
|
|
| 364 |
original_direct <- asa_api_run_single_via_direct
|
| 365 |
|
| 366 |
dispatch_calls <- character(0)
|
| 367 |
-
asa_api_run_single_via_asa <- function(payload) {
|
| 368 |
dispatch_calls <<- c(dispatch_calls, "asa")
|
| 369 |
list(status = "success", execution = list(mode = "asa_agent"))
|
| 370 |
}
|
| 371 |
-
asa_api_run_single_via_direct <- function(payload) {
|
| 372 |
dispatch_calls <<- c(dispatch_calls, "direct")
|
| 373 |
list(status = "success", execution = list(mode = "provider_direct"))
|
| 374 |
}
|
|
|
|
| 322 |
"Route error rendering should preserve structured auth diagnostics."
|
| 323 |
)
|
| 324 |
|
| 325 |
+
assert_true(
|
| 326 |
+
identical(asa_api_runtime_error_code("asa_agent_batch"), "agent_batch_failure") &&
|
| 327 |
+
identical(asa_api_runtime_error_code("provider_direct_single"), "direct_provider_failure") &&
|
| 328 |
+
identical(asa_api_runtime_error_code("asa_agent_single"), "agent_pipeline_failure"),
|
| 329 |
+
"Runtime failures should map to stable mode-specific error codes."
|
| 330 |
+
)
|
| 331 |
+
|
| 332 |
+
diagnostic_prompt <- paste(rep("diagnostic prompt segment", 16), collapse = " ")
|
| 333 |
+
expected_excerpt <- asa_api_redact_text_excerpt(diagnostic_prompt)
|
| 334 |
+
diagnostic_log_lines <- character(0)
|
| 335 |
+
diagnostic_log_con <- textConnection("diagnostic_log_lines", "w", local = TRUE)
|
| 336 |
+
runtime_error <- NULL
|
| 337 |
+
tryCatch(
|
| 338 |
+
asa_api_invoke_with_runtime_diagnostics(
|
| 339 |
+
fn = function() {
|
| 340 |
+
stop("subscript out of bounds", call. = FALSE)
|
| 341 |
+
},
|
| 342 |
+
mode = "asa_agent_single",
|
| 343 |
+
route = "/v1/run",
|
| 344 |
+
payload = list(
|
| 345 |
+
include_raw_output = TRUE,
|
| 346 |
+
include_trace_json = FALSE
|
| 347 |
+
),
|
| 348 |
+
prompt = diagnostic_prompt,
|
| 349 |
+
config = list(
|
| 350 |
+
backend = "gemini",
|
| 351 |
+
model = "gemini-2.5-pro",
|
| 352 |
+
conda_env = "asa_env",
|
| 353 |
+
use_browser = FALSE
|
| 354 |
+
),
|
| 355 |
+
forwarded_arg_names = c("output_format", "performance_profile"),
|
| 356 |
+
request_shape = list(
|
| 357 |
+
output_format = "json",
|
| 358 |
+
performance_profile = "quality"
|
| 359 |
+
),
|
| 360 |
+
stage_ref = "invoke",
|
| 361 |
+
log_con = diagnostic_log_con
|
| 362 |
+
),
|
| 363 |
+
error = function(e) {
|
| 364 |
+
runtime_error <<- e
|
| 365 |
+
NULL
|
| 366 |
+
}
|
| 367 |
+
)
|
| 368 |
+
close(diagnostic_log_con)
|
| 369 |
+
assert_true(
|
| 370 |
+
inherits(runtime_error, "asa_api_runtime_error") &&
|
| 371 |
+
identical(runtime_error$error_code, "agent_pipeline_failure"),
|
| 372 |
+
"Runtime diagnostics should rethrow a structured asa_api_runtime_error."
|
| 373 |
+
)
|
| 374 |
+
runtime_failure <- asa_api_error_failure(runtime_error)
|
| 375 |
+
assert_true(
|
| 376 |
+
identical(runtime_failure$status_code, 500L) &&
|
| 377 |
+
identical(runtime_failure$error_code, "agent_pipeline_failure") &&
|
| 378 |
+
identical(runtime_failure$details$route, "/v1/run") &&
|
| 379 |
+
identical(runtime_failure$details$stage, "invoke") &&
|
| 380 |
+
identical(runtime_failure$details$request$output_format, "json") &&
|
| 381 |
+
identical(runtime_failure$details$request$include_raw_output, TRUE) &&
|
| 382 |
+
nzchar(runtime_failure$details$diagnostic_id),
|
| 383 |
+
"Structured runtime failures should preserve diagnostic metadata for API responses."
|
| 384 |
+
)
|
| 385 |
+
diagnostic_log_text <- paste(diagnostic_log_lines, collapse = "\n")
|
| 386 |
+
assert_true(
|
| 387 |
+
grepl("diagnostic_id=", diagnostic_log_text, fixed = TRUE) &&
|
| 388 |
+
grepl("subscript out of bounds", diagnostic_log_text, fixed = TRUE),
|
| 389 |
+
"Runtime diagnostics should log the failure message with a correlation id."
|
| 390 |
+
)
|
| 391 |
+
assert_true(
|
| 392 |
+
grepl(expected_excerpt, diagnostic_log_text, fixed = TRUE) &&
|
| 393 |
+
!grepl(diagnostic_prompt, diagnostic_log_text, fixed = TRUE),
|
| 394 |
+
"Runtime diagnostics should log only a redacted prompt excerpt, not the full prompt."
|
| 395 |
+
)
|
| 396 |
+
runtime_res_env <- new.env(parent = emptyenv())
|
| 397 |
+
runtime_payload <- asa_api_failure_payload(runtime_res_env, runtime_failure)
|
| 398 |
+
assert_true(
|
| 399 |
+
identical(runtime_res_env$status, 500L) &&
|
| 400 |
+
identical(runtime_payload$error_code, "agent_pipeline_failure") &&
|
| 401 |
+
identical(runtime_payload$details$diagnostic_id, runtime_failure$details$diagnostic_id),
|
| 402 |
+
"Route error rendering should preserve structured runtime diagnostics."
|
| 403 |
+
)
|
| 404 |
+
|
| 405 |
health_payload <- asa_api_health_payload()
|
| 406 |
assert_true(
|
| 407 |
identical(isTRUE(health_payload$direct_provider_available), isTRUE(asa_api_has_run_direct_task())),
|
|
|
|
| 444 |
original_direct <- asa_api_run_single_via_direct
|
| 445 |
|
| 446 |
dispatch_calls <- character(0)
|
| 447 |
+
asa_api_run_single_via_asa <- function(payload, request_context = NULL) {
|
| 448 |
dispatch_calls <<- c(dispatch_calls, "asa")
|
| 449 |
list(status = "success", execution = list(mode = "asa_agent"))
|
| 450 |
}
|
| 451 |
+
asa_api_run_single_via_direct <- function(payload, request_context = NULL) {
|
| 452 |
dispatch_calls <<- c(dispatch_calls, "direct")
|
| 453 |
list(status = "success", execution = list(mode = "provider_direct"))
|
| 454 |
}
|