harn-stdlib 0.10.103

Embedded Harn standard library source catalog
Documentation
/**
 * Chat-template tool-call envelopes.
 *
 * Some templates wrap a call in their own envelope — marker pairs, an XML
 * shell, a bare JSON list — instead of the fence the route taught. This module
 * owns recognizing those shells and unwrapping them; what the unwrapped body
 * MEANS is std/llm/tool_parse's business.
 *
 * Split out of std/llm/tool_parse so that module stays a composition layer over
 * the byte-oriented host scanners rather than also carrying every envelope
 * dialect.
 */
import { ARGUMENT_ALIASES, NAME_ALIASES } from "std/llm/dialects"
import { json_fields } from "std/llm/tool_parse_json_support"
import { empty_parse, protocol_violation, provider_result } from "std/llm/tool_parse_result"

fn __tool_parse_envelope_error(kind: string, detail: string, prose: string) -> dict {
  return {
    matched: true,
    result: empty_parse()
      + {
      tool_parse_errors: [
        "The `<"
          + kind
          + ">` chat-template envelope is malformed: "
          + detail
          + ". Re-emit complete calls as canonical ```tool JSON blocks; "
          + "incomplete entries were not executed.",
      ],
      prose: trim(prose),
    },
  }
}

fn __tool_parse_envelope_call(value) -> dict {
  if type_of(value) != "dict" {
    return {ok: false, error: "each chat-template tool entry must be a JSON object"}
  }
  const fields = json_fields(value)
  const name = fields.name
  if name == "" {
    return {ok: false, error: "a JSON tool object was missing a non-empty name"}
  }
  let arguments = fields.arguments
  let has_arguments = false
  for alias in ARGUMENT_ALIASES {
    if value[alias] != nil {
      has_arguments = true
    }
  }
  if !has_arguments {
    arguments = {}
    for key in keys(value) {
      if !NAME_ALIASES.contains(key) && key != "id" {
        arguments[key] = value[key]
      }
    }
  }
  if type_of(arguments) == "string" {
    arguments = try {
      json_parse(arguments)
    } catch (error) {
      return {ok: false, error: "arguments string did not parse: " + to_string(error)}
    }
  }
  if type_of(arguments) != "dict" {
    return {ok: false, error: "arguments must be a JSON object"}
  }
  return {ok: true, call: {id: "tc_envelope", name: name, arguments: arguments}}
}

fn __tool_parse_envelope_json_list(
  kind: string,
  body: string,
  close: string,
  prose: string,
) -> dict {
  const source = trim(body)
  const stream = __host_tool_json_stream(source)
  if len(stream?.values ?? []) == 0 {
    const detail =
      stream?.eof ?? false ? "a JSON tool object ended before its closing `}`" : "expected a JSON tool object, found `"
      + trim(
      body,
    )
      .slice(0, 80)
      + "`"
    return __tool_parse_envelope_error("tool_calls", detail, prose)
  }
  let calls: list<dict> = []
  for row in stream.values {
    const normalized = __tool_parse_envelope_call(row.value)
    if !(normalized?.ok ?? false) {
      return __tool_parse_envelope_error("tool_calls", to_string(normalized.error), prose)
    }
    calls = calls.appending(normalized.call)
  }
  let after = trim(source.slice(stream.end, len(source)))
  if starts_with(after, close) {
    after = trim(after.slice(len(close), len(after)))
  } else if after != "" {
    return __tool_parse_envelope_error(
      "tool_calls",
      "expected a JSON tool object, found `" + after.slice(0, 80) + "`",
      prose,
    )
  }
  return {
    matched: true,
    calls: calls,
    prose: [trim(prose), after].filter(fn(part) { return part != "" }).join("\n"),
  }
}

fn __tool_parse_envelope_markers(body: string, prose: string) -> dict {
  let rest = trim(body)
  let calls: list<dict> = []
  let saw_marker = false
  let marker_active = false
  let marker_completed = false
  while rest != "" {
    if starts_with(rest, "</tool_calls>") {
      rest = trim(rest.slice(len("</tool_calls>"), len(rest)))
      break
    }
    if starts_with(rest, "<tool>") {
      if marker_active {
        return __tool_parse_envelope_error(
          "tool_calls",
          "a `<tool>` marker must be followed by a JSON object before another `<tool>` marker",
          prose,
        )
      }
      saw_marker = true
      marker_active = true
      marker_completed = false
      rest = trim(rest.slice(len("<tool>"), len(rest)))
      continue
    }
    if starts_with(rest, "</tool>") {
      if marker_active || !marker_completed {
        return __tool_parse_envelope_error(
          "tool_calls",
          "found an unmatched `</tool>` close without a preceding `<tool>` marker",
          prose,
        )
      }
      marker_active = false
      marker_completed = false
      rest = trim(rest.slice(len("</tool>"), len(rest)))
      continue
    }
    if !marker_active {
      if marker_completed && starts_with(rest, "{") {
        marker_active = true
      } else {
        const detail =
          saw_marker ? "expected a `<tool>` marker before this JSON object" : "the envelope contained no `<tool>` marker"
        return __tool_parse_envelope_error("tool_calls", detail, prose)
      }
    }
    if !starts_with(rest, "{") {
      return __tool_parse_envelope_error(
        "tool_calls",
        "expected a JSON tool object, found `" + rest.slice(0, 80) + "`",
        prose,
      )
    }
    const object_len = __host_tool_balanced_json_len(rest)
    if object_len == 0 {
      return __tool_parse_envelope_error(
        "tool_calls",
        "a JSON tool object ended before its closing `}`",
        prose,
      )
    }
    const decoded = try {
      json_parse(rest.slice(0, object_len))
    } catch (error) {
      return __tool_parse_envelope_error(
        "tool_calls",
        "a JSON tool object did not parse: " + to_string(error),
        prose,
      )
    }
    const normalized = __tool_parse_envelope_call(decoded)
    if !(normalized?.ok ?? false) {
      return __tool_parse_envelope_error("tool_calls", to_string(normalized.error), prose)
    }
    calls = calls.appending(normalized.call)
    marker_active = false
    marker_completed = true
    rest = trim(rest.slice(object_len, len(rest)))
  }
  if marker_active {
    return __tool_parse_envelope_error(
      "tool_calls",
      "a `<tool>` marker ended without a complete JSON object",
      prose,
    )
  }
  if !saw_marker {
    return __tool_parse_envelope_error(
      "tool_calls",
      "the envelope contained no `<tool>` marker",
      prose,
    )
  }
  return {
    matched: true,
    calls: calls,
    prose: [trim(prose), rest].filter(fn(p) { return p != "" }).join("\n"),
  }
}

