Obsidian plugin· Active · beta · not yet released
Model Hub
Named roles mapped to provider and model, keys kept in secret storage, and a cost ledger with the billed cost of every call. Other plugins call it for their AI.
What's changed in Model Hub
Unreleased
not yet released
Everything built so far. The first beta will be 0.1.0.
New
- Providers: OpenRouter (with provider routing, its own fallbacks, the exact billed cost and web search), Anthropic, Ollama, LM Studio and any OpenAI-compatible endpoint. Each has a tile, a dialog and a Test button.
- Keys in Obsidian's keychain, never in the settings file.
- Tiers: quick, balanced, careful, frontier, research, embedding and decision. Each is an ordered list of models across providers with a limit per call and a budget per month. Presets (Value, Claude, Local) fill all seven at once.
- Tiers of your own: New tier… on the Tiers tab or in a role's editor, and Rename… and Delete… on each tier you made.
- Fallback along the models you order, with every attempt recorded and shown.
- Roles: each plugin's jobs show on the Roles tab; you choose the tier, or the models, each one runs on.
- A cost ledger of every call with tokens, listed cost and billed cost; limits checked before a call is sent; calls billed well above the listed price flagged. A usage view shows spend by plugin and role over a period.
- Note chat panel in the right sidebar: ask about the open note or a selection, or research on the web with sources; pick a tier or a model with its price; insert, replace, copy, or save a reply as
.baseor.canvas; the cost of each reply. - Skills for Markdown, Bases, Canvas and more, plus your own from a vault folder. Edits in the skills folder are picked up without Reload skills.
- Typed decisions (yes/no, pick one, score) through TypeSafe's decision model, or any text model.
- Settings in five tabs: Providers, Tiers, Roles, Skills and Usage.
- OpenTelemetry export to a collector you run, off by default.
Changed
- Behaviour changeA limit that isn't a number of dollars is refused where you type it, instead of being saved as "no limit".
- Behaviour changeA role that asks for a tier that doesn't exist still runs on Balanced, and the Roles tab now says so.
- Behaviour changeInside a skill folder that has a
SKILL.md, other.mdfiles are data, not skills. Before, aprompts/*.mdwith a name and a description became a stray skill. - Behaviour changeA skill that extends a skill that isn't loaded is listed as a problem in the settings. Before, it ran without its parent, silently.
For plugin developers · 7 changes
- An API reference generated from the code:
docs/api/model-hub-api.d.ts(every public declaration in one file) anddocs/api/api.json(the same as data), published with the docs. - API 0.4 (serves plugins built for 0.1–0.3):
- Custom tiers.
createTier(pluginId, name, policy)opens a Hub dialog naming the plugin and showing the tier's models and limits; it resolves to the tier made, ornullif the user declines.deleteTier(pluginId, id)lists the roles mapped to the tier and asks first; it resolvestrueonce deleted. Both reject with a HubError for bad input, before the user sees anything.Tiercarries{ id, name, custom };tiers()lists the built-in tiers, then the user's own. - Plugin roles name a tier by id or by name in
registerRoles;setRoleaccepts a tier by name too (stored by id). Store theidcreateTierreturns; names keep working across renames: a renamed tier keeps its old names as aliases (saved with it, taken like names: no other tier may use one; cleared when the tier is deleted), and a custom tier's id also reads as the name it was made with. - Types:
TierNameis nowstring(a built-in or custom tier's id or name). New exports:BUILTIN_TIERS,isBuiltinTier,BuiltinTierName(TIER_NAMESstays, as the seven built-in tiers). - Ledger:
LedgerRow.tieris the tier's id (a built-in tier's id is its name), and the newtierNameis its display name when the call was made. - Behaviour change: tier names match ignoring case, spacing,
-and_, so a 0.3 role saying'Quick'now runs onquick(before, it fell back tobalanced). - Settings: New tier…, and Rename… and Delete… on each tier of your own. A limit in a tier card or the role editor that isn't a number of dollars is refused inline, not saved as "no limit".
- Behaviour change: a role whose plugin default names a tier that doesn't exist still runs on
balanced, butroles()now flags it (tierMissing: true, with the name as given inmissingTier). In 0.3 such a tier becamebalancedsilently. - The New tier dialog refuses a limit that isn't a number of dollars, instead of reading it as "no limit".
- Skills for consumers. Each
skills()entry hasfiles(): the skill's own text files by path relative to its folder (reader.yml,prompts/extract.md), recursive, withoutSKILL.md; a vault skill's are read at call time, so edits show without Reload skills. A file over 1 MB, or past 5 MB for the skill in all, is left out and listed as a skill problem; aSKILL.mddirectly in the skills folder (not in a subfolder) is ignored and listed as one. Entries also carryplugin(who registered it) andextendsMissing. registerSkills(pluginId, skills): a plugin registers its built-in skills as folders of files (SKILL.mdamong them). They survive Reload skills, a vault skill of the same name overrides one, the first plugin to register a name owns it (skill-takenfor others, and for the Hub's own skill names), and they are removed when the plugin unloads. No plugin may register asmodel-hub; a registered skill may not carry a nestedSKILL.mdor files over the size caps.- Registered skills are chosen-only: a registered
SKILL.mdwithmode: alwaysis refused (invalid-skill): a plugin's skill can't be always-on; the user can make a vault copy always-on. model-hub:ready: the Hub fires this workspace event with its API each time it loads. A Hub disabled and enabled again starts without registered skills, so consumers register again then:onHubReady(app, plugin, wanted, cb)(besidegetHub) callscbnow and on every reload.skillsReady()resolves once the vault's skills have loaded (layout-ready), also when loading them fails (the failure is listed as a skill problem);onSkillsChanged(cb)fires on register/unregister and on any edit in the skills folder (which now reloads itself), and returns an unsubscribe function.- Behaviour change: inside a folder that has a
SKILL.md, other.mdfiles are data, not skills (before, aprompts/*.mdwithnameanddescriptionfrontmatter became a stray skill). - Behaviour change: a skill whose
extendsnames a skill that isn't loaded is flagged (extendsMissing: true) and listed as a load problem in the settings; before, it composed without its parent, silently. createTier/deleteTierdialogs mark a plugin id that no installed plugin has as "(unknown plugin)"; a proposed candidate with routing that isn't plain data is refused asinvalid-request, and only a candidate's own fields are kept.
- Custom tiers.
- API 0.3: per-call
routing(merged into each candidate's OpenRouter routing, recorded in the ledger) and per-callcandidates(roles that allow any model);batch()runs many calls with progress and cost, stopping on an overcharged call, a spend limit or an abort. - Plugin-owned roles: plugins open the Hub's role editor (
openRoleEditor) or useroles()/setRole(); API 0.2 (serves 0.1 plugins). decide(): typed decisions (yes/no, pick one, score) through TypeSafe's Jev with calibrated probabilities, or any text model through structured output.- The plugin loads and exposes its API:
registerRoles,complete,stream,embed,models,usage, withgetHub()for consumers. - Repository set up from Obsidian's sample plugin: MIT licence, CI (lint, types, unit tests with coverage, build, size budget, end-to-end tests in a sandboxed Obsidian), release workflow, issue templates.
Each released version is also a GitHub release with the same notes. What is planned next: the roadmap.