mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:50:46 +00:00
fix: make models list read-only
This commit is contained in:
@@ -15,7 +15,10 @@ import {
|
||||
} from "../plugins/provider-runtime.js";
|
||||
import { resolveRuntimeSyntheticAuthProviderRefs } from "../plugins/synthetic-auth.runtime.js";
|
||||
import { isRecord } from "../utils.js";
|
||||
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
|
||||
import {
|
||||
ensureAuthProfileStore,
|
||||
loadAuthProfileStoreForSecretsRuntime,
|
||||
} from "./auth-profiles/store.js";
|
||||
import { resolveProviderEnvApiKeyCandidates } from "./model-auth-env-vars.js";
|
||||
import { resolveEnvApiKey } from "./model-auth-env.js";
|
||||
import { resolvePiCredentialMapFromStore, type PiCredentialMap } from "./pi-auth-credentials.js";
|
||||
@@ -277,8 +280,18 @@ export function addEnvBackedPiCredentials(
|
||||
return next;
|
||||
}
|
||||
|
||||
export function resolvePiCredentialsForDiscovery(agentDir: string): PiCredentialMap {
|
||||
const store = ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||
type DiscoverAuthStorageOptions = {
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
export function resolvePiCredentialsForDiscovery(
|
||||
agentDir: string,
|
||||
options?: DiscoverAuthStorageOptions,
|
||||
): PiCredentialMap {
|
||||
const store =
|
||||
options?.readOnly === true
|
||||
? loadAuthProfileStoreForSecretsRuntime(agentDir)
|
||||
: ensureAuthProfileStore(agentDir, { allowKeychainPrompt: false });
|
||||
const credentials = addEnvBackedPiCredentials(resolvePiCredentialMapFromStore(store));
|
||||
for (const provider of resolveRuntimeSyntheticAuthProviderRefs()) {
|
||||
if (credentials[provider]) {
|
||||
@@ -305,10 +318,15 @@ export function resolvePiCredentialsForDiscovery(agentDir: string): PiCredential
|
||||
}
|
||||
|
||||
// Compatibility helpers for pi-coding-agent 0.50+ (discover* helpers removed).
|
||||
export function discoverAuthStorage(agentDir: string): PiAuthStorage {
|
||||
const credentials = resolvePiCredentialsForDiscovery(agentDir);
|
||||
export function discoverAuthStorage(
|
||||
agentDir: string,
|
||||
options?: DiscoverAuthStorageOptions,
|
||||
): PiAuthStorage {
|
||||
const credentials = resolvePiCredentialsForDiscovery(agentDir, options);
|
||||
const authPath = path.join(agentDir, "auth.json");
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery(authPath);
|
||||
if (options?.readOnly !== true) {
|
||||
scrubLegacyStaticAuthJsonEntriesForDiscovery(authPath);
|
||||
}
|
||||
return createAuthStorage(PiAuthStorageClass, authPath, credentials);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { resolveConfiguredEntries } from "./list.configured.js";
|
||||
import { formatErrorWithStack } from "./list.errors.js";
|
||||
import {
|
||||
appendCatalogSupplementRows,
|
||||
appendConfiguredProviderRows,
|
||||
appendConfiguredRows,
|
||||
appendDiscoveredRows,
|
||||
appendProviderCatalogRows,
|
||||
@@ -47,9 +48,8 @@ export async function modelsListCommand(
|
||||
if (providerFilter === null) {
|
||||
return;
|
||||
}
|
||||
const { ensureAuthProfileStore, ensureOpenClawModelsJson, resolveOpenClawAgentDir } =
|
||||
await import("./list.runtime.js");
|
||||
const { sourceConfig, resolvedConfig: cfg } = await loadModelsConfigWithSource({
|
||||
const { ensureAuthProfileStore, resolveOpenClawAgentDir } = await import("./list.runtime.js");
|
||||
const { resolvedConfig: cfg } = await loadModelsConfigWithSource({
|
||||
commandName: "models list",
|
||||
runtime,
|
||||
});
|
||||
@@ -62,11 +62,8 @@ export async function modelsListCommand(
|
||||
let availabilityErrorMessage: string | undefined;
|
||||
const useProviderCatalogFastPath = Boolean(opts.all && providerFilter === "codex");
|
||||
try {
|
||||
// Keep command behavior explicit: sync models.json from the source config
|
||||
// before building the read-only model registry view.
|
||||
if (!useProviderCatalogFastPath) {
|
||||
await ensureOpenClawModelsJson(sourceConfig ?? cfg);
|
||||
const loaded = await loadListModelRegistry(cfg, { sourceConfig, providerFilter });
|
||||
const loaded = await loadListModelRegistry(cfg, { providerFilter });
|
||||
modelRegistry = loaded.registry;
|
||||
discoveredKeys = loaded.discoveredKeys;
|
||||
availableKeys = loaded.availableKeys;
|
||||
@@ -107,6 +104,14 @@ export async function modelsListCommand(
|
||||
context: rowContext,
|
||||
});
|
||||
|
||||
if (providerFilter) {
|
||||
appendConfiguredProviderRows({
|
||||
rows,
|
||||
context: rowContext,
|
||||
seenKeys,
|
||||
});
|
||||
}
|
||||
|
||||
if (modelRegistry) {
|
||||
await appendCatalogSupplementRows({
|
||||
rows,
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
|
||||
import { modelKey } from "../../agents/model-ref-shared.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { isLocalBaseUrl } from "./list.local-url.js";
|
||||
import type { ModelRow } from "./list.types.js";
|
||||
|
||||
export type ListRowModel = {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
input: Array<"text" | "image">;
|
||||
baseUrl?: string;
|
||||
contextWindow?: number | null;
|
||||
};
|
||||
|
||||
export type ModelAuthAvailabilityResolver = (params: {
|
||||
provider: string;
|
||||
cfg: OpenClawConfig;
|
||||
@@ -18,7 +26,7 @@ function authStoreHasProviderProfile(authStore: AuthProfileStore, provider: stri
|
||||
}
|
||||
|
||||
export function toModelRow(params: {
|
||||
model?: Model<Api>;
|
||||
model?: ListRowModel;
|
||||
key: string;
|
||||
tags: string[];
|
||||
aliases?: string[];
|
||||
@@ -52,7 +60,7 @@ export function toModelRow(params: {
|
||||
}
|
||||
|
||||
const input = model.input.join("+") || "text";
|
||||
const local = isLocalBaseUrl(model.baseUrl);
|
||||
const local = isLocalBaseUrl(model.baseUrl ?? "");
|
||||
const modelIsAvailable = availableKeys?.has(modelKey(model.provider, model.id)) ?? false;
|
||||
// Prefer model-level registry availability when present.
|
||||
// Fall back to provider-level auth heuristics only if registry availability isn't available,
|
||||
|
||||
@@ -108,12 +108,9 @@ function loadAvailableModels(registry: ModelRegistry, cfg: OpenClawConfig): Mode
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadModelRegistry(
|
||||
cfg: OpenClawConfig,
|
||||
opts?: { sourceConfig?: OpenClawConfig; providerFilter?: string },
|
||||
) {
|
||||
export async function loadModelRegistry(cfg: OpenClawConfig, opts?: { providerFilter?: string }) {
|
||||
const agentDir = resolveOpenClawAgentDir();
|
||||
const authStorage = discoverAuthStorage(agentDir);
|
||||
const authStorage = discoverAuthStorage(agentDir, { readOnly: true });
|
||||
const registry = discoverModels(authStorage, agentDir, {
|
||||
providerFilter: opts?.providerFilter,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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 { 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 { ListRowModel } from "./list.model-row.js";
|
||||
import { loadModelRegistry, toModelRow } from "./list.registry.js";
|
||||
import {
|
||||
loadModelCatalog,
|
||||
@@ -42,7 +45,7 @@ function matchesRowFilter(filter: RowFilter, model: { provider: string; baseUrl?
|
||||
}
|
||||
|
||||
function buildRow(params: {
|
||||
model: Model<Api>;
|
||||
model: ListRowModel;
|
||||
key: string;
|
||||
context: RowBuilderContext;
|
||||
allowProviderAvailabilityFallback?: boolean;
|
||||
@@ -75,9 +78,35 @@ function shouldSuppressListModel(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function resolveConfiguredModelInput(params: {
|
||||
model: Partial<ModelDefinitionConfig>;
|
||||
}): Array<"text" | "image"> {
|
||||
const input = Array.isArray(params.model.input)
|
||||
? params.model.input.filter(
|
||||
(item): item is "text" | "image" => item === "text" || item === "image",
|
||||
)
|
||||
: [];
|
||||
return input.length > 0 ? input : ["text"];
|
||||
}
|
||||
|
||||
function toConfiguredProviderListModel(params: {
|
||||
provider: string;
|
||||
providerConfig: Partial<ModelProviderConfig>;
|
||||
model: Partial<ModelDefinitionConfig> & Pick<ModelDefinitionConfig, "id">;
|
||||
}): ListRowModel {
|
||||
return {
|
||||
provider: params.provider,
|
||||
id: params.model.id,
|
||||
name: params.model.name ?? params.model.id,
|
||||
baseUrl: params.model.baseUrl ?? params.providerConfig.baseUrl,
|
||||
input: resolveConfiguredModelInput({ model: params.model }),
|
||||
contextWindow: params.model.contextWindow ?? DEFAULT_CONTEXT_TOKENS,
|
||||
};
|
||||
}
|
||||
|
||||
export async function loadListModelRegistry(
|
||||
cfg: OpenClawConfig,
|
||||
opts?: { sourceConfig?: OpenClawConfig; providerFilter?: string },
|
||||
opts?: { providerFilter?: string },
|
||||
) {
|
||||
const loaded = await loadModelRegistry(cfg, opts);
|
||||
return {
|
||||
@@ -121,6 +150,43 @@ export function appendDiscoveredRows(params: {
|
||||
return seenKeys;
|
||||
}
|
||||
|
||||
export function appendConfiguredProviderRows(params: {
|
||||
rows: ModelRow[];
|
||||
context: RowBuilderContext;
|
||||
seenKeys: Set<string>;
|
||||
}): void {
|
||||
for (const [provider, providerConfig] of Object.entries(
|
||||
params.context.cfg.models?.providers ?? {},
|
||||
)) {
|
||||
for (const configuredModel of providerConfig.models ?? []) {
|
||||
const key = modelKey(provider, configuredModel.id);
|
||||
if (params.seenKeys.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const model = toConfiguredProviderListModel({
|
||||
provider,
|
||||
providerConfig,
|
||||
model: configuredModel,
|
||||
});
|
||||
if (!matchesRowFilter(params.context.filter, model)) {
|
||||
continue;
|
||||
}
|
||||
if (shouldSuppressListModel({ model, context: params.context })) {
|
||||
continue;
|
||||
}
|
||||
params.rows.push(
|
||||
buildRow({
|
||||
model,
|
||||
key,
|
||||
context: params.context,
|
||||
allowProviderAvailabilityFallback: !params.context.discoveredKeys.has(key),
|
||||
}),
|
||||
);
|
||||
params.seenKeys.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function appendCatalogSupplementRows(params: {
|
||||
rows: ModelRow[];
|
||||
modelRegistry: ModelRegistry;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export { ensureAuthProfileStoreWithoutExternalProfiles as ensureAuthProfileStore } from "../../agents/auth-profiles/store.js";
|
||||
export { ensureOpenClawModelsJson } from "../../agents/models-config.js";
|
||||
export { loadAuthProfileStoreWithoutExternalProfiles as ensureAuthProfileStore } from "../../agents/auth-profiles/store.js";
|
||||
export { resolveOpenClawAgentDir } from "../../agents/agent-paths.js";
|
||||
export { listProfilesForProvider } from "../../agents/auth-profiles.js";
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user