ci / check (push) Waiting to run
The first public release of LLeMbas CLI: a terminal coding agent and project manager for any LLM API, with permission modes, git snapshots, memory and skills, knowledge bases, MCP, voice, and a link to a LLeMbas instance whose web UI can work its sessions too. Signed Linux binaries for x64 and arm64. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
3.5 KiB
TypeScript
71 lines
3.5 KiB
TypeScript
import { findRef, modelRefs } from "./refs.ts"
|
|
import type { Loaded } from "../config/load.ts"
|
|
import { AnthropicClient } from "./anthropic.ts"
|
|
import { OpenAIChatClient } from "./openai-chat.ts"
|
|
import { ResponsesClient } from "./responses.ts"
|
|
import { GeminiClient } from "./gemini.ts"
|
|
import { OllamaClient } from "./ollama.ts"
|
|
import { ProviderError, type Client, type ResolvedModel } from "./types.ts"
|
|
|
|
export class ModelError extends Error {}
|
|
|
|
/** `connection/model`. The model part may itself contain slashes (`openrouter/qwen/qwen3-coder`). */
|
|
export function resolveModel(loaded: Loaded, ref: string | undefined): ResolvedModel {
|
|
const refs = loaded.refs ?? modelRefs(loaded.connections)
|
|
if (!ref) {
|
|
// A LLeMbas instance says which model its account starts on; that, before anything else.
|
|
const instance = Object.entries(loaded.connections).find(([, c]) => c.webui?.default && c.models[c.webui.default])
|
|
if (instance) ref = refs.find((r) => r.connection === instance[0] && r.id === instance[1].webui!.default)?.ref ?? `${instance[0]}/${instance[1].webui!.default}`
|
|
}
|
|
if (!ref) {
|
|
const first = refs[0]
|
|
if (!first) throw new ModelError("No model configured. Add a connection to ~/.config/lembas/connections.yaml and set `model:` in config.yaml.")
|
|
ref = first.ref
|
|
}
|
|
// Any form of the name (`deepseek/x`, or the old `example/x`): the model it names, under
|
|
// the ref it has now.
|
|
const known = findRef(loaded.connections, refs, ref)
|
|
if (known) {
|
|
const connection = loaded.connections[known.connection]!
|
|
if (loaded.broken?.[known.connection]) throw new ModelError(`Connection "${known.connection}" is unusable: ${loaded.broken?.[known.connection]}`)
|
|
return { ref: known.ref, connectionName: known.connection, connection, id: known.id, spec: connection.models[known.id] ?? {} }
|
|
}
|
|
const slash = ref.indexOf("/")
|
|
if (slash <= 0) throw new ModelError(`Model "${ref}" must be written connection/model`)
|
|
const connectionName = ref.slice(0, slash)
|
|
const id = ref.slice(slash + 1)
|
|
if (loaded.broken?.[connectionName]) throw new ModelError(`Connection "${connectionName}" is unusable: ${loaded.broken[connectionName]}`)
|
|
const connection = loaded.connections[connectionName]
|
|
if (!connection) throw new ModelError(`No connection named "${connectionName}" (have: ${Object.keys(loaded.connections).join(", ") || "none"})`)
|
|
const spec = connection.models[id]
|
|
if (!spec && !connection.discover)
|
|
throw new ModelError(`Connection "${connectionName}" has no model "${id}" (have: ${Object.keys(connection.models).join(", ") || "none"}). Add it, or set discover: true.`)
|
|
return { ref, connectionName, connection, id, spec: spec ?? {} }
|
|
}
|
|
|
|
export function clientFor(m: ResolvedModel): Client {
|
|
switch (m.connection.dialect) {
|
|
case "openai-chat":
|
|
return new OpenAIChatClient(m)
|
|
case "anthropic":
|
|
return new AnthropicClient(m)
|
|
case "responses":
|
|
return new ResponsesClient(m)
|
|
case "gemini":
|
|
return new GeminiClient(m)
|
|
case "ollama":
|
|
return new OllamaClient(m)
|
|
}
|
|
}
|
|
|
|
/** Which prompt family a model gets when the config does not say: anthropic, gpt, gemini, or
|
|
* local — every open-weight or unknown model, which gets Hermes' act-don't-describe guidance. */
|
|
export function familyOf(m: ResolvedModel): string {
|
|
if (m.spec.family) return m.spec.family
|
|
const id = m.id.toLowerCase()
|
|
if (id.includes("claude")) return "anthropic"
|
|
if (id.includes("gemini")) return "gemini"
|
|
if (/\bgpt|^o[134]\b|^o[134]-|codex/.test(id)) return "gpt"
|
|
return "local"
|
|
}
|