harn-stdlib 0.10.130

Embedded Harn standard library source catalog
Documentation
import {
  agent_tool_lifecycle_error_category,
  agent_tool_result_is_ok,
  agent_tool_result_outcome,
} from "std/agent/tool_lifecycle"

/**
 * Host dispatches are canonically a result list; a turn without tools has no
 * dispatch. Normalize the absent case here so downstream policy stays shape
 * agnostic.
 */
pub type AgentToolDispatch = list?

pub fn __dispatch_results_list(dispatch: AgentToolDispatch) -> list {
  return dispatch ?? []
}

fn __error_category_is_dispatch_rejection(category: any) -> bool {
  return category == "schema_validation" || category == "permission_denied"
}

/**
 * Dispatch rejection from STRUCTURED fields only: the outer envelope's typed
 * error category (via `agent_tool_result_outcome`), or the nested host body
 * `result.error` / `result.error_category` that denial producers already stamp.
 * Never substring-matches rendered prose.
 *
 * @effects: []
 * @errors: []
 */
pub fn __tool_result_dispatch_rejection(result: dict) -> bool {
  if type_of(result) != "dict" {
    return false
  }
  const outcome = agent_tool_result_outcome(result)
  if __error_category_is_dispatch_rejection(outcome.error_category) {
    return true
  }
  const nested = result?.result
  if type_of(nested) == "dict" {
    const nested_cat = agent_tool_lifecycle_error_category(nested?.error_category ?? nested?.error)
    if __error_category_is_dispatch_rejection(nested_cat) {
      return true
    }
  }
  return false
}

pub fn __tool_result_product_error(result: dict) -> bool {
  if type_of(result) != "dict" {
    return false
  }
  const outcome = agent_tool_result_outcome(result)
  if outcome.product_error != nil {
    return outcome.product_error ? true : false
  }
  return __tool_result_dispatch_rejection(result)
}

pub fn __tool_result_ok(result: dict) -> bool {
  const outcome = agent_tool_result_outcome(result)
  const structured = agent_tool_result_is_ok(outcome)
  if structured == false {
    return false
  }
  if __tool_result_product_error(result) {
    return false
  }
  if structured == true {
    return true
  }
  if result?.ok != nil {
    return result.ok ? true : false
  }
  if result?.success != nil {
    return result.success ? true : false
  }
  const status = result?.status ?? ""
  return status == "ok" || status == "success"
}

/**
 * The outcomes a tool call can end in that the AGENT did not choose.
 *
 * A call the user cancelled, or one the loop abandoned while exiting, is not
 * evidence about whether the agent gave up: someone else ended it. Counting
 * either as a failed attempt would attribute a user's cancel to the model, and
 * a run whose only tool call the user cancelled would be reported as abandoned
 * by the agent.
 */
const __AGENT_TOOL_NOT_THE_AGENTS_OUTCOME = ["cancelled", "abandoned_at_loop_exit"]

/**
 * The typed error category of a failing tool result, from STRUCTURED fields
 * only: the outer envelope's category, else the nested host body's. Never a
 * substring match on rendered prose.
 *
 * Returns `unclassified` for a result that failed carrying no typed category,
 * so a reader can tell "failed with no category" from "did not fail" instead
 * of both arriving as nothing.
 *
 * @effects: []
 * @errors: []
 */
pub fn __tool_result_failure_category(result: dict) -> string? {
  if type_of(result) != "dict" || __tool_result_ok(result) {
    return nil
  }
  const nested = result?.result
  const nested_cat = if type_of(nested) == "dict" {
    agent_tool_lifecycle_error_category(nested?.error_category ?? nested?.error)
  } else {
    nil
  }
  const resolved = agent_tool_result_outcome(result).error_category ?? nested_cat
  if resolved == nil {
    return "unclassified"
  }
  return to_string(resolved)
}

/**
 * How many tool calls a dispatch made and how many of them worked.
 *
 * Deliberately NOT derivable from the session's `successful_tools_seen` and
 * `rejected_tools_seen`. Those lists deduplicate by tool NAME, so their lengths
 * answer "which tools worked", not "how many calls were made". A terminal that
 * printed one while calling it the other would be a mislabeled instrument, and
 * telling two abandoned runs apart is the whole point of this evidence.
 *
 * Calls that ended in an outcome the agent did not choose are counted in
 * neither total, so they cannot convict and cannot acquit.
 *
 * @effects: []
 * @errors: []
 */
pub fn __dispatch_call_tally(dispatch: AgentToolDispatch) -> dict {
  let attempted = 0
  let succeeded = 0
  for result in __dispatch_results_list(dispatch) {
    if type_of(result) != "dict" {
      continue
    }
    const category = __tool_result_failure_category(result)
    if category != nil && contains(__AGENT_TOOL_NOT_THE_AGENTS_OUTCOME, category) {
      continue
    }
    attempted = attempted + 1
    if __tool_result_ok(result) {
      succeeded = succeeded + 1
    }
  }
  return {attempted: attempted, succeeded: succeeded}
}

/**
 * The typed category of the last counted failure in a dispatch.
 *
 * Skips the same outcomes the tally skips, so the reported category always
 * names something the counts actually reflect.
 *
 * @effects: []
 * @errors: []
 */
pub fn __dispatch_last_rejection_category(dispatch: AgentToolDispatch) -> string? {
  let category = nil
  for result in __dispatch_results_list(dispatch) {
    const resolved = __tool_result_failure_category(result)
    if resolved != nil && !contains(__AGENT_TOOL_NOT_THE_AGENTS_OUTCOME, resolved) {
      category = resolved
    }
  }
  return category
}

pub fn __tool_result_name(result: dict) {
  return result?.tool_name ?? result?.name ?? ""
}

pub fn __tool_names_by_status(dispatch: AgentToolDispatch, want_ok: bool) -> list<string> {
  const results = __dispatch_results_list(dispatch)
  let names = []
  for result in results {
    const name = __tool_result_name(result)
    if name != "" && __tool_result_ok(result) == want_ok {
      names = names.appending(name)
    }
  }
  return names
}