harn-stdlib 0.8.18

Embedded Harn standard library source catalog
Documentation
import { agent_emit_event } from "std/agent/state"

fn __agent_progress_status(value, label) {
  if type_of(value) != "string" {
    throw label + ".status must be one of pending, in_progress, completed"
  }
  if contains(["pending", "in_progress", "completed"], value) {
    return value
  }
  throw label + ".status must be one of pending, in_progress, completed"
}

fn __agent_progress_priority(value, label) {
  if value == nil {
    return nil
  }
  if type_of(value) != "string" {
    throw label + ".priority must be one of high, medium, low, or nil"
  }
  if contains(["high", "medium", "low"], value) {
    return value
  }
  throw label + ".priority must be one of high, medium, low, or nil"
}

fn __agent_progress_entries(value) {
  if value == nil {
    return []
  }
  if type_of(value) != "list" {
    throw "agent_progress: entries must be a list"
  }
  var entries = []
  var index = 0
  for raw in value {
    let label = "agent_progress: entries[" + to_string(index) + "]"
    if type_of(raw) != "dict" {
      throw label + " must be a dict"
    }
    let content = raw?.content
    if type_of(content) != "string" || trim(content) == "" {
      throw label + ".content must be a non-empty string"
    }
    var entry = {content: content, status: __agent_progress_status(raw?.status, label)}
    let priority = __agent_progress_priority(raw?.priority, label)
    if priority != nil {
      entry = entry + {priority: priority}
    }
    entries = entries.push(entry)
    index = index + 1
  }
  return entries
}

fn __agent_progress_payload(input) {
  if type_of(input) != "dict" {
    throw "agent_progress: argument must be a dict"
  }
  let message = input?.message
  if message != nil && type_of(message) != "string" {
    throw "agent_progress: message must be a string or nil"
  }
  let entries = __agent_progress_entries(input?.entries)
  if (message == nil || trim(message) == "") && len(entries) == 0 {
    throw "agent_progress: message or entries is required"
  }
  let replace = input?.replace ?? true
  if type_of(replace) != "bool" {
    throw "agent_progress: replace must be a bool"
  }
  let metadata = input?.metadata ?? {}
  if type_of(metadata) != "dict" {
    throw "agent_progress: metadata must be a dict"
  }
  return {
    message: if message == nil {
      nil
    } else {
      message
    },
    entries: entries,
    replace: replace,
    metadata: metadata,
  }
}

fn __agent_progress_tool_name(config) {
  let name = config?.name ?? "agent_progress"
  if type_of(name) != "string" || trim(name) == "" {
    throw "agent_loop: progress_tool.name must be a non-empty string"
  }
  return name
}

fn __agent_progress_tool_description(config) {
  let description = config?.description
    ?? "Report concise task progress to the host without ending the turn."
  if type_of(description) != "string" || trim(description) == "" {
    throw "agent_loop: progress_tool.description must be a non-empty string"
  }
  return description
}

fn __agent_progress_tool_nudge(config) {
  let tool_name = __agent_progress_tool_name(config)
  let nudge = config?.system_prompt_nudge
    ?? "When useful, call "
    + tool_name
    + " to report concise progress. Use message for narration, entries for task-list state, and replace=true unless the update should append."
  if nudge == nil {
    return nil
  }
  if type_of(nudge) != "string" {
    throw "agent_loop: progress_tool.system_prompt_nudge must be a string or nil"
  }
  let trimmed = trim(nudge)
  if trimmed == "" {
    return nil
  }
  return trimmed
}

fn __agent_progress_tool_config(value) {
  if value == nil {
    return nil
  }
  let kind = type_of(value)
  if kind == "bool" {
    if value {
      return {}
    }
    return nil
  }
  if kind != "dict" {
    throw "agent_loop: progress_tool must be true, false, a dict, or nil"
  }
  return value
}

/** agent_progress emits a structured progress_reported event for the current agent session. */
pub fn agent_progress(input) {
  let payload = __agent_progress_payload(input)
  let session_id = agent_session_current_id()
  if session_id == nil || session_id == "" {
    throw "agent_progress: no active agent session"
  }
  agent_emit_event(session_id, "progress_reported", payload)
  return nil
}

/** agent_progress_tool adds a handler-backed agent_progress tool to a registry. */
pub fn agent_progress_tool(registry = nil, options = nil) {
  let config = options ?? {}
  return tool_define(
    registry ?? tool_registry(),
    __agent_progress_tool_name(config),
    __agent_progress_tool_description(config),
    {
      parameters: {
        message: {type: "string", description: "Short progress narration", required: false},
        entries: {
          type: "array",
          description: "Task-list entries with content, status, and optional priority",
          required: false,
        },
        replace: {
          type: "boolean",
          description: "Whether this update replaces the prior progress view",
          required: false,
        },
        metadata: {type: "object", description: "Free-form progress metadata", required: false},
      },
      returns: {type: "null"},
      handler: { args -> agent_progress(args) },
    },
  )
}

/** agent_progress_apply_options injects the progress tool when agent_loop opts in. */
pub fn agent_progress_apply_options(options = nil) {
  let opts = options ?? {}
  let config = __agent_progress_tool_config(opts?.progress_tool)
  if config == nil {
    return opts
  }
  let tools = agent_progress_tool(opts?.tools, config)
  let nudge = __agent_progress_tool_nudge(config)
  var out = opts + {tools: tools}
  if nudge != nil {
    out = out + {_progress_tool_system_prompt_nudge: nudge}
  }
  return out
}