// @harn-entrypoint-category llm.stdlib
//
// std/llm/catalog — thin Harn wrappers over the runtime model catalog.
//
// The wrapper functions intentionally use shorter, idiomatic names
// (`model_info`, `resolved_options`) instead of the underlying builtin
// names (`llm_model_info`, `llm_resolved_options`) so that wrapping
// does not shadow the builtin and recurse infinitely.
/**
* Wraps the llm_model_info(selector) Rust builtin. The runtime always
* returns a dict; when the selector is unknown, the dict's `catalog`
* field will be nil and the inferred provider will be the default.
*/
pub fn model_info(selector) {
return llm_model_info(selector)
}
/** Wraps llm_resolved_options(opts). opts.model is required; throws otherwise. */
pub fn resolved_options(opts) {
if type_of(opts) != "dict" {
throw "resolved_options: opts must be a dict"
}
if opts?.model == nil || opts.model == "" {
throw "resolved_options: opts.model is required"
}
return llm_resolved_options(opts)
}
fn __capability_field(caps, capability) {
if capability == "thinking" {
let direct = caps?.thinking ?? false
let modes = caps?.thinking_modes ?? []
return direct || len(modes) > 0
}
if capability == "tool_search" {
return len(caps?.tool_search ?? []) > 0
}
if capability == "vision" {
return caps?.vision_supported ?? false
}
if capability == "files_api" {
return caps?.files_api_supported ?? false
}
if capability == "reasoning_effort" {
return caps?.reasoning_effort_supported ?? false
}
if capability == "interleaved_thinking" {
return caps?.interleaved_thinking_supported ?? false
}
if capability == "prompt_caching" {
return caps?.prompt_caching ?? false
}
if capability == "native_tools" {
return caps?.native_tools ?? false
}
if capability == "audio" {
return caps?.audio ?? false
}
if capability == "pdf" {
return caps?.pdf ?? false
}
return false
}
/**
* True if (model, capability) is supported per the runtime catalog.
* capability is one of: "thinking", "tool_search", "interleaved_thinking",
* "prompt_caching", "vision", "audio", "pdf", "files_api",
* "reasoning_effort", "native_tools". Returns false on unknown model.
*/
pub fn has_capability(model, capability) {
let info = llm_model_info(model)
if type_of(info) != "dict" {
return false
}
let caps = info?.capabilities
if type_of(caps) != "dict" {
return false
}
return __capability_field(caps, capability)
}
fn __family_for_anthropic(id) {
if contains(id, "haiku") {
return "anthropic_haiku"
}
if contains(id, "opus-4-7") || contains(id, "opus-mythos") {
return "anthropic_opus_adaptive"
}
return "anthropic_sonnet_opus"
}
fn __family_for_openai(id) {
if starts_with(id, "gpt-5") {
return "openai_gpt5_family"
}
return "openai_legacy"
}
fn __family_for_gemini(id) {
if contains(id, "flash") {
return "gemini_flash"
}
return "gemini_pro"
}
fn __family_for_ollama(id) {
if contains(id, "qwen") {
return "ollama_qwen3"
}
return "ollama_generic"
}
/**
* Best-effort family classifier. Returns one of:
* "anthropic_haiku", "anthropic_opus_adaptive", "anthropic_sonnet_opus",
* "openai_gpt5_family", "openai_legacy", "gemini_flash", "gemini_pro",
* "ollama_qwen3", "ollama_generic", or "generic".
*/
pub fn family_of(model_id) {
let info = llm_model_info(model_id)
if type_of(info) != "dict" {
return "generic"
}
let provider = lowercase(to_string(info?.provider ?? ""))
let id = lowercase(to_string(model_id))
if provider == "anthropic" {
return __family_for_anthropic(id)
}
if provider == "openai" {
return __family_for_openai(id)
}
if provider == "gemini" {
return __family_for_gemini(id)
}
if provider == "ollama" {
return __family_for_ollama(id)
}
return "generic"
}