fn __tool_parse_envelope_xml(body: string, prose: string) -> dict {
  let rest = trim(body)
  let calls: list<dict> = []
  while rest != "" {
    if starts_with(rest, "</tool_calls>") {
      rest = trim(rest.slice(len("</tool_calls>"), len(rest)))
      break
    }
    const opened = regex_captures("^<([A-Za-z_][A-Za-z0-9_.-]*)>", rest)
    if len(opened) == 0 {
      return __tool_parse_envelope_error(
        "tool_calls",
        "expected a tool-call tag inside `<tool_calls>`, found `" + rest.slice(0, 60) + "`",
        prose,
      )
    }
    const name = to_string(opened[0].groups[0])
    const close = "</" + name + ">"
    const after_open = rest.slice(opened[0].end, len(rest))
    const close_at = after_open.index_of(close)
    const inner = close_at >= 0 ? after_open.slice(0, close_at) : after_open
    let arguments: dict = {}
    let argument_rest = trim(inner)
    while argument_rest != "" {
      const argument_open = regex_captures("^<([A-Za-z_][A-Za-z0-9_.-]*)>", argument_rest)
      if len(argument_open) == 0 {
        return __tool_parse_envelope_error(
          "tool_calls",
          "expected an argument tag inside `<"
            + name
            + ">`, found `"
            + argument_rest.slice(0, 60)
            + "`",
          prose,
        )
      }
      const key = to_string(argument_open[0].groups[0])
      if arguments[key] != nil {
        return __tool_parse_envelope_error(
          "tool_calls",
          "the `<"
            + name
            + ">` call repeated the `<"
            + key
            + ">` argument; a call with an ambiguous duplicate argument is not executed",
          prose,
        )
      }
      const argument_close = "</" + key + ">"
      const value_source = argument_rest.slice(argument_open[0].end, len(argument_rest))
      const argument_close_at = value_source.index_of(argument_close)
      if argument_close_at < 0 {
        return __tool_parse_envelope_error(
          "tool_calls",
          "the `<"
            + key
            + ">` argument tag was not closed with `"
            + argument_close
            + "` before end of output",
          prose,
        )
      }
      arguments[key] = trim(value_source.slice(0, argument_close_at))
      argument_rest = trim(
        value_source.slice(argument_close_at + len(argument_close), len(value_source)),
      )
    }
    if close_at < 0 {
      return __tool_parse_envelope_error(
        "tool_calls",
        "the `<" + name + ">` call tag was not closed with `" + close + "` before end of output",
        prose,
      )
    }
    calls = calls.appending({id: "tc_envelope_xml", name: name, arguments: arguments})
    rest = trim(after_open.slice(close_at + len(close), len(after_open)))
  }
  if len(calls) == 0 {
    return __tool_parse_envelope_error(
      "tool_calls",
      "the `<tool_calls>` envelope contained no tool-call tags",
      prose,
    )
  }
  return {
    matched: true,
    calls: calls,
    prose: [trim(prose), rest].filter(fn(p) { return p != "" }).join("\n"),
  }
}

pub fn tool_parse_envelope(text: string) -> dict {
  let opener = ""
  let kind = ""
  let at = -1
  for candidate in [
    {open: "<tool_calls>", kind: "tool_calls"},
    {open: "<tool_code>", kind: "tool_code"},
    {open: "<tool_call>", kind: "tool_call"},
  ] {
    const found = text.index_of(candidate.open)
    if found >= 0 && (at < 0 || found < at) {
      at = found
      opener = candidate.open
      kind = candidate.kind
    }
  }
  if at < 0 {
    return {matched: false}
  }
  const prose = text.slice(0, at)
  const body = text.slice(at + len(opener), len(text))
  if kind == "tool_call" && !starts_with(trim(body), "{") {
    return {matched: false}
  }
  let parsed = if kind == "tool_calls" {
    const leading = trim(body)
    if starts_with(leading, "<tool>") || starts_with(leading, "{")
      || starts_with(leading, "</tool>") {
      __tool_parse_envelope_markers(body, prose)
    } else {
      __tool_parse_envelope_xml(body, prose)
    }
  } else {
    __tool_parse_envelope_json_list(kind, body, "</" + kind + ">", prose)
  }
  if parsed?.result != nil {
    return parsed
  }
  const violation = protocol_violation(
    "wrong_tool_format",
    "protocol_violation: a tool call was emitted in a chat-template tool envelope "
      + "while `tool_format` is `json`; accepted this turn, but emit canonical "
      + "```tool JSON blocks next turn.",
  )
  parsed = parsed
    + {result: provider_result(parsed.calls, [], parsed.prose, [violation])}
  return parsed
}