harn-stdlib 0.8.49

Embedded Harn standard library source catalog
Documentation
/**
 * `eval/model_selector` — `.harn` port of the model-selector resolver
 * helper shared across `harn eval *` commands. See harn#2306 (W6).
 *
 * **Helper-only module.** The Rust-side `eval_model_selector.rs` is not
 * a CLI subcommand — it is a pure helper invoked by `eval_tool_calls`,
 * `eval_coding_agent`, etc. Porting it here gives the `.harn`-side
 * renderers a parallel implementation they can call once the wider
 * cluster ports land. Today, callers are still on the Rust helper.
 *
 * Why the parallel impl ships now: the W6 epic explicitly asked for
 * `model_selector` alongside `context` and `tool_calls` (see #2306).
 * Embedding it here pins the .harn surface so the renderers in the
 * same wave can reach for the resolver without round-tripping through
 * the Rust shim. The alias-resolution branch (the third leg of the
 * Rust resolver — `harn_vm::llm_config::resolve_model_info`) is not
 * reachable from script-land today and is delegated to the shim via
 * `HARN_MODEL_SELECTOR_ALIASES_JSON` (an alias→{provider, model} map
 * the Rust caller pre-resolves once and forwards). G4 (#2297) will
 * expose the provider catalog directly to scripts and let us drop the
 * env hand-off.
 *
 * The script's `main` is a tiny REPL-style entrypoint that resolves a
 * single selector from `HARN_MODEL_SELECTOR_INPUT` and prints the
 * resolved `{selector, provider, model}` as JSON. It exists so the
 * dispatch wedge has a stable invocation surface; production callers
 * use the helpers below directly from sibling scripts.
 */
/**
 * Parse a `provider=...,model=...` comma-separated key-value form
 * (used by `--planner provider=openrouter,model=google/gemma` etc).
 * Returns `{provider, model}` on success or `nil` when either key is
 * absent or empty. Mirrors `parse_provider_model_kv` in the Rust
 * helper.
 */
fn __parse_provider_model_kv(raw: string) -> dict {
  var provider = ""
  var model = ""
  for part in split(raw, ",") {
    let idx = part.index_of("=")
    if idx < 0 {
      return {}
    }
    let key = part[0:idx].trim()
    let value = part[idx + 1:len(part)].trim()
    if key == "provider" {
      provider = value
    } else if key == "model" {
      model = value
    }
  }
  if provider == "" || model == "" {
    return {}
  }
  return {provider: provider, model: model}
}

/**
 * Resolve a raw selector string into `{selector, provider, model}`.
 *
 * Resolution precedence — matches the Rust `resolve_selector`:
 *   1. `provider=...,model=...` key-value form.
 *   2. `provider:model` colon-delimited form.
 *   3. Alias lookup via the pre-resolved `aliases` dict (the Rust
 *      caller supplies this via `HARN_MODEL_SELECTOR_ALIASES_JSON`).
 *      When the alias is absent the selector falls back to itself as
 *      both the provider and model — the legacy Rust path used the
 *      VM's `llm_config::resolve_model_info` which returns the same
 *      string in both fields for an unknown alias.
 */
fn resolve_selector(raw: string, aliases: dict) -> dict {
  let trimmed = raw.trim()
  let kv = __parse_provider_model_kv(trimmed)
  if len(keys(kv)) > 0 {
    return {selector: trimmed, provider: kv["provider"], model: kv["model"]}
  }
  let colon_idx = trimmed.index_of(":")
  if colon_idx >= 0 {
    let provider = trimmed[0:colon_idx]
    let model = trimmed[colon_idx + 1:len(trimmed)]
    if provider != "" && model != "" {
      return {selector: trimmed, provider: provider, model: model}
    }
  }
  let resolved = aliases[trimmed]
  if type_of(resolved) == "dict" {
    let provider = __safe_string_local(resolved["provider"], trimmed)
    let model = __safe_string_local(resolved["model"], trimmed)
    return {selector: trimmed, provider: provider, model: model}
  }
  return {selector: trimmed, provider: trimmed, model: trimmed}
}

/**
 * Render a selector as `provider:model`, matching the Rust
 * `selector_label`. Used in human-readable summaries.
 */
fn selector_label(selector: dict) -> string {
  return __safe_string_local(selector["provider"], "")
    + ":"
    + __safe_string_local(selector["model"], "")
}

/**
 * Return true when the resolved selector targets a local provider
 * (matches the Rust `selector_is_local`). Used by the runner to skip
 * cloud-only preflight checks on local-model phases.
 */
fn selector_is_local(selector: dict) -> bool {
  let provider = __safe_string_local(selector["provider"], "")
  return provider == "ollama"
    || provider == "llamacpp"
    || provider == "mlx"
    || provider == "local"
    || provider == "vllm"
    || provider == "tgi"
}

/**
 * Private `__safe_string` (kept local so this module stays
 * self-contained and re-readable as a unit). Kept byte-identical with
 * the copy in `_runner.harn`; the parity tests catch any drift.
 */
fn __safe_string_local(value, fallback: string) -> string {
  if type_of(value) == "string" {
    return value
  }
  return fallback
}

/**
 * Dispatch entrypoint for `HARN_CLI_IMPL=harn` callers that want to
 * resolve a single selector through the .harn helper. The Rust shim
 * has no such caller today — the helper is consumed in-process by
 * sibling .harn scripts — but exposing `main` keeps the dispatch
 * surface symmetrical and lets the parity tests black-box the helper
 * via the wedge.
 *
 * Inputs:
 *   HARN_MODEL_SELECTOR_INPUT        — raw selector string.
 *   HARN_MODEL_SELECTOR_ALIASES_JSON — JSON dict mapping alias →
 *                                       {provider, model}. Optional
 *                                       (defaults to `{}`).
 *
 * Output: one line of compact JSON on stdout —
 *   `{"selector":"...","provider":"...","model":"..."}`.
 */
fn main(harness: Harness) -> int {
  let raw = harness.env.get_or("HARN_MODEL_SELECTOR_INPUT", "")
  if raw == "" {
    harness.stdio
      .eprintln("internal error: HARN_MODEL_SELECTOR_INPUT not set")
    return 70
  }
  let aliases_json = harness.env.get_or("HARN_MODEL_SELECTOR_ALIASES_JSON", "{}")
  let aliases = try {
    json_parse(aliases_json)
  } catch (e) {
    harness.stdio.eprintln("internal error: failed to parse aliases JSON: " + to_string(e))
    return 70
  }
  let resolved = resolve_selector(raw, aliases)
  // Extend the resolved record with `label` and `is_local` so a
  // dispatch caller can read the full helper surface in a single
  // round-trip. Also keeps the linter from flagging the helpers as
  // unused — they're public API for sibling scripts but the main
  // entrypoint is currently the only consumer.
  let labelled = resolved + {label: selector_label(resolved), is_local: selector_is_local(resolved)}
  harness.stdio.println(json_stringify(labelled))
  return 0
}