Insurgently

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 .base or .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 .md files are data, not skills. Before, a prompts/*.md with 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) and docs/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, or null if the user declines. deleteTier(pluginId, id) lists the roles mapped to the tier and asks first; it resolves true once deleted. Both reject with a HubError for bad input, before the user sees anything. Tier carries { 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; setRole accepts a tier by name too (stored by id). Store the id createTier returns; 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: TierName is now string (a built-in or custom tier's id or name). New exports: BUILTIN_TIERS, isBuiltinTier, BuiltinTierName (TIER_NAMES stays, as the seven built-in tiers).
    • Ledger: LedgerRow.tier is the tier's id (a built-in tier's id is its name), and the new tierName is 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 on quick (before, it fell back to balanced).
    • 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, but roles() now flags it (tierMissing: true, with the name as given in missingTier). In 0.3 such a tier became balanced silently.
    • 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 has files(): the skill's own text files by path relative to its folder (reader.yml, prompts/extract.md), recursive, without SKILL.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; a SKILL.md directly in the skills folder (not in a subfolder) is ignored and listed as one. Entries also carry plugin (who registered it) and extendsMissing.
    • registerSkills(pluginId, skills): a plugin registers its built-in skills as folders of files (SKILL.md among them). They survive Reload skills, a vault skill of the same name overrides one, the first plugin to register a name owns it (skill-taken for others, and for the Hub's own skill names), and they are removed when the plugin unloads. No plugin may register as model-hub; a registered skill may not carry a nested SKILL.md or files over the size caps.
    • Registered skills are chosen-only: a registered SKILL.md with mode: always is 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) (beside getHub) calls cb now 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 .md files are data, not skills (before, a prompts/*.md with name and description frontmatter became a stray skill).
    • Behaviour change: a skill whose extends names 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/deleteTier dialogs mark a plugin id that no installed plugin has as "(unknown plugin)"; a proposed candidate with routing that isn't plain data is refused as invalid-request, and only a candidate's own fields are kept.
  • API 0.3: per-call routing (merged into each candidate's OpenRouter routing, recorded in the ledger) and per-call candidates (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 use roles() / 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, with getHub() 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.