harn-stdlib 0.10.110

Embedded Harn standard library source catalog
Documentation
// @harn-entrypoint-category llm.stdlib
//
// std/llm/call_shape — THE taught tool-call shape, one row per tool format.
//
// Every corrective a model reads has to name the call syntax it should have
// used, and every one of them has to name the SAME syntax the active parser
// accepts. Written out at each call site they drift: one live session told a
// model to "emit every call as `<tool_call>name({ ... })</tool_call>`" from the
// unrecognized-span guidance while every other corrective in the same session
// taught a ```tool fenced JSON object, because that one message was written
// under the tagged pin and never learned about the others.
//
// So the taught syntax is data, keyed by tool format, read by everything that
// teaches it: the loop's tool contract prompt, the no-progress nudge, the
// fenced-call and missing-call correctives, parse guidance, and the
// unrecognized-span violation. Adding a format is an edit to this table.
// Rewording what a format teaches is an edit to this table. Nothing downstream
// spells a delimiter out for itself.
import { call_block, prose_block } from "std/llm/tool_parse_result"

/**
 * How one tool format is taught and rendered.
 *
 * `call_open`/`call_close` and `body_open`/`body_close` are the physical
 * delimiters an exemplar renders between. The rest is prose the correctives
 * quote:
 *
 * - `call_noun` names one call as a noun phrase, for a sentence that already
 *   supplies its own verb ("Emit the call as a ```tool JSON block instead.").
 * - `call_syntax` is the full wire form, for a sentence that has to show the
 *   shape rather than refer to it.
 * - `final_answer` is how a finished turn yields its answer.
 * - `body_hint` is the single answer to "how do I pass a multi-line or
 *   code-bearing field", which is where weak models lose calls to escaping.
 */
pub type ToolCallShape = {
  kind: string,
  call_open: string,
  call_close: string,
  body_open: string,
  body_close: string,
  call_noun: string,
  call_syntax: string,
  final_answer: string,
  body_hint: string,
}

const __NATIVE_SHAPE: ToolCallShape = {
  kind: "native",
  call_open: "",
  call_close: "",
  body_open: "\"",
  body_close: "\"",
  call_noun: "native tool call",
  call_syntax: "a native tool call on the provider's tool channel",
  final_answer: "in concise assistant text",
  body_hint:
    "Pass multi-line or code-bearing fields (file contents, replacement text) as ordinary JSON string arguments — the runtime handles escaping.",
}

const __JSON_SHAPE: ToolCallShape = {
  kind: "json",
  call_open: "```tool\n",
  call_close: "\n```",
  body_open: "\"",
  body_close: "\"",
  call_noun: "```tool JSON block",
  call_syntax: "a ```tool fenced block wrapping one JSON object with `name` and `args` keys",
  final_answer: "as plain text",
  body_hint:
    "Each tool call is one JSON object `{ \"name\": \"...\", \"args\": { ... } }` in a ```tool fenced block. For a multi-line or code-bearing field (file contents, replacement text, exact-match strings) use a verbatim body instead of escaping: set the value to `\"<<BODY\"`, then put the raw text after the JSON object INSIDE the same ```tool fence, ending with a line that is exactly `BODY`. A verbatim body needs NO escaping at all — quotes, backslashes, newlines, and backticks are literal bytes. Use `\"<<BODY:N\"`, where N is the exact number of body lines, only when the body itself contains a line that is exactly `BODY`. Short scalar values stay ordinary JSON strings (escape newlines as \\n, quotes as \\\", backslashes as \\\\). For several calls in one turn, emit consecutive objects — several ```tool blocks or several objects in one block — never a JSON array. Never wrap the call itself in a Markdown fence other than ```tool, and never use a triple-quoted string.",
}

const __TEXT_SHAPE: ToolCallShape = {
  kind: "text",
  call_open: "<tool_call>",
  call_close: "</tool_call>",
  body_open: "<<EOF\n",
  body_close: "\nEOF",
  call_noun: "`<tool_call>` block",
  call_syntax: "`<tool_call>name({ ... })</tool_call>`",
  final_answer: "in a `<user_response>` block",
  body_hint:
    "For any multi-line or code-bearing field (file contents, replacement text, exact-match strings) use a heredoc body: write `<<EOF` after the colon, the raw content on the following lines, then a line that is exactly `EOF` (the closing `}`/`)` may follow it). Heredoc bodies need NO escaping — quotes, backslashes, and backticks are all literal. Never wrap code or multi-line text in a quoted string.",
}

