feat(models): strong/cheap/vision model slots + multi-provider engine federation

Model slots wave (2026-09-20), chunk B:

- settings: new `models` section {strong, cheap, vision} slots (each
  field independent, same shape as a role override); ROLE_SLOT maps all
  nine roles to tiers (strong=prober+teacher, cheap=researcher/
  demoBuilder/extractor/auditor/labeler, vision=visualMaker+pdfReader);
  resolution chain per role: override → slot → factory pin.
- config: ModelSpec gains provider (all pins openrouter, the BYOK
  default); MODEL_PINS in stores.ts now spans all nine roles (the strong
  slot's factory rep IS the prober pin).
- engine: piModels() registers EVERY builtin provider catalog;
  piEngineForSpec(resolved spec) is the new engine factory (pi-ai
  resolves per-provider keys from env itself); piEngineFor stays as the
  config-pin fallback. chat-handler resolves the conversation chain
  (chat override → strong slot → pin) and injects engineFor into the job
  runner, closing the old gap where per-role overrides never reached
  job engines.
- /api/models: models federate ONLY keyed providers (providers surface
  with keyPresent, env-wins, fresh per call); slots (resolved tier pins)
  join rolePins; catalogDefaultsFor inherits the strong slot; the models
  settings section gets the same catalog gate as chat.
- pricing: PRICES/CONTEXT_LIMITS uniformly keyed provider:id
  (regenerated from ALL pi-ai catalogs, 1354 keys); usage stamps
  (TurnUsage, JobUsage, extraction stages) switch to provider:id —
  owner decision: no legacy compat, old stamps render unpriced.
This commit is contained in:
amosblomqvist
2026-09-22 00:14:56 +03:00
parent f1373d5de5
commit f9e3a4c43d
26 changed files with 3529 additions and 912 deletions
+15 -10
View File
@@ -46,7 +46,12 @@ export const PATHS = {
export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
export interface ModelSpec {
/** OpenRouter model id (e.g. "z-ai/glm-5.3-flash") — must exist in pi-ai's catalog. */
/** The provider that owns the model (model slots wave, 2026-09-20):
* pi-ai provider id. Native ids are plain strings ("glm-5.3-flash" on
* zai); OpenRouter ids are vendor slugs ("z-ai/glm-5.3-flash"). The
* factory pins stay OpenRouter (the BYOK default provider). */
provider: string;
/** The model id on that provider — must exist in pi-ai's catalog. */
model: string;
/** Thinking budget — what the model may spend on reasoning tokens. */
effort: ThinkingLevel;
@@ -54,28 +59,28 @@ export interface ModelSpec {
export const MODELS = {
/** The prober (agent A) — edge-bracketing questions; keep it lively and adaptive. */
prober: { model: "z-ai/glm-5.3-flash", effort: "low" },
prober: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
/** The teacher (agent B) — planning + streaming, lesson-facing: smart, but keep it lively. */
teacher: { model: "z-ai/glm-5.3-flash", effort: "low" },
teacher: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
/** Background sub-agents: fast and cheap beats deep thought. */
researcher: { model: "z-ai/glm-5.3-flash", effort: "low" },
demoBuilder: { model: "z-ai/glm-5.3-flash", effort: "low" },
researcher: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
demoBuilder: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
/** Must be vision-capable: it looks at rendered SVGs. */
visualMaker: { model: "qwen/qwen3.8-flash", effort: "low" },
visualMaker: { provider: "openrouter", model: "qwen/qwen3.8-flash", effort: "low" },
/** SEAM (master-approved 2026-09, revised by the user 2026-09-19): the
* pdf-reader sub-agent. Must be vision-capable — it looks at rendered
* PDF pages; its briefs are text. The user overrode the initial
* glm-5.3-flash pick (catalog says it accepts images, but qwen is
* proven for exactly this look-at-renders job — visualMaker's daily
* workload). */
pdfReader: { model: "qwen/qwen3.8-flash", effort: "low" },
pdfReader: { provider: "openrouter", model: "qwen/qwen3.8-flash", effort: "low" },
/** Extraction is judgment over given evidence — light thinking suffices. */
extractor: { model: "z-ai/glm-5.3-flash", effort: "low" },
extractor: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
/** The auditor verifies evidence exists; keep it cheap. */
auditor: { model: "z-ai/glm-5.3-flash", effort: "low" },
auditor: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
/** Names detected communities — one singular call, given member names
* + statements. Purely a labeling flourish; keep it cheap. */
labeler: { model: "z-ai/glm-5.3-flash", effort: "low" },
labeler: { provider: "openrouter", model: "z-ai/glm-5.3-flash", effort: "low" },
} satisfies Record<string, ModelSpec>;
export type ModelRole = keyof typeof MODELS;