harn-stdlib 0.10.34

Embedded Harn standard library source catalog
Documentation
// @harn-entrypoint-category llm.stdlib
//
// std/llm/envelope — THE canonical `llm_call` response contract.
//
// One rigid snake_case envelope, one spelling per field, all accounting owned
// by `usage`, and a typed `outcome` classification so no consumer ever
// re-derives "what did this call produce?" from stop-reason vocabularies or
// token probing. The Rust projection (`vm_build_llm_result` in
// `crates/harn-vm/src/llm/api/result.rs`) is the single producer; its
// `envelope_top_level_keys_are_canonical` test and these types must move
// together.

// -------------------------------------------------------------------------------------------------

/**
 * Canonical outcome vocabulary for a completed (non-thrown) LLM call.
 *
 * - `complete`  — the model committed a normal answer and stopped cleanly.
 * - `tool_use`  — the actionable content is one or more tool calls.
 * - `truncated` — generation was cut on an output-token limit; text and
 *   especially tool-call arguments are suspect.
 * - `refused`   — the provider refused or filtered the completion.
 * - `paused`    — the provider paused the turn (e.g. Anthropic `pause_turn`);
 *   resume it rather than judging it.
 * - `empty`     — nothing usable was committed: no visible text, no tool
 *   calls, no thinking.
 */
pub type LlmOutcomeKind = "complete" | "tool_use" | "truncated" | "refused" | "paused" | "empty"

/**
 * Typed outcome carried on every `llm_call` response. `billed` is true when
 * the provider charged tokens for the call; `kind == "empty"` together with
 * `billed` is the billed-noncommittal signal (the S2 failure class in the
 * tool-calling north star) that retry policy keys on.
 */
pub type LlmOutcome = {kind: LlmOutcomeKind, billed: bool}

/**
 * Single owner of ALL call accounting. No usage field is duplicated at the
 * envelope's top level.
 *
 * `cost_usd` is nil (not 0) when catalog pricing is unknown. `cache_hit_ratio`
 * is nil with `cache_visibility: "unsupported"` when the provider reports no
 * cache accounting at all (native Ollama), so a local model is never scored as
 * a 100% cache miss.
 */
pub type LlmUsage = {
  input_tokens: int,
  output_tokens: int,
  cost_usd?: float,
  cache_read_tokens: int,
  cache_write_tokens: int,
  cache_hit_ratio?: float,
  cache_visibility?: string,
  cache_savings_usd: float,
  served_fast: bool,
  provider_telemetry?: dict,
}

/**
 * A dispatchable tool call from the merged (provider-native + text-protocol)
 * channel. `arguments` is the parsed argument dict; `id` is present when the
 * provider assigned one.
 */
pub type LlmToolCall = {id?: string, type?: string, name: string, arguments?: dict}

/**
 * The canonical `llm_call` response envelope.
 *
 * Text channels (each with a distinct job — none are aliases):
 * - `text`         — the public answer after tool/protocol projection.
 * - `raw_text`     — the pre-projection parser source (protocol tags intact).
 * - `visible_text` — sanitized human-visible output.
 * - `canonical_text` — canonical replay form of a tagged-protocol response.
 *
 * `tool_calls` (merged) and `native_tool_calls` (provider-native only) are
 * always present, possibly empty. `stop_reason` is the provider-native stop
 * vocabulary kept for forensics; `outcome` is the canonical interpretation —
 * consumers should branch on `outcome`, never on `stop_reason`.
 */
pub type LlmResponse = {
  model: string,
  provider: string,
  usage: LlmUsage,
  outcome: LlmOutcome,
  text: string,
  raw_text: string,
  visible_text: string,
  canonical_text?: string,
  data?: any,
  thinking?: string,
  thinking_summary?: string,
  stop_reason?: string,
  tool_calls: list<LlmToolCall>,
  native_tool_calls: list<LlmToolCall>,
  protocol_violations?: list<string>,
  tool_parse_errors?: list<string>,
  done_marker?: string,
  provider_response_id?: string,
  transcript?: dict,
  blocks: list,
  logprobs?: list,
  routing?: dict,
}

/**
 * One streamed chunk from `llm_stream_call`. The terminal chunk carries the
 * envelope's `stop_reason` (same spelling as `LlmResponse.stop_reason`);
 * non-terminal chunks carry nil.
 */
pub type LlmStreamChunk = {
  delta: string,
  visible_delta: string,
  partial: string,
  role: string,
  stop_reason?: string,
}

// -------------------------------------------------------------------------------------------------

fn __envelope_outcome_kind(response) -> string {
  return to_string(response?.outcome?.kind ?? "")
}

/**
 * True when the response committed nothing usable (outcome `empty`).
 *
 * @effects: []
 * @errors: []
 */
pub fn llm_response_is_empty(response) -> bool {
  return __envelope_outcome_kind(response) == "empty"
}

/**
 * True for the billed-noncommittal class: the provider charged tokens and the
 * response committed nothing usable. This is the condition default retry
 * policy re-dispatches on.
 *
 * @effects: []
 * @errors: []
 */
pub fn llm_response_is_billed_empty(response) -> bool {
  return llm_response_is_empty(response) && (response?.outcome?.billed ?? false)
}

/**
 * True when generation was cut on an output-token limit. A truncated response
 * with tool calls must not be dispatched as a clean tool_use — arguments may
 * be partial.
 *
 * @effects: []
 * @errors: []
 */
pub fn llm_response_is_truncated(response) -> bool {
  return __envelope_outcome_kind(response) == "truncated"
}