harn-stdlib 0.10.42

Embedded Harn standard library source catalog
Documentation
/** Shared result projection for the text-tool dialect modules. */
import { IMPLICIT_TOOL_NAMES } from "std/llm/dialects"

/** Stable reason vocabulary for a response-protocol violation. */
pub type ProtocolViolationKind = "ambiguous_recovery" \
  | "bare_json" \
  | "empty_done" \
  | "fence_dialect" \
  | "provider_dialect" \
  | "recovered_dialect" \
  | "stray_text" \
  | "unclosed_response" \
  | "unknown_tag" \
  | "unparsed_tool_syntax" \
  | "wrong_tool_format"

/**
 * A model response violated the selected tool-call protocol.
 *
 * `message` is lossless corrective feedback, but consumers branch on `kind`.
 * Shape-derived violations also carry the bounded source `excerpt` and the
 * parser's `dropped_reason`, keeping classification structural end to end.
 */
pub type ProtocolViolation = {
  kind: ProtocolViolationKind,
  message: string,
  excerpt?: string,
  dropped_reason?: string,
}

/**
 * Construct one typed protocol violation.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn protocol_violation(
  kind: ProtocolViolationKind,
  message: string,
  excerpt: string? = nil,
  dropped_reason: string? = nil,
) -> ProtocolViolation {
  return {kind: kind, message: message, excerpt: excerpt, dropped_reason: dropped_reason}
}

/**
 * Render typed violations only at a human/model feedback edge.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn protocol_violation_messages(violations: list<ProtocolViolation>) -> list<string> {
  return violations.map(fn(violation) { return violation.message })
}

/**
 * Return the sorted declared and implicit tool names for parser policy.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn registry_names(tools) -> list<string> {
  let names: list<string> = []
  if type_of(tools) == "dict" {
    for entry in tools?.tools ?? [] {
      const name = trim(to_string(entry?.name ?? ""))
      if name != "" && !names.contains(name) {
        names = names.appending(name)
      }
    }
  }
  for name in IMPLICIT_TOOL_NAMES {
    if !names.contains(name) {
      names = names.appending(name)
    }
  }
  return names.sorted()
}

/**
 * Construct the complete empty text-tool parse projection.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn empty_parse() -> dict {
  return {
    calls: [],
    tool_calls: [],
    tool_parse_errors: [],
    protocol_violations: [],
    recovered_from_stray_count: 0,
    prose: "",
    user_response: nil,
    done_marker: nil,
    canonical_text: "",
    dropped: [],
  }
}

/**
 * Render one canonical tool-call response block.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn call_block(call) -> string {
  return "<tool_call>\n"
    + __host_tool_render_call(to_string(call?.name ?? ""), call?.arguments ?? {})
    + "\n</tool_call>"
}

/**
 * Render one canonical assistant-prose response block.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn prose_block(text: string) -> string {
  return "<assistant_prose>\n" + trim(text) + "\n</assistant_prose>"
}

/**
 * Render one canonical user-response block.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn answer_block(text: string) -> string {
  return "<user_response>\n" + trim(text) + "\n</user_response>"
}

/**
 * Join canonical response blocks with the protocol separator.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn canonical(parts: list<string>) -> string {
  return parts.join("\n\n")
}

/**
 * Project a provider-dialect parse into the shared result shape.
 * @effects: []
 * @errors: []
 * @api_stability: internal
 */
pub fn provider_result(calls, errors, prose: string, violations) -> dict {
  let parts: list<string> = []
  if trim(prose) != "" {
    parts = parts.appending(prose_block(prose))
  }
  for call in calls {
    parts = parts.appending(call_block(call))
  }
  return empty_parse()
    + {
    calls: calls,
    tool_calls: calls,
    tool_parse_errors: errors,
    protocol_violations: violations,
    prose: trim(prose),
    canonical_text: canonical(parts),
  }
}