harn-stdlib 0.10.124

Embedded Harn standard library source catalog
Documentation
/**
 * Registry value produced by `tool_registry()` / `tool_define(...)`.
 *
 * Packages that accept or forward registries through typed public APIs
 * should import this alias from `std/tools` instead of `dict` or a local
 * structural copy.
 */
pub type ToolRegistryInfo = {name: string, version?: string, description?: string}

pub type ToolRegistry = {_type: "tool_registry", tools: list, info?: ToolRegistryInfo}

/** Deterministic command path projected by `harn tool run`. */
pub type ToolCliSpec = {command: list<string>, hidden?: bool}

/** Origin coordinates retained across generated adapters. */
pub type ToolSource = {kind: string, id?: string, binding?: dict}

/** Harn-owned execution classification used by approval and scheduling policy. */
pub type ToolPolicy = {
  kind: "read" | "edit" | "delete" | "move" | "search" | "execute" | "think" | "fetch" | "other",
  side_effect_level: "none" \
    | "read_only" \
    | "workspace_write" \
    | "process_exec" \
    | "network" \
    | "desktop_control",
}

/** MCP task execution contract retained by the shared tool catalog. */
pub type ToolExecution = {taskSupport: "forbidden" | "optional" | "required"}

/** Portable icon metadata used by MCP and future presentation adapters. */
pub type ToolIcon = {src: string, mimeType?: string, sizes?: list<string>, theme?: "light" | "dark"}

/** Static, handler-free projection of one executable registry entry. */
pub type ToolCatalogEntry = {
  name: string,
  title?: string,
  description?: string,
  inputSchema: dict,
  outputSchema?: dict,
  annotations?: dict,
  icons?: list<ToolIcon>,
  execution?: ToolExecution,
  cli: ToolCliSpec,
  namespace?: string,
  deferLoading: bool,
  source?: ToolSource,
  policy?: ToolPolicy,
  _meta?: dict,
}

/** Versioned static projection shared by generated presentation adapters. */
pub type ToolCatalog = {
  schema_version: "harn-tools/1.0",
  info?: ToolRegistryInfo,
  tools: list<ToolCatalogEntry>,
  components?: dict,
}

type ToolDefinitionConfig = {
  parameters?: dict,
  returns?: dict,
  output_schema?: dict,
  handler?: fn(dict) -> any,
  executor?: string,
  host_capability?: string,
  mcp_server?: string,
  defer_loading?: bool,
  namespace?: string,
  title?: string,
  annotations?: dict,
  cli?: ToolCliSpec,
  source?: ToolSource,
  policy?: dict,
  execution_policy?: ToolPolicy,
  execution?: ToolExecution,
  icons?: list<ToolIcon>,
  meta?: dict,
  guidance?: string,
}

/**
 * Declarative tool spec accepted by `tool_define_many` / `tool_registry_from`.
 *
 * System-prompt `guidance` is co-located with the tool. When this tool is
 * present in the active tool set, the runtime auto-injects this text as a
 * capability-gated system-prompt fragment (gated on the tool's own
 * presence), so instruction and tool can never drift. Omit it and the
 * instruction never appears. See docs/src/prompt-assembly.md.
 */
pub type ToolDefinitionSpec = {
  name: string,
  description?: string,
  desc?: string,
  config?: ToolDefinitionConfig,
  parameters?: dict,
  returns?: dict,
  output_schema?: dict,
  handler?: fn(dict) -> any,
  executor?: string,
  host_capability?: string,
  mcp_server?: string,
  defer_loading?: bool,
  namespace?: string,
  title?: string,
  annotations?: dict,
  cli?: ToolCliSpec,
  source?: ToolSource,
  policy?: dict,
  execution_policy?: ToolPolicy,
  execution?: ToolExecution,
  icons?: list<ToolIcon>,
  meta?: dict,
  guidance?: string,
}

type PathScopeMatcherOptions = {
  scope?: string,
  arg_keys?: list<string>,
  mount_modes?: list<string>,
  on_violation?: string,
  patterns?: list<string>,
}?

/**
 * path_scope returns a dynamic-permissions matcher that checks path args
 * against the active session workspace anchor.
 *
 * @effects: []
 * @errors: []
 * @api_stability: experimental
 * @example: path_scope({mount_modes: ["extend"]})
 */
pub fn path_scope(options: PathScopeMatcherOptions = nil) -> dict {
  const opts = options ?? {}
  let matcher = {
    type: "path_scope",
    scope: opts?.scope ?? "anchor_plus_mounted",
    arg_keys: opts?.arg_keys ?? ["path", "destination", "source", "file"],
    on_violation: opts?.on_violation ?? "deny",
  }
  if opts?.mount_modes != nil {
    matcher = matcher + {mount_modes: opts.mount_modes}
  }
  if opts?.patterns != nil {
    matcher = matcher + {patterns: opts.patterns}
  }
  return matcher
}

fn __tool_spec_config(spec: ToolDefinitionSpec) -> ToolDefinitionConfig {
  if type_of(spec) != "dict" {
    throw "tool_define_many: each spec must be a dict"
  }
  let config = spec.config ?? {}
  for key in spec.keys() {
    if !contains(["name", "description", "desc", "config"], key) {
      config = config + {[key]: spec[key]}
    }
  }
  return config
}

/**
 * tool_define_many adds a list of tool specs to a registry.
 *
 * @effects: []
 * @errors: []
 */
pub fn tool_define_many(registry: ToolRegistry, specs: list<ToolDefinitionSpec>) -> ToolRegistry {
  if type_of(specs) != "list" {
    throw "tool_define_many: specs must be a list"
  }
  let tools = registry ?? tool_registry()
  for spec in specs {
    const name = spec.name
    const description = spec.description ?? spec.desc
    if name == nil || name == "" {
      throw "tool_define_many: each spec needs a non-empty name"
    }
    if description == nil || description == "" {
      throw "tool_define_many: each spec needs a non-empty description"
    }
    tools = tool_define(tools, name, description, __tool_spec_config(spec))
  }
  return tools
}

/**
 * tool_registry_from creates a registry from a list of tool specs.
 *
 * @effects: []
 * @errors: []
 */
pub fn tool_registry_from(
  specs: list<ToolDefinitionSpec>,
  info: ToolRegistryInfo? = nil,
) -> ToolRegistry {
  return tool_define_many(tool_registry(info), specs)
}