fix(model): require provider readiness for saved default selection
This commit is contained in:
parent
e247f2fe61
commit
c96d01acb7
2 changed files with 100 additions and 18 deletions
|
|
@ -0,0 +1,78 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
import type { Api, Model } from "@gsd/pi-ai";
|
||||
import type { ModelRegistry } from "./model-registry.js";
|
||||
import { findInitialModel } from "./model-resolver.js";
|
||||
|
||||
function makeModel(provider: string, id: string): Model<Api> {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
provider,
|
||||
api: "openai-responses",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128000,
|
||||
maxTokens: 8192,
|
||||
} as Model<Api>;
|
||||
}
|
||||
|
||||
function makeRegistry(opts: {
|
||||
readyProviders?: Set<string>;
|
||||
byProviderAndId?: Map<string, Model<Api>>;
|
||||
available?: Model<Api>[];
|
||||
}): ModelRegistry {
|
||||
const readyProviders = opts.readyProviders ?? new Set<string>();
|
||||
const byProviderAndId = opts.byProviderAndId ?? new Map<string, Model<Api>>();
|
||||
const available = opts.available ?? [];
|
||||
|
||||
return {
|
||||
find: (provider: string, modelId: string) => byProviderAndId.get(`${provider}/${modelId}`),
|
||||
getAvailable: async () => available,
|
||||
isProviderRequestReady: (provider: string) => readyProviders.has(provider),
|
||||
} as unknown as ModelRegistry;
|
||||
}
|
||||
|
||||
describe("findInitialModel auth gating for saved defaults", () => {
|
||||
it("uses saved default when provider is request-ready", async () => {
|
||||
const saved = makeModel("anthropic", "claude-opus-4-6");
|
||||
const registry = makeRegistry({
|
||||
readyProviders: new Set(["anthropic"]),
|
||||
byProviderAndId: new Map([[`anthropic/claude-opus-4-6`, saved]]),
|
||||
available: [saved],
|
||||
});
|
||||
|
||||
const result = await findInitialModel({
|
||||
scopedModels: [],
|
||||
isContinuing: false,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModelId: "claude-opus-4-6",
|
||||
modelRegistry: registry,
|
||||
});
|
||||
|
||||
assert.equal(result.model?.provider, "anthropic");
|
||||
assert.equal(result.model?.id, "claude-opus-4-6");
|
||||
});
|
||||
|
||||
it("skips saved default when provider is not request-ready and falls back to available", async () => {
|
||||
const staleDefault = makeModel("anthropic", "claude-opus-4-6");
|
||||
const fallback = makeModel("openai", "gpt-5.4");
|
||||
const registry = makeRegistry({
|
||||
readyProviders: new Set(["openai"]),
|
||||
byProviderAndId: new Map([[`anthropic/claude-opus-4-6`, staleDefault]]),
|
||||
available: [fallback],
|
||||
});
|
||||
|
||||
const result = await findInitialModel({
|
||||
scopedModels: [],
|
||||
isContinuing: false,
|
||||
defaultProvider: "anthropic",
|
||||
defaultModelId: "claude-opus-4-6",
|
||||
modelRegistry: registry,
|
||||
});
|
||||
|
||||
assert.equal(result.model?.provider, "openai");
|
||||
assert.equal(result.model?.id, "gpt-5.4");
|
||||
});
|
||||
});
|
||||
|
|
@ -504,27 +504,31 @@ export async function findInitialModel(options: {
|
|||
|
||||
// 3. Try saved default from settings
|
||||
if (defaultProvider && defaultModelId) {
|
||||
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
||||
if (found) {
|
||||
// Check if the provider's recommended default is a higher-capability variant
|
||||
// of the saved model (e.g. saved "claude-opus-4-6" vs recommended "claude-opus-4-6-extended").
|
||||
// If so, prefer the recommended variant to avoid using a smaller context window (#1125).
|
||||
const recommendedId = defaultModelPerProvider[defaultProvider as KnownProvider];
|
||||
if (recommendedId && recommendedId !== defaultModelId && recommendedId.startsWith(defaultModelId)) {
|
||||
const recommended = modelRegistry.find(defaultProvider, recommendedId);
|
||||
if (recommended) {
|
||||
model = recommended;
|
||||
if (defaultThinkingLevel) {
|
||||
thinkingLevel = defaultThinkingLevel;
|
||||
// Guard against stale settings defaults: only use the saved provider/model
|
||||
// if the provider is actually request-ready (auth/OAuth/CLI ready).
|
||||
if (modelRegistry.isProviderRequestReady(defaultProvider)) {
|
||||
const found = modelRegistry.find(defaultProvider, defaultModelId);
|
||||
if (found) {
|
||||
// Check if the provider's recommended default is a higher-capability variant
|
||||
// of the saved model (e.g. saved "claude-opus-4-6" vs recommended "claude-opus-4-6-extended").
|
||||
// If so, prefer the recommended variant to avoid using a smaller context window (#1125).
|
||||
const recommendedId = defaultModelPerProvider[defaultProvider as KnownProvider];
|
||||
if (recommendedId && recommendedId !== defaultModelId && recommendedId.startsWith(defaultModelId)) {
|
||||
const recommended = modelRegistry.find(defaultProvider, recommendedId);
|
||||
if (recommended) {
|
||||
model = recommended;
|
||||
if (defaultThinkingLevel) {
|
||||
thinkingLevel = defaultThinkingLevel;
|
||||
}
|
||||
return { model, thinkingLevel, fallbackMessage: undefined };
|
||||
}
|
||||
return { model, thinkingLevel, fallbackMessage: undefined };
|
||||
}
|
||||
model = found;
|
||||
if (defaultThinkingLevel) {
|
||||
thinkingLevel = defaultThinkingLevel;
|
||||
}
|
||||
return { model, thinkingLevel, fallbackMessage: undefined };
|
||||
}
|
||||
model = found;
|
||||
if (defaultThinkingLevel) {
|
||||
thinkingLevel = defaultThinkingLevel;
|
||||
}
|
||||
return { model, thinkingLevel, fallbackMessage: undefined };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue