harn-stdlib 0.7.62

Embedded Harn standard library source catalog
Documentation
// @harn-entrypoint-category agent.stdlib
import { agent_capture_events } from "std/agent/events"
import { agent_loop } from "std/agent/loop"
import { agent_loop_options } from "std/agent/options"
import { agent_turn_preamble_prompt } from "std/agent/prompts"

fn __agent_turn_system(user_system, opts = nil) {
  let preamble = agent_turn_preamble_prompt(opts)
  if user_system == nil || user_system == "" {
    return preamble
  }
  if type_of(user_system) != "string" {
    throw "agent_turn: system must be a string or nil"
  }
  return preamble + "\n\n" + user_system
}

fn __false_or_nil(value) {
  return value == nil || (type_of(value) == "bool" && !value)
}

fn __agent_turn_native_natural_completion(opts) {
  return opts?.tools != nil && opts?.tool_format == "native" && opts?.done_sentinel == nil
}

fn __agent_turn_options(options) {
  var opts = options ?? {}
  if type_of(opts) != "dict" {
    throw "agent_turn(prompt, opts?): opts must be a dict or nil"
  }
  if contains(opts.keys(), "judge") {
    opts = opts + {done_judge: opts.judge}
  }
  if contains(opts.keys(), "done_judge") && __false_or_nil(opts?.done_judge) {
    throw "agent_turn: done_judge is mandatory; pass a dict, true, or omit it"
  }
  if contains(opts.keys(), "persistent") {
    throw "agent_turn: `persistent` was removed; use `loop_until_done` for completion looping"
  }
  if contains(opts.keys(), "loop_until_done") && __false_or_nil(opts?.loop_until_done) {
    throw "agent_turn: completion looping is required; pass loop_until_done: true or omit it"
  }
  if !contains(opts.keys(), "done_judge") {
    opts = opts + {done_judge: true}
  }
  if !contains(opts.keys(), "loop_until_done") {
    opts = opts + {loop_until_done: true}
  }
  let loop_opts = agent_loop_options(opts)
  if loop_opts?.done_sentinel == nil && !__agent_turn_native_natural_completion(loop_opts) {
    throw "agent_turn: done_sentinel is required for non-native loops; pass a non-empty string or omit it"
  }
  if contains(loop_opts.keys(), "session_id") {
    return loop_opts
  }
  return loop_opts + {session_id: "agent_turn_session_" + uuid_v7()}
}

fn __agent_turn_judge_decisions(events) {
  var out = []
  for event in events {
    if event?.type == "judge_decision" {
      out = out
        .push(
        {
          iteration: event.iteration,
          verdict: event.verdict,
          reasoning: event.reasoning,
          next_step: event.next_step,
          judge_duration_ms: event.judge_duration_ms,
        },
      )
    }
  }
  return out
}

fn __agent_turn_iteration_summaries(events) {
  var turns = {}
  var order = []
  for event in events {
    if event?.type == "turn_start" {
      let key = to_string(event.iteration)
      if turns[key] == nil {
        turns[key] = {iteration: event.iteration}
        order = order.push(key)
      }
      var entry = turns[key]
      entry["started"] = true
      turns[key] = entry
    }
    if event?.type == "turn_end" {
      let key = to_string(event.iteration)
      if turns[key] == nil {
        turns[key] = {iteration: event.iteration}
        order = order.push(key)
      }
      var entry = turns[key]
      entry["ended"] = true
      let info = event?.turn_info ?? {}
      if info?.tool_count != nil {
        entry["tool_count"] = info.tool_count
      }
      if info?.text != nil {
        entry["prose_chars"] = len(info.text)
      }
      turns[key] = entry
    }
  }
  var out = []
  for key in order {
    out = out.push(turns[key])
  }
  return out
}

/** agent_turn. */
pub fn agent_turn(prompt, options = nil) {
  let opts = options ?? {}
  let loop_opts = __agent_turn_options(opts)
  let captured = agent_capture_events(
    loop_opts.session_id,
    fn() { return agent_loop(prompt, __agent_turn_system(opts?.system, loop_opts), loop_opts) },
  )
  let result = captured.result
  if type_of(result) != "dict" {
    return result
  }
  return result
    + {
    iterations: __agent_turn_iteration_summaries(captured.events),
    judge_decisions: __agent_turn_judge_decisions(captured.events),
  }
}