mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-22 10:25:20 -06:00
fix(cli): infer provider lists respect selected agent (#123884)
* fix(cli): require inference provider owners * test(cli): type provider owner fixtures * chore: leave infer release note to release
This commit is contained in:
committed by
GitHub
parent
722e1ff48e
commit
72e67904be
@@ -16,6 +16,7 @@ import {
|
||||
providerHasGenericConfig,
|
||||
providerSummaryText,
|
||||
requireProviderModelOverride,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -80,10 +81,12 @@ export function registerAudioCapabilityCommands(capability: Command): void {
|
||||
audio
|
||||
.command("providers")
|
||||
.description("List audio transcription providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const cfg = getRuntimeConfig();
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, opts.agent as string | undefined);
|
||||
const remoteProviders = [...buildMediaUnderstandingRegistry(undefined, cfg).values()]
|
||||
.filter((provider) => provider.capabilities?.includes("audio"))
|
||||
.map((provider) => ({
|
||||
@@ -91,6 +94,7 @@ export function registerAudioCapabilityCommands(capability: Command): void {
|
||||
configured: providerHasGenericConfig({
|
||||
cfg,
|
||||
providerId: provider.id,
|
||||
agentId,
|
||||
envVars: getProviderEnvVars(provider.id, {
|
||||
config: cfg,
|
||||
includeUntrustedWorkspacePlugins: false,
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
providerHasGenericConfig,
|
||||
providerSummaryText,
|
||||
requireProviderModelOverride,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -122,11 +123,12 @@ export function registerEmbeddingCapabilityCommands(capability: Command): void {
|
||||
embedding
|
||||
.command("providers")
|
||||
.description("List embedding providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const cfg = getRuntimeConfig();
|
||||
const agentId = resolveDefaultAgentId(cfg);
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, opts.agent as string | undefined);
|
||||
const resolvedMemory = resolveMemorySearchConfig(cfg, agentId);
|
||||
const selectedProvider = resolvedMemory?.provider;
|
||||
const providers = new Map(
|
||||
@@ -155,7 +157,7 @@ export function registerEmbeddingCapabilityCommands(capability: Command): void {
|
||||
providers.set(selectedProvider, {
|
||||
id: selectedProvider,
|
||||
defaultModel: resolvedMemory?.model || undefined,
|
||||
transport: providerHasGenericConfig({ cfg, providerId: selectedProvider })
|
||||
transport: providerHasGenericConfig({ cfg, providerId: selectedProvider, agentId })
|
||||
? "remote"
|
||||
: undefined,
|
||||
autoSelectPriority: undefined,
|
||||
@@ -168,6 +170,7 @@ export function registerEmbeddingCapabilityCommands(capability: Command): void {
|
||||
providerHasGenericConfig({
|
||||
cfg,
|
||||
providerId: provider.id,
|
||||
agentId,
|
||||
}),
|
||||
selected: provider.id === selectedProvider,
|
||||
id: provider.id,
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
providerHasGenericConfig,
|
||||
providerSummaryText,
|
||||
requireProviderModelOverride,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
resolveSelectedProviderFromModelRef,
|
||||
} from "./shared.js";
|
||||
@@ -407,10 +408,12 @@ export function registerImageCapabilityCommands(capability: Command): void {
|
||||
image
|
||||
.command("providers")
|
||||
.description("List image generation providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const cfg = getRuntimeConfig();
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, opts.agent as string | undefined);
|
||||
const selectedProvider = resolveSelectedProviderFromModelRef(
|
||||
resolveAgentModelPrimaryValue(cfg.agents?.defaults?.mediaModels?.image),
|
||||
);
|
||||
@@ -418,7 +421,7 @@ export function registerImageCapabilityCommands(capability: Command): void {
|
||||
available: true,
|
||||
configured:
|
||||
selectedProvider === provider.id ||
|
||||
providerHasGenericConfig({ cfg, providerId: provider.id }),
|
||||
providerHasGenericConfig({ cfg, providerId: provider.id, agentId }),
|
||||
selected: selectedProvider === provider.id,
|
||||
id: provider.id,
|
||||
label: provider.label,
|
||||
|
||||
@@ -47,7 +47,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "model.providers",
|
||||
description: "List model providers discovered from the catalog.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids with counts and defaults",
|
||||
},
|
||||
{
|
||||
@@ -134,7 +134,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "image.providers",
|
||||
description: "List image generation providers.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids and defaults",
|
||||
},
|
||||
{
|
||||
@@ -148,7 +148,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "audio.providers",
|
||||
description: "List audio transcription providers.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids and capabilities",
|
||||
},
|
||||
{
|
||||
@@ -179,7 +179,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "tts.providers",
|
||||
description: "List speech providers.",
|
||||
transports: ["local", "gateway"],
|
||||
flags: ["--local", "--gateway", "--json"],
|
||||
flags: ["--agent", "--local", "--gateway", "--json"],
|
||||
resultShape: "provider ids, configured state, models, voices",
|
||||
},
|
||||
{
|
||||
@@ -254,7 +254,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "video.providers",
|
||||
description: "List video generation and description providers.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids and defaults",
|
||||
},
|
||||
{
|
||||
@@ -275,7 +275,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "web.providers",
|
||||
description: "List web search and fetch providers.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids grouped by family",
|
||||
},
|
||||
{
|
||||
@@ -289,7 +289,7 @@ export const CAPABILITY_METADATA: CapabilityMetadata[] = [
|
||||
id: "embedding.providers",
|
||||
description: "List embedding providers.",
|
||||
transports: ["local"],
|
||||
flags: ["--json"],
|
||||
flags: ["--agent", "--json"],
|
||||
resultShape: "provider ids and default models",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../../../packages/gateway-protocol/src/client-info.js";
|
||||
import { resolveAgentDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import {
|
||||
resolveAgentDir,
|
||||
resolveAgentEffectiveModelPrimary,
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
import {
|
||||
listProfilesForProvider,
|
||||
loadAuthProfileStoreForRuntime,
|
||||
@@ -27,7 +31,6 @@ import {
|
||||
} from "../../agents/simple-completion-runtime.js";
|
||||
import { normalizeThinkLevel, type ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import { getRuntimeConfig } from "../../config/config.js";
|
||||
import { resolveAgentModelPrimaryValue } from "../../config/model-input.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { callGateway, randomIdempotencyKey } from "../../gateway/call.js";
|
||||
import { ADMIN_SCOPE } from "../../gateway/operator-scopes.js";
|
||||
@@ -46,6 +49,7 @@ import {
|
||||
providerHasGenericConfig,
|
||||
providerSummaryText,
|
||||
requireProviderModelOverride,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
resolveSelectedProviderFromModelRef,
|
||||
resolveTransport,
|
||||
@@ -54,8 +58,8 @@ import {
|
||||
const LOCAL_MODEL_RUN_SYSTEM_PROMPT = "You are a personal assistant running inside OpenClaw.";
|
||||
const HEIC_MODEL_RUN_MIMES = new Set(["image/heic", "image/heif"]);
|
||||
|
||||
async function loadModelCatalogForInspection(cfg: OpenClawConfig) {
|
||||
const prepared = await loadPreparedModelCatalog({ config: cfg, readOnly: true });
|
||||
async function loadModelCatalogForInspection(cfg: OpenClawConfig, agentId?: string) {
|
||||
const prepared = await loadPreparedModelCatalog({ config: cfg, agentId, readOnly: true });
|
||||
const metadataSnapshot = loadManifestMetadataSnapshot({ config: cfg, env: process.env });
|
||||
const manifest = planEffectiveModelCatalogRows({
|
||||
registry: metadataSnapshot.manifestRegistry,
|
||||
@@ -342,11 +346,12 @@ async function runModelRun(params: {
|
||||
} satisfies CapabilityEnvelope;
|
||||
}
|
||||
|
||||
async function buildModelProviders() {
|
||||
async function buildModelProviders(rawAgentId?: string) {
|
||||
const cfg = getRuntimeConfig();
|
||||
const catalog = await loadModelCatalogForInspection(cfg);
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, rawAgentId);
|
||||
const catalog = await loadModelCatalogForInspection(cfg, agentId);
|
||||
const selectedProvider = resolveSelectedProviderFromModelRef(
|
||||
resolveAgentModelPrimaryValue(cfg.agents?.defaults?.model),
|
||||
resolveAgentEffectiveModelPrimary(cfg, agentId),
|
||||
);
|
||||
const grouped = new Map<
|
||||
string,
|
||||
@@ -368,6 +373,7 @@ async function buildModelProviders() {
|
||||
configured: providerHasGenericConfig({
|
||||
cfg,
|
||||
providerId: entry.provider,
|
||||
agentId,
|
||||
envVars: getProviderEnvVars(entry.provider),
|
||||
}),
|
||||
selected: selectedProvider === entry.provider,
|
||||
@@ -511,10 +517,11 @@ export function registerModelCapabilityCommands(capability: Command): void {
|
||||
model
|
||||
.command("providers")
|
||||
.description("List model providers from the catalog")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const result = await buildModelProviders();
|
||||
const result = await buildModelProviders(opts.agent as string | undefined);
|
||||
emitJsonOrText(defaultRuntime, Boolean(opts.json), result, providerSummaryText);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,7 +2,8 @@ import {
|
||||
parseStrictFiniteNumber,
|
||||
parseStrictPositiveInteger,
|
||||
} from "@openclaw/normalization-core/number-coercion";
|
||||
import { resolveAgentDir, resolveDefaultAgentId } from "../../agents/agent-scope.js";
|
||||
import { listAgentIds, resolveSystemAgentTargetAgentId } from "../../agents/agent-scope-config.js";
|
||||
import { resolveAgentDir } from "../../agents/agent-scope.js";
|
||||
import {
|
||||
listProfilesForProvider,
|
||||
loadAuthProfileStoreForRuntime,
|
||||
@@ -101,8 +102,32 @@ export function resolveSelectedProviderFromModelRef(
|
||||
return resolveModelRefOverride(modelRef).provider;
|
||||
}
|
||||
|
||||
function getAuthProfileIdsForProvider(cfg: OpenClawConfig, providerId: string): string[] {
|
||||
const agentDir = resolveAgentDir(cfg, resolveDefaultAgentId(cfg));
|
||||
export function resolveCapabilityProviderAgentId(
|
||||
cfg: OpenClawConfig,
|
||||
rawAgentId: string | undefined,
|
||||
): string {
|
||||
const requestedAgentId = rawAgentId?.trim();
|
||||
if (rawAgentId !== undefined && !requestedAgentId) {
|
||||
throw new Error("--agent must not be blank");
|
||||
}
|
||||
const agentId = resolveSystemAgentTargetAgentId(cfg, requestedAgentId, {
|
||||
surface: "inference provider inspection",
|
||||
hint: "Pass --agent <id> or set agents.defaults.systemAgent.agentId.",
|
||||
});
|
||||
if (!listAgentIds(cfg).includes(agentId)) {
|
||||
throw new Error(
|
||||
`Unknown agent id "${agentId}". Run \`openclaw agents list\` to see configured agents.`,
|
||||
);
|
||||
}
|
||||
return agentId;
|
||||
}
|
||||
|
||||
function getAuthProfileIdsForProvider(
|
||||
cfg: OpenClawConfig,
|
||||
providerId: string,
|
||||
agentId: string,
|
||||
): string[] {
|
||||
const agentDir = resolveAgentDir(cfg, agentId);
|
||||
const store = loadAuthProfileStoreForRuntime(agentDir);
|
||||
return listProfilesForProvider(store, providerId);
|
||||
}
|
||||
@@ -110,6 +135,8 @@ function getAuthProfileIdsForProvider(cfg: OpenClawConfig, providerId: string):
|
||||
export function providerHasGenericConfig(params: {
|
||||
cfg: OpenClawConfig;
|
||||
providerId: string;
|
||||
/** Omit only for aggregate/global callers that intentionally exclude agent auth stores. */
|
||||
agentId?: string;
|
||||
envVars?: string[];
|
||||
}): boolean {
|
||||
const modelsProviders = (params.cfg.models?.providers ?? {}) as Record<string, unknown>;
|
||||
@@ -123,7 +150,9 @@ export function providerHasGenericConfig(params: {
|
||||
});
|
||||
const envConfigured = envVars.some((envVar) => Boolean(process.env[envVar]?.trim()));
|
||||
return (
|
||||
getAuthProfileIdsForProvider(params.cfg, params.providerId).length > 0 ||
|
||||
(params.agentId
|
||||
? getAuthProfileIdsForProvider(params.cfg, params.providerId, params.agentId).length > 0
|
||||
: false) ||
|
||||
hasOwnKeys(modelsProviders[params.providerId]) ||
|
||||
hasOwnKeys(pluginEntries[params.providerId]?.config) ||
|
||||
hasOwnKeys(ttsProviders[params.providerId]) ||
|
||||
|
||||
@@ -31,6 +31,7 @@ import type { CapabilityEnvelope, CapabilityTransport } from "./metadata.js";
|
||||
import {
|
||||
pinRuntimeConfigSnapshot,
|
||||
providerHasGenericConfig,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
resolveSelectedProviderFromModelRef,
|
||||
} from "./shared.js";
|
||||
@@ -413,9 +414,12 @@ function resolvedTtsConfigHasProviderApiKey(config: unknown, providerId: string)
|
||||
return ttsProviderConfigHasApiKey(config.providerConfigs[providerId]);
|
||||
}
|
||||
|
||||
export async function runTtsProviders(transport: CapabilityTransport) {
|
||||
export async function runTtsProviders(transport: CapabilityTransport, rawAgentId?: string) {
|
||||
const cfg = getRuntimeConfig();
|
||||
if (transport === "gateway") {
|
||||
if (rawAgentId !== undefined) {
|
||||
throw new Error("--agent is only supported with local TTS provider inspection.");
|
||||
}
|
||||
const payload: {
|
||||
providers?: Array<Record<string, unknown>>;
|
||||
active?: string;
|
||||
@@ -441,6 +445,7 @@ export async function runTtsProviders(transport: CapabilityTransport) {
|
||||
}),
|
||||
};
|
||||
}
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, rawAgentId);
|
||||
const config = resolveTtsConfig(cfg);
|
||||
const prefsPath = resolveTtsPrefsPath(config);
|
||||
const active = getTtsProvider(config, prefsPath);
|
||||
@@ -448,7 +453,8 @@ export async function runTtsProviders(transport: CapabilityTransport) {
|
||||
providers: listSpeechProviders(cfg).map((provider) => ({
|
||||
available: true,
|
||||
configured:
|
||||
active === provider.id || providerHasGenericConfig({ cfg, providerId: provider.id }),
|
||||
active === provider.id ||
|
||||
providerHasGenericConfig({ cfg, providerId: provider.id, agentId }),
|
||||
selected: active === provider.id,
|
||||
id: provider.id,
|
||||
name: provider.label,
|
||||
|
||||
@@ -95,16 +95,20 @@ export function registerTtsCapabilityCommands(capability: Command): void {
|
||||
});
|
||||
});
|
||||
|
||||
for (const [name, description, run] of [
|
||||
["providers", "List speech providers", runTtsProviders],
|
||||
["personas", "List TTS personas", runTtsPersonas],
|
||||
] as const) {
|
||||
registerTransportTtsCommand(
|
||||
tts.command(name).description(description),
|
||||
"local",
|
||||
(_, transport) => run(transport),
|
||||
);
|
||||
}
|
||||
registerTransportTtsCommand(
|
||||
tts
|
||||
.command("providers")
|
||||
.description("List speech providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected"),
|
||||
"local",
|
||||
(opts, transport) => runTtsProviders(transport, opts.agent as string | undefined),
|
||||
);
|
||||
|
||||
registerTransportTtsCommand(
|
||||
tts.command("personas").description("List TTS personas"),
|
||||
"local",
|
||||
(_, transport) => runTtsPersonas(transport),
|
||||
);
|
||||
|
||||
tts
|
||||
.command("status")
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
parseOptionalTimeoutMs,
|
||||
providerHasGenericConfig,
|
||||
requireProviderModelOverride,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
resolveSelectedProviderFromModelRef,
|
||||
} from "./shared.js";
|
||||
@@ -310,10 +311,12 @@ export function registerVideoCapabilityCommands(capability: Command): void {
|
||||
video
|
||||
.command("providers")
|
||||
.description("List video generation and description providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const cfg = getRuntimeConfig();
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, opts.agent as string | undefined);
|
||||
const selectedGenerationProvider = resolveSelectedProviderFromModelRef(
|
||||
resolveAgentModelPrimaryValue(cfg.agents?.defaults?.mediaModels?.video),
|
||||
);
|
||||
@@ -322,7 +325,7 @@ export function registerVideoCapabilityCommands(capability: Command): void {
|
||||
available: true,
|
||||
configured:
|
||||
selectedGenerationProvider === provider.id ||
|
||||
providerHasGenericConfig({ cfg, providerId: provider.id }),
|
||||
providerHasGenericConfig({ cfg, providerId: provider.id, agentId }),
|
||||
selected: selectedGenerationProvider === provider.id,
|
||||
id: provider.id,
|
||||
label: provider.label,
|
||||
@@ -334,7 +337,7 @@ export function registerVideoCapabilityCommands(capability: Command): void {
|
||||
.filter((provider) => provider.capabilities?.includes("video"))
|
||||
.map((provider) => ({
|
||||
available: true,
|
||||
configured: providerHasGenericConfig({ cfg, providerId: provider.id }),
|
||||
configured: providerHasGenericConfig({ cfg, providerId: provider.id, agentId }),
|
||||
selected: false,
|
||||
id: provider.id,
|
||||
capabilities: provider.capabilities,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
import type { Command } from "commander";
|
||||
import { resolveAgentDir } from "../../agents/agent-scope.js";
|
||||
import { getRuntimeConfig } from "../../config/config.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import {
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
emitJsonOrText,
|
||||
formatEnvelopeForText,
|
||||
parseOptionalPositiveInteger,
|
||||
resolveCapabilityProviderAgentId,
|
||||
resolveLocalCapabilityRuntimeConfig,
|
||||
} from "./shared.js";
|
||||
|
||||
@@ -178,10 +180,13 @@ export function registerWebCapabilityCommands(capability: Command): void {
|
||||
web
|
||||
.command("providers")
|
||||
.description("List web providers")
|
||||
.option("--agent <id>", "Agent whose provider state should be inspected")
|
||||
.option("--json", "Output JSON", false)
|
||||
.action(async (opts) => {
|
||||
await runCommandWithRuntime(defaultRuntime, async () => {
|
||||
const cfg = getRuntimeConfig();
|
||||
const agentId = resolveCapabilityProviderAgentId(cfg, opts.agent as string | undefined);
|
||||
const agentDir = resolveAgentDir(cfg, agentId);
|
||||
const selectedSearchProvider =
|
||||
typeof cfg.tools?.web?.search?.provider === "string"
|
||||
? normalizeLowercaseStringOrEmpty(cfg.tools.web.search.provider)
|
||||
@@ -193,7 +198,7 @@ export function registerWebCapabilityCommands(capability: Command): void {
|
||||
const result = {
|
||||
search: listWebSearchProviders({ config: cfg }).map((provider) => ({
|
||||
available: true,
|
||||
configured: isWebSearchProviderConfigured({ provider, config: cfg }),
|
||||
configured: isWebSearchProviderConfigured({ provider, config: cfg, agentDir }),
|
||||
selected: provider.id === selectedSearchProvider,
|
||||
id: provider.id,
|
||||
envVars: provider.envVars,
|
||||
|
||||
Reference in New Issue
Block a user