fix(models): avoid registry for configured list

This commit is contained in:
Vincent Koc
2026-04-26 03:24:28 -07:00
parent 23710167cd
commit 8740ca7dee
7 changed files with 292 additions and 119 deletions

View File

@@ -147,6 +147,7 @@ function installModelsListCommandForwardCompatMocks() {
vi.doMock("./list.provider-catalog.js", () => ({
hasProviderStaticCatalogForFilter: mocks.hasProviderStaticCatalogForFilter,
loadProviderCatalogModelsForList: mocks.loadProviderCatalogModelsForList,
}));
vi.doMock("./list.manifest-catalog.js", () => ({
@@ -190,14 +191,27 @@ function installModelsListCommandForwardCompatMocks() {
},
}));
vi.doMock("./list.runtime.js", () => ({
ensureOpenClawModelsJson: mocks.ensureOpenClawModelsJson,
ensureAuthProfileStore: mocks.ensureAuthProfileStore,
vi.doMock("../../agents/auth-profiles/store.js", () => ({
loadAuthProfileStoreWithoutExternalProfiles: mocks.ensureAuthProfileStore,
}));
vi.doMock("../../agents/agent-paths.js", () => ({
resolveOpenClawAgentDir: mocks.resolveOpenClawAgentDir,
}));
vi.doMock("../../agents/auth-profiles/profile-list.js", () => ({
listProfilesForProvider: mocks.listProfilesForProvider,
}));
vi.doMock("../../agents/model-catalog.js", () => ({
loadModelCatalog: mocks.loadModelCatalog,
loadProviderCatalogModelsForList: mocks.loadProviderCatalogModelsForList,
}));
vi.doMock("../../agents/pi-embedded-runner/model.js", () => ({
resolveModelWithRegistry: mocks.resolveModelWithRegistry,
}));
vi.doMock("../../agents/model-auth.js", () => ({
resolveEnvApiKey: vi.fn().mockReturnValue(undefined),
resolveAwsSdkEnvVarName: vi.fn().mockReturnValue(undefined),
hasUsableCustomProviderApiKey: vi.fn().mockReturnValue(false),
@@ -232,7 +246,7 @@ async function buildAllOpenAiCodexRows(opts: { supplementCatalog?: boolean } = {
),
filter: { provider: "openai-codex" },
};
const seenKeys = listRowsModule.appendDiscoveredRows({
const seenKeys = await listRowsModule.appendDiscoveredRows({
rows: rows as never,
models: loaded.models as never,
modelRegistry: loaded.registry as never,
@@ -256,17 +270,14 @@ beforeEach(() => {
describe("modelsListCommand forward-compat", () => {
describe("configured rows", () => {
it("passes provider filters into registry loading before row assembly", async () => {
it("keeps configured provider filters on the registry-free row path", async () => {
const runtime = createRuntime();
await modelsListCommand({ json: true, provider: "moonshot" }, runtime as never);
expect(mocks.loadModelRegistry).toHaveBeenCalledWith(
mocks.resolvedConfig,
expect.objectContaining({
providerFilter: "moonshot",
}),
);
expect(mocks.loadModelRegistry).not.toHaveBeenCalled();
expect(mocks.printModelTable).not.toHaveBeenCalled();
expect(runtime.log).toHaveBeenCalledWith("No models found.");
});
it("does not mark configured codex model as missing when forward-compat can build a fallback", async () => {
@@ -345,17 +356,12 @@ describe("modelsListCommand forward-compat", () => {
expect(codexPro?.tags).not.toContain("missing");
});
it("loads model registry without source config persistence input", async () => {
it("does not load the model registry for configured-mode listing", async () => {
const runtime = createRuntime();
await modelsListCommand({ json: true }, runtime as never);
expect(mocks.loadModelRegistry).toHaveBeenCalledWith(
mocks.resolvedConfig,
expect.not.objectContaining({
sourceConfig: expect.anything(),
}),
);
expect(mocks.loadModelRegistry).not.toHaveBeenCalled();
});
it("keeps configured local openai gpt-5.4 entries visible in --local output", async () => {
@@ -568,19 +574,15 @@ describe("modelsListCommand forward-compat", () => {
]);
});
it("keeps the registry path for provider filters without static catalog coverage", async () => {
it("does not fall back to the registry for provider filters without catalog coverage", async () => {
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
mocks.hasProviderStaticCatalogForFilter.mockResolvedValueOnce(false);
const runtime = createRuntime();
await modelsListCommand({ all: true, provider: "openrouter", json: true }, runtime as never);
expect(mocks.loadModelRegistry).toHaveBeenCalledWith(
mocks.resolvedConfig,
expect.objectContaining({
providerFilter: "openrouter",
}),
);
expect(mocks.loadModelRegistry).not.toHaveBeenCalled();
expect(runtime.log).toHaveBeenCalledWith("No models found.");
});
it("includes provider-owned supplemental catalog rows with provider filters", async () => {
@@ -748,7 +750,7 @@ describe("modelsListCommand forward-compat", () => {
it("suppresses direct openai gpt-5.3-codex-spark rows in --all output", async () => {
mocks.resolveConfiguredEntries.mockReturnValueOnce({ entries: [] });
const rows: unknown[] = [];
listRowsModule.appendDiscoveredRows({
await listRowsModule.appendDiscoveredRows({
rows: rows as never,
models: [
{

View File

@@ -5,13 +5,6 @@ import type { RuntimeEnv } from "../../runtime.js";
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
import { resolveConfiguredEntries } from "./list.configured.js";
import { formatErrorWithStack } from "./list.errors.js";
import { hasProviderStaticCatalogForFilter } from "./list.provider-catalog.js";
import { loadConfiguredListModelRegistry, loadListModelRegistry } from "./list.registry-load.js";
import {
appendAllModelRowSources,
appendConfiguredModelRowSources,
modelRowSourcesRequireRegistry,
} from "./list.row-sources.js";
import { printModelTable } from "./list.table.js";
import type { ModelRow } from "./list.types.js";
import { loadModelsConfigWithSource } from "./load-config.js";
@@ -19,6 +12,45 @@ import { DEFAULT_PROVIDER, ensureFlagCompatibility } from "./shared.js";
const DISPLAY_MODEL_PARSE_OPTIONS = { allowPluginNormalization: false } as const;
type RegistryLoadModule = typeof import("./list.registry-load.js");
type RowSourcesModule = typeof import("./list.row-sources.js");
type ProviderCatalogModule = typeof import("./list.provider-catalog.js");
let registryLoadModulePromise: Promise<RegistryLoadModule> | undefined;
let rowSourcesModulePromise: Promise<RowSourcesModule> | undefined;
let providerCatalogModulePromise: Promise<ProviderCatalogModule> | undefined;
function loadRegistryLoadModule(): Promise<RegistryLoadModule> {
registryLoadModulePromise ??= import("./list.registry-load.js");
return registryLoadModulePromise;
}
function loadRowSourcesModule(): Promise<RowSourcesModule> {
rowSourcesModulePromise ??= import("./list.row-sources.js");
return rowSourcesModulePromise;
}
function loadProviderCatalogModule(): Promise<ProviderCatalogModule> {
providerCatalogModulePromise ??= import("./list.provider-catalog.js");
return providerCatalogModulePromise;
}
function modelRowSourcesRequireRegistry(params: {
all?: boolean;
providerFilter?: string;
useManifestCatalogFastPath: boolean;
useProviderCatalogFastPath: boolean;
useProviderIndexCatalogFastPath: boolean;
}): boolean {
if (!params.all) {
return false;
}
if (params.providerFilter) {
return false;
}
return true;
}
export async function modelsListCommand(
opts: {
all?: boolean;
@@ -48,12 +80,16 @@ export async function modelsListCommand(
if (providerFilter === null) {
return;
}
const { ensureAuthProfileStore, resolveOpenClawAgentDir } = await import("./list.runtime.js");
const [{ loadAuthProfileStoreWithoutExternalProfiles }, { resolveOpenClawAgentDir }] =
await Promise.all([
import("../../agents/auth-profiles/store.js"),
import("../../agents/agent-paths.js"),
]);
const { resolvedConfig: cfg } = await loadModelsConfigWithSource({
commandName: "models list",
runtime,
});
const authStore = ensureAuthProfileStore();
const authStore = loadAuthProfileStoreWithoutExternalProfiles();
const agentDir = resolveOpenClawAgentDir();
let modelRegistry: ModelRegistry | undefined;
@@ -69,16 +105,24 @@ export async function modelsListCommand(
manifestCatalogRows = loadStaticManifestCatalogRowsForList({ cfg, providerFilter });
}
const useManifestCatalogFastPath = manifestCatalogRows.length > 0;
const useProviderCatalogFastPath =
!useManifestCatalogFastPath && opts.all && providerFilter
? await hasProviderStaticCatalogForFilter({ cfg, providerFilter })
: false;
if (!useManifestCatalogFastPath && !useProviderCatalogFastPath && opts.all && providerFilter) {
if (!useManifestCatalogFastPath && opts.all && providerFilter) {
const { loadProviderIndexCatalogRowsForList } =
await import("./list.provider-index-catalog.js");
providerIndexCatalogRows = loadProviderIndexCatalogRowsForList({ cfg, providerFilter });
}
const useProviderIndexCatalogFastPath = providerIndexCatalogRows.length > 0;
const useProviderCatalogFastPath = await (async () => {
if (
useManifestCatalogFastPath ||
useProviderIndexCatalogFastPath ||
!opts.all ||
!providerFilter
) {
return false;
}
const { hasProviderStaticCatalogForFilter } = await loadProviderCatalogModule();
return hasProviderStaticCatalogForFilter({ cfg, providerFilter });
})();
const shouldLoadRegistry = modelRowSourcesRequireRegistry({
all: opts.all,
providerFilter,
@@ -87,6 +131,7 @@ export async function modelsListCommand(
useProviderIndexCatalogFastPath,
});
const loadRegistryState = async () => {
const { loadListModelRegistry } = await loadRegistryLoadModule();
const loaded = await loadListModelRegistry(cfg, { providerFilter });
modelRegistry = loaded.registry;
discoveredKeys = loaded.discoveredKeys;
@@ -96,7 +141,8 @@ export async function modelsListCommand(
try {
if (shouldLoadRegistry) {
await loadRegistryState();
} else if (!opts.all) {
} else if (!opts.all && opts.local) {
const { loadConfiguredListModelRegistry } = await loadRegistryLoadModule();
const loaded = loadConfiguredListModelRegistry(cfg, entries, { providerFilter });
modelRegistry = loaded.registry;
discoveredKeys = loaded.discoveredKeys;
@@ -123,6 +169,7 @@ export async function modelsListCommand(
const rows: ModelRow[] = [];
if (opts.all) {
const { appendAllModelRowSources } = await loadRowSourcesModule();
let rowContext = buildRowContext(
useManifestCatalogFastPath || useProviderCatalogFastPath || useProviderIndexCatalogFastPath,
);
@@ -158,17 +205,12 @@ export async function modelsListCommand(
});
}
} else {
const registry = modelRegistry;
if (!registry) {
runtime.error("Model registry unavailable.");
process.exitCode = 1;
return;
}
appendConfiguredModelRowSources({
const { appendConfiguredModelRowSources } = await loadRowSourcesModule();
await appendConfiguredModelRowSources({
rows,
entries,
modelRegistry: registry,
context: buildRowContext(false),
modelRegistry,
context: buildRowContext(!modelRegistry),
});
}

View File

@@ -1,9 +1,10 @@
import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js";
import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js";
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { loadModelRegistry } from "./list.registry.js";
import { discoverAuthStorage, discoverModels, resolveOpenClawAgentDir } from "./list.runtime.js";
import type { ConfiguredEntry } from "./list.types.js";
import { modelKey } from "./shared.js";

View File

@@ -1,7 +1,15 @@
import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js";
import { listProfilesForProvider } from "../../agents/auth-profiles/profile-list.js";
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
import {
hasUsableCustomProviderApiKey,
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js";
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { resolveRuntimeSyntheticAuthProviderRefs } from "../../plugins/synthetic-auth.runtime.js";
import {
@@ -10,15 +18,6 @@ import {
shouldFallbackToAuthHeuristics,
} from "./list.errors.js";
import { toModelRow as toModelRowBase } from "./list.model-row.js";
import {
discoverAuthStorage,
discoverModels,
hasUsableCustomProviderApiKey,
listProfilesForProvider,
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
resolveOpenClawAgentDir,
} from "./list.runtime.js";
import type { ModelRow } from "./list.types.js";
import { modelKey } from "./shared.js";

View File

@@ -37,12 +37,7 @@ export function modelRowSourcesRequireRegistry(params: {
if (!params.all) {
return false;
}
if (
params.providerFilter &&
(params.useManifestCatalogFastPath ||
params.useProviderCatalogFastPath ||
params.useProviderIndexCatalogFastPath)
) {
if (params.providerFilter) {
return false;
}
return true;
@@ -58,14 +53,14 @@ export async function appendAllModelRowSources(
params.useProviderIndexCatalogFastPath)
) {
let seenKeys = new Set<string>();
appendConfiguredProviderRows({
await appendConfiguredProviderRows({
rows: params.rows,
context: params.context,
seenKeys,
});
let catalogRows = 0;
if (params.useManifestCatalogFastPath) {
catalogRows = appendManifestCatalogRows({
catalogRows = await appendManifestCatalogRows({
rows: params.rows,
context: params.context,
seenKeys,
@@ -81,7 +76,7 @@ export async function appendAllModelRowSources(
});
}
if (catalogRows === 0 && params.useProviderIndexCatalogFastPath) {
catalogRows = appendModelCatalogRows({
catalogRows = await appendModelCatalogRows({
rows: params.rows,
context: params.context,
seenKeys,
@@ -92,7 +87,7 @@ export async function appendAllModelRowSources(
if (!params.modelRegistry) {
return { requiresRegistryFallback: true };
}
appendDiscoveredRows({
await appendDiscoveredRows({
rows: params.rows,
models: params.modelRegistry.getAll(),
modelRegistry: params.modelRegistry,
@@ -102,14 +97,14 @@ export async function appendAllModelRowSources(
return { requiresRegistryFallback: false };
}
const seenKeys = appendDiscoveredRows({
const seenKeys = await appendDiscoveredRows({
rows: params.rows,
models: params.modelRegistry?.getAll() ?? [],
modelRegistry: params.modelRegistry,
context: params.context,
});
appendConfiguredProviderRows({
await appendConfiguredProviderRows({
rows: params.rows,
context: params.context,
seenKeys,
@@ -133,11 +128,11 @@ export async function appendAllModelRowSources(
return { requiresRegistryFallback: false };
}
export function appendConfiguredModelRowSources(params: {
export async function appendConfiguredModelRowSources(params: {
rows: ModelRow[];
entries: ConfiguredEntry[];
modelRegistry: ModelRegistry;
modelRegistry?: ModelRegistry;
context: RowBuilderContext;
}): void {
appendConfiguredRows(params);
}): Promise<void> {
await appendConfiguredRows(params);
}

View File

@@ -23,9 +23,15 @@ vi.mock("../../agents/model-suppression.js", () => ({
shouldSuppressBuiltInModel: mocks.shouldSuppressBuiltInModel,
}));
vi.mock("./list.runtime.js", () => ({
vi.mock("./list.provider-catalog.js", () => ({
loadProviderCatalogModelsForList: mocks.loadProviderCatalogModelsForList,
}));
vi.mock("../../agents/auth-profiles/profile-list.js", () => ({
listProfilesForProvider: mocks.listProfilesForProvider,
}));
vi.mock("../../agents/model-auth.js", () => ({
resolveAwsSdkEnvVarName: vi.fn().mockReturnValue(undefined),
resolveEnvApiKey: vi.fn().mockReturnValue(null),
hasUsableCustomProviderApiKey: vi.fn().mockReturnValue(false),

View File

@@ -2,22 +2,27 @@ import type { Api, Model } from "@mariozechner/pi-ai";
import type { ModelRegistry } from "@mariozechner/pi-coding-agent";
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
import {
hasUsableCustomProviderApiKey,
resolveAwsSdkEnvVarName,
resolveEnvApiKey,
} from "../../agents/model-auth.js";
import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js";
import { normalizeProviderId } from "../../agents/provider-id.js";
import type { ModelDefinitionConfig, ModelProviderConfig } from "../../config/types.models.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import type { NormalizedModelCatalogRow } from "../../model-catalog/index.js";
import type { ListRowModel } from "./list.model-row.js";
import { toModelRow } from "./list.registry.js";
import {
loadModelCatalog,
loadProviderCatalogModelsForList,
resolveModelWithRegistry,
} from "./list.runtime.js";
import { toModelRow } from "./list.model-row.js";
import type { ConfiguredEntry, ModelRow } from "./list.types.js";
import { isLocalBaseUrl, modelKey } from "./shared.js";
type ConfiguredByKey = Map<string, ConfiguredEntry>;
type ModelCatalogModule = typeof import("../../agents/model-catalog.js");
type ModelResolverModule = typeof import("../../agents/pi-embedded-runner/model.js");
type ProfileListModule = typeof import("../../agents/auth-profiles/profile-list.js");
type ProviderCatalogModule = typeof import("./list.provider-catalog.js");
type SyntheticAuthModule = typeof import("../../plugins/synthetic-auth.runtime.js");
type RowFilter = {
provider?: string;
@@ -35,6 +40,37 @@ export type RowBuilderContext = {
skipRuntimeModelSuppression?: boolean;
};
let modelCatalogModulePromise: Promise<ModelCatalogModule> | undefined;
let modelResolverModulePromise: Promise<ModelResolverModule> | undefined;
let profileListModulePromise: Promise<ProfileListModule> | undefined;
let providerCatalogModulePromise: Promise<ProviderCatalogModule> | undefined;
let syntheticAuthModulePromise: Promise<SyntheticAuthModule> | undefined;
function loadModelCatalogModule(): Promise<ModelCatalogModule> {
modelCatalogModulePromise ??= import("../../agents/model-catalog.js");
return modelCatalogModulePromise;
}
function loadModelResolverModule(): Promise<ModelResolverModule> {
modelResolverModulePromise ??= import("../../agents/pi-embedded-runner/model.js");
return modelResolverModulePromise;
}
function loadProfileListModule(): Promise<ProfileListModule> {
profileListModulePromise ??= import("../../agents/auth-profiles/profile-list.js");
return profileListModulePromise;
}
function loadProviderCatalogModule(): Promise<ProviderCatalogModule> {
providerCatalogModulePromise ??= import("./list.provider-catalog.js");
return providerCatalogModulePromise;
}
function loadSyntheticAuthModule(): Promise<SyntheticAuthModule> {
syntheticAuthModulePromise ??= import("../../plugins/synthetic-auth.runtime.js");
return syntheticAuthModulePromise;
}
function matchesRowFilter(filter: RowFilter, model: { provider: string; baseUrl?: string }) {
if (filter.provider && normalizeProviderId(model.provider) !== filter.provider) {
return false;
@@ -45,13 +81,44 @@ function matchesRowFilter(filter: RowFilter, model: { provider: string; baseUrl?
return true;
}
function buildRow(params: {
async function hasAuthForProvider(params: {
provider: string;
cfg: OpenClawConfig;
authStore: AuthProfileStore;
}): Promise<boolean> {
const { listProfilesForProvider } = await loadProfileListModule();
if (listProfilesForProvider(params.authStore, params.provider).length > 0) {
return true;
}
if (params.provider === "amazon-bedrock" && resolveAwsSdkEnvVarName()) {
return true;
}
if (resolveEnvApiKey(params.provider)) {
return true;
}
if (hasUsableCustomProviderApiKey(params.cfg, params.provider)) {
return true;
}
const { resolveRuntimeSyntheticAuthProviderRefs } = await loadSyntheticAuthModule();
return resolveRuntimeSyntheticAuthProviderRefs().includes(params.provider);
}
async function buildRow(params: {
model: ListRowModel;
key: string;
context: RowBuilderContext;
allowProviderAvailabilityFallback?: boolean;
}): ModelRow {
}): Promise<ModelRow> {
const configured = params.context.configuredByKey.get(params.key);
const shouldResolveProviderAuth =
params.context.availableKeys === undefined || params.allowProviderAvailabilityFallback === true;
const hasProviderAuth = shouldResolveProviderAuth
? await hasAuthForProvider({
provider: params.model.provider,
cfg: params.context.cfg,
authStore: params.context.authStore,
})
: false;
return toModelRow({
model: params.model,
key: params.key,
@@ -61,6 +128,7 @@ function buildRow(params: {
cfg: params.context.cfg,
authStore: params.context.authStore,
allowProviderAvailabilityFallback: params.allowProviderAvailabilityFallback ?? false,
hasAuthForProvider: shouldResolveProviderAuth ? () => hasProviderAuth : undefined,
});
}
@@ -79,14 +147,14 @@ function shouldSuppressListModel(params: {
});
}
function appendVisibleRow(params: {
async function appendVisibleRow(params: {
rows: ModelRow[];
model: ListRowModel;
key: string;
context: RowBuilderContext;
seenKeys?: Set<string>;
allowProviderAvailabilityFallback?: boolean;
}): boolean {
}): Promise<boolean> {
if (params.seenKeys?.has(params.key)) {
return false;
}
@@ -97,7 +165,7 @@ function appendVisibleRow(params: {
return false;
}
params.rows.push(
buildRow({
await buildRow({
model: params.model,
key: params.key,
context: params.context,
@@ -153,13 +221,49 @@ function shouldListConfiguredProviderModel(params: {
return params.providerConfig.api !== undefined || params.model.api !== undefined;
}
export function appendDiscoveredRows(params: {
function findConfiguredProviderModel(params: {
cfg: OpenClawConfig;
provider: string;
modelId: string;
}): ListRowModel | undefined {
const providerConfig = params.cfg.models?.providers?.[params.provider];
const configuredModel = providerConfig?.models?.find((model) => model.id === params.modelId);
if (!providerConfig || !configuredModel) {
return undefined;
}
return toConfiguredProviderListModel({
provider: params.provider,
providerConfig,
model: configuredModel,
});
}
function toFallbackConfiguredListModel(entry: ConfiguredEntry, cfg: OpenClawConfig): ListRowModel {
return (
findConfiguredProviderModel({
cfg,
provider: entry.ref.provider,
modelId: entry.ref.model,
}) ?? {
provider: entry.ref.provider,
id: entry.ref.model,
name: entry.ref.model,
input: ["text"],
contextWindow: DEFAULT_CONTEXT_TOKENS,
}
);
}
export async function appendDiscoveredRows(params: {
rows: ModelRow[];
models: Model<Api>[];
modelRegistry?: ModelRegistry;
context: RowBuilderContext;
}): Set<string> {
}): Promise<Set<string>> {
const seenKeys = new Set<string>();
const modelResolver = params.modelRegistry
? (await loadModelResolverModule()).resolveModelWithRegistry
: undefined;
const sorted = [...params.models].toSorted((a, b) => {
const providerCompare = a.provider.localeCompare(b.provider);
if (providerCompare !== 0) {
@@ -170,20 +274,21 @@ export function appendDiscoveredRows(params: {
for (const model of sorted) {
const key = modelKey(model.provider, model.id);
const resolvedModel = params.modelRegistry
? resolveModelWithRegistry({
provider: model.provider,
modelId: model.id,
modelRegistry: params.modelRegistry,
cfg: params.context.cfg,
agentDir: params.context.agentDir,
})
: undefined;
const resolvedModel =
params.modelRegistry && modelResolver
? modelResolver({
provider: model.provider,
modelId: model.id,
modelRegistry: params.modelRegistry,
cfg: params.context.cfg,
agentDir: params.context.agentDir,
})
: undefined;
const rowModel =
resolvedModel && modelKey(resolvedModel.provider, resolvedModel.id) === key
? resolvedModel
: model;
appendVisibleRow({
await appendVisibleRow({
rows: params.rows,
model: rowModel,
key,
@@ -195,11 +300,11 @@ export function appendDiscoveredRows(params: {
return seenKeys;
}
export function appendConfiguredProviderRows(params: {
export async function appendConfiguredProviderRows(params: {
rows: ModelRow[];
context: RowBuilderContext;
seenKeys: Set<string>;
}): void {
}): Promise<void> {
for (const [provider, providerConfig] of Object.entries(
params.context.cfg.models?.providers ?? {},
)) {
@@ -213,7 +318,7 @@ export function appendConfiguredProviderRows(params: {
providerConfig,
model: configuredModel,
});
appendVisibleRow({
await appendVisibleRow({
rows: params.rows,
model,
key,
@@ -225,17 +330,17 @@ export function appendConfiguredProviderRows(params: {
}
}
export function appendModelCatalogRows(params: {
export async function appendModelCatalogRows(params: {
rows: ModelRow[];
context: RowBuilderContext;
seenKeys: Set<string>;
catalogRows: readonly NormalizedModelCatalogRow[];
}): number {
}): Promise<number> {
let appended = 0;
for (const catalogRow of params.catalogRows) {
const key = modelKey(catalogRow.provider, catalogRow.id);
if (
appendVisibleRow({
await appendVisibleRow({
rows: params.rows,
model: toManifestCatalogListModel(catalogRow),
key,
@@ -255,7 +360,7 @@ export function appendManifestCatalogRows(params: {
context: RowBuilderContext;
seenKeys: Set<string>;
manifestRows: readonly NormalizedModelCatalogRow[];
}): number {
}): Promise<number> {
return appendModelCatalogRows({
...params,
catalogRows: params.manifestRows,
@@ -268,6 +373,10 @@ export async function appendCatalogSupplementRows(params: {
context: RowBuilderContext;
seenKeys: Set<string>;
}): Promise<void> {
const [{ loadModelCatalog }, { resolveModelWithRegistry }] = await Promise.all([
loadModelCatalogModule(),
loadModelResolverModule(),
]);
const catalog = await loadModelCatalog({ config: params.context.cfg, readOnly: true });
for (const entry of catalog) {
if (
@@ -286,7 +395,7 @@ export async function appendCatalogSupplementRows(params: {
if (!model) {
continue;
}
appendVisibleRow({
await appendVisibleRow({
rows: params.rows,
model,
key,
@@ -314,6 +423,7 @@ export async function appendProviderCatalogRows(params: {
staticOnly?: boolean;
}): Promise<number> {
let appended = 0;
const { loadProviderCatalogModelsForList } = await loadProviderCatalogModule();
for (const model of await loadProviderCatalogModelsForList({
cfg: params.context.cfg,
agentDir: params.context.agentDir,
@@ -322,7 +432,7 @@ export async function appendProviderCatalogRows(params: {
})) {
const key = modelKey(model.provider, model.id);
if (
appendVisibleRow({
await appendVisibleRow({
rows: params.rows,
model,
key,
@@ -337,12 +447,15 @@ export async function appendProviderCatalogRows(params: {
return appended;
}
export function appendConfiguredRows(params: {
export async function appendConfiguredRows(params: {
rows: ModelRow[];
entries: ConfiguredEntry[];
modelRegistry: ModelRegistry;
modelRegistry?: ModelRegistry;
context: RowBuilderContext;
}) {
}): Promise<void> {
const resolveModelWithRegistry = params.modelRegistry
? (await loadModelResolverModule()).resolveModelWithRegistry
: undefined;
for (const entry of params.entries) {
if (
params.context.filter.provider &&
@@ -350,12 +463,15 @@ export function appendConfiguredRows(params: {
) {
continue;
}
const model = resolveModelWithRegistry({
provider: entry.ref.provider,
modelId: entry.ref.model,
modelRegistry: params.modelRegistry,
cfg: params.context.cfg,
});
const model =
params.modelRegistry && resolveModelWithRegistry
? resolveModelWithRegistry({
provider: entry.ref.provider,
modelId: entry.ref.model,
modelRegistry: params.modelRegistry,
cfg: params.context.cfg,
})
: toFallbackConfiguredListModel(entry, params.context.cfg);
if (params.context.filter.local && model && !isLocalBaseUrl(model.baseUrl ?? "")) {
continue;
}
@@ -365,6 +481,17 @@ export function appendConfiguredRows(params: {
if (model && shouldSuppressListModel({ model, context: params.context })) {
continue;
}
const shouldResolveProviderAuth =
model &&
(params.context.availableKeys === undefined ||
!params.context.discoveredKeys.has(modelKey(model.provider, model.id)));
const hasProviderAuth = shouldResolveProviderAuth
? await hasAuthForProvider({
provider: model.provider,
cfg: params.context.cfg,
authStore: params.context.authStore,
})
: false;
params.rows.push(
toModelRow({
model,
@@ -377,6 +504,7 @@ export function appendConfiguredRows(params: {
allowProviderAvailabilityFallback: model
? !params.context.discoveredKeys.has(modelKey(model.provider, model.id))
: false,
hasAuthForProvider: shouldResolveProviderAuth ? () => hasProviderAuth : undefined,
}),
);
}