/**
 * The taught shape for one tool format.
 *
 * `text` is the fallback for an empty or unrecognized format because it is the
 * grammar the parser falls back to, so a corrective rendered from an unpinned
 * format still names what that turn would actually be read as.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn tool_call_shape(tool_format) -> ToolCallShape {
  const format = to_string(tool_format ?? "")
  if format == "native" {
    return __NATIVE_SHAPE
  }
  if format == "json" {
    return __JSON_SHAPE
  }
  return __TEXT_SHAPE
}

/**
 * The taught call syntax for ONE named tool.
 *
 * A corrective that has already identified which call the model meant should
 * name it: "emit `run` like this" is a smaller ask than "emit calls like this",
 * and the recovery notes that raise it always know the name. Falls back to the
 * unnamed `call_syntax` for an empty name, so a caller that has no name to
 * offer still renders the format's shape rather than a hole.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn tool_call_syntax_named(tool_format, name) -> string {
  const shape = tool_call_shape(tool_format)
  const called = to_string(name ?? "")
  if called == "" {
    return shape.call_syntax
  }
  if shape.kind == "native" {
    return "a native tool call to `" + called + "` on the provider's tool channel"
  }
  if shape.kind == "json" {
    return "a ```tool fenced block wrapping `{\"name\": \"" + called + "\", \"args\": { ... }}`"
  }
  return "`<tool_call>" + called + "({ ... })</tool_call>`"
}

/**
 * Render one assistant turn the way the transcript should REMEMBER it.
 *
 * A model learns its call syntax from its own transcript far more strongly
 * than from its instructions: measured against a local open-weight model, a
 * session whose persisted turns carried a drifted fence kept reproducing that
 * fence, and no prompt-side wording rescued it — rewriting those persisted
 * turns to canonical form did.
 *
 * So a turn whose calls were read out of TEXT is written back in the canonical
 * form of the format that read it, and the model's own delimiters stop being
 * few-shot teaching. `prose` is what the model actually said, already
 * separated from its calls by the parser — never the raw completion, which is
 * the thing being canonicalized away.
 *
 * This rewrites the SHAPE of a call, never its content. Argument values are
 * the model's own and are replayed unchanged; nothing here can tell a copied
 * placeholder from a value the caller meant. Argument drift propagates through
 * self-history independently of fence drift and is cut off at its source
 * instead, by correctives that render no argument values to copy.
 *
 * Returns "" when there is nothing to rewrite: no calls to render, a native
 * route whose calls live on the provider's tool channel instead of in the
 * text, or calls with no name to render. The caller keeps the turn's own text
 * in every one of those cases.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 */
pub fn tool_call_history_text(tool_format, prose, calls) -> string {
  const turn_calls = calls ?? []
  if len(turn_calls) == 0 {
    return ""
  }
  const shape = tool_call_shape(tool_format)
  if shape.kind == "native" {
    return ""
  }
  const said = trim(to_string(prose ?? ""))
  let parts: list<string> = []
  if said != "" {
    const opening = if shape.kind == "json" {
      said
    } else {
      prose_block(said)
    }
    parts = parts.appending(opening)
  }
  for call in turn_calls {
    const name = trim(to_string(call?.name ?? call?.tool_name ?? ""))
    if name == "" {
      continue
    }
    // A dispatched call spells its arguments either way, depending on which
    // side of the tool envelope it came from.
    const raw_args = call?.arguments ?? call?.tool_args
    const args = if type_of(raw_args) == "dict" {
      raw_args
    } else {
      {}
    }
    const block = if shape.kind == "json" {
      shape.call_open + json_stringify({name: name, args: args}) + shape.call_close
    } else {
      call_block({name: name, arguments: args})
    }
    parts = parts.appending(block)
  }
  if len(parts) == 0 {
    return ""
  }
  return parts.join("\n\n")
}