A space can now map the tiers fast, standard and code to connected models, including several models served by one OpenAI-compatible router endpoint (one credential, many model ids). A bot can ask for a tier, and a single run_subagent call can ask for one. A tier with no model mapped falls back to the usual model choice, so nothing breaks before tiers are set up. - db: SpaceModelTier repo (list, find, set or clear); tier mappings are copied into a new space like model and voice preferences; createBot carries a tier, so duplicates keep it and spawned bots inherit it. - contracts/api: models.tiers.list and models.tiers.set; models.probeOpenAiCompatible can list a connected server's models by credential id with its stored address and key; bots.update accepts modelTier and keeps a pinned model and a tier mutually exclusive, so a model picked on a surface that knows no tiers (mobile) is never silently overridden. Thinking-level validation follows the tier's model. AI consent status discloses tier models; desktop local settings allow the tier procedures. - adapters: selectConfiguredModel runs a mapped tier ahead of the pinned model and the space default; the executor resolves bot tiers for runs and model lookups, and helper tiers through resolveModelTier. run_subagent takes model_tier; an explicit model wins over it. - web: a collapsed Tiers section in model settings (shown once a model is connected) and mapped tiers as choices in the bot Model select. - docs: tiers in self-host model setup. - tests: resolution order and fallbacks (selection, executor, Pi helper), repo, API and contract units, authorization and offline Pi Postgres suites, and a model-tiers web E2E spec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import type { ModelCatalogEntry, ModelCredential } from "@rakazo/contracts";
|
|
import { describe, expect, it } from "vitest";
|
|
import { connectedModelOptions } from "./connected-models";
|
|
|
|
const catalog: ModelCatalogEntry[] = [
|
|
{ provider: "xai", providerName: "xAI", id: "grok-4.6", label: "Grok 4.6", billing: "" },
|
|
{ provider: "xai", providerName: "xAI", id: "grok-mini", label: "Grok mini", billing: "" },
|
|
{
|
|
provider: "openai-compatible",
|
|
providerName: "OpenAI-compatible",
|
|
id: "custom",
|
|
label: "Custom",
|
|
billing: "",
|
|
placeholder: true,
|
|
},
|
|
];
|
|
|
|
const credentials: ModelCredential[] = [
|
|
{
|
|
id: "router",
|
|
provider: "openai-compatible",
|
|
label: "OpenAI-compatible",
|
|
hasKey: true,
|
|
isDefault: true,
|
|
modelId: "router-standard",
|
|
},
|
|
{ id: "xai", provider: "xai", label: "xAI", hasKey: true, isDefault: false, modelId: "grok-4.6" },
|
|
];
|
|
|
|
describe("connected model options", () => {
|
|
it("expands catalog providers and keeps a free-form connection's saved model", () => {
|
|
expect(connectedModelOptions(credentials, catalog)).toEqual([
|
|
{
|
|
credentialId: "router",
|
|
provider: "openai-compatible",
|
|
modelId: "router-standard",
|
|
label: "OpenAI-compatible · router-standard",
|
|
},
|
|
{ credentialId: "xai", provider: "xai", modelId: "grok-4.6", label: "xAI · Grok 4.6" },
|
|
{ credentialId: "xai", provider: "xai", modelId: "grok-mini", label: "xAI · Grok mini" },
|
|
]);
|
|
});
|
|
|
|
it("adds every model a router endpoint lists, once each", () => {
|
|
const options = connectedModelOptions(credentials, catalog, {
|
|
router: ["router-standard", "router-fast", "router-code"],
|
|
});
|
|
|
|
expect(options.filter((option) => option.credentialId === "router")).toEqual(
|
|
["router-standard", "router-fast", "router-code"].map((modelId) => ({
|
|
credentialId: "router",
|
|
provider: "openai-compatible",
|
|
modelId,
|
|
label: `OpenAI-compatible · ${modelId}`,
|
|
})),
|
|
);
|
|
});
|
|
});
|