mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 21:10:43 +00:00
test: lighten fast lane imports
This commit is contained in:
155
src/cli/run-main-policy.ts
Normal file
155
src/cli/run-main-policy.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
||||||
|
import {
|
||||||
|
resolveManifestCommandAliasOwnerInRegistry,
|
||||||
|
type PluginManifestCommandAliasRecord,
|
||||||
|
type PluginManifestCommandAliasRegistry,
|
||||||
|
} from "../plugins/manifest-command-aliases.js";
|
||||||
|
import {
|
||||||
|
normalizeLowercaseStringOrEmpty,
|
||||||
|
normalizeOptionalLowercaseString,
|
||||||
|
} from "../shared/string-coerce.js";
|
||||||
|
import { resolveCliArgvInvocation } from "./argv-invocation.js";
|
||||||
|
import { resolveCliCommandPathPolicy } from "./command-path-policy.js";
|
||||||
|
|
||||||
|
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
|
||||||
|
const index = argv.indexOf("--update");
|
||||||
|
if (index === -1) {
|
||||||
|
return argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = [...argv];
|
||||||
|
next.splice(index, 1, "update");
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldEnsureCliPath(argv: string[]): boolean {
|
||||||
|
const invocation = resolveCliArgvInvocation(argv);
|
||||||
|
if (invocation.hasHelpOrVersion || shouldStartCrestodianForBareRoot(argv)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return resolveCliCommandPathPolicy(invocation.commandPath).ensureCliPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldUseRootHelpFastPath(
|
||||||
|
argv: string[],
|
||||||
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
|
): boolean {
|
||||||
|
const invocation = resolveCliArgvInvocation(argv);
|
||||||
|
return (
|
||||||
|
env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH !== "1" &&
|
||||||
|
(invocation.isRootHelpInvocation ||
|
||||||
|
(invocation.commandPath.length === 1 &&
|
||||||
|
invocation.commandPath[0] === "help" &&
|
||||||
|
invocation.hasHelpOrVersion))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldUseBrowserHelpFastPath(
|
||||||
|
argv: string[],
|
||||||
|
env: NodeJS.ProcessEnv = process.env,
|
||||||
|
): boolean {
|
||||||
|
if (env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH === "1") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const invocation = resolveCliArgvInvocation(argv);
|
||||||
|
return (
|
||||||
|
invocation.commandPath.length === 1 &&
|
||||||
|
invocation.commandPath[0] === "browser" &&
|
||||||
|
invocation.hasHelpOrVersion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldStartCrestodianForBareRoot(argv: string[]): boolean {
|
||||||
|
const invocation = resolveCliArgvInvocation(argv);
|
||||||
|
return invocation.commandPath.length === 0 && !invocation.hasHelpOrVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shouldStartCrestodianForModernOnboard(argv: string[]): boolean {
|
||||||
|
const invocation = resolveCliArgvInvocation(argv);
|
||||||
|
return (
|
||||||
|
invocation.commandPath[0] === "onboard" &&
|
||||||
|
argv.includes("--modern") &&
|
||||||
|
!invocation.hasHelpOrVersion
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveMissingPluginCommandMessage(
|
||||||
|
pluginId: string,
|
||||||
|
config?: OpenClawConfig,
|
||||||
|
options?: {
|
||||||
|
registry?: PluginManifestCommandAliasRegistry;
|
||||||
|
resolveCommandAliasOwner?: (params: {
|
||||||
|
command: string | undefined;
|
||||||
|
config?: OpenClawConfig;
|
||||||
|
registry?: PluginManifestCommandAliasRegistry;
|
||||||
|
}) => PluginManifestCommandAliasRecord | undefined;
|
||||||
|
},
|
||||||
|
): string | null {
|
||||||
|
const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId);
|
||||||
|
if (!normalizedPluginId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const allow =
|
||||||
|
Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0
|
||||||
|
? config.plugins.allow
|
||||||
|
.filter((entry): entry is string => typeof entry === "string")
|
||||||
|
.map((entry) => normalizeOptionalLowercaseString(entry))
|
||||||
|
.filter(Boolean)
|
||||||
|
: [];
|
||||||
|
const commandAlias = options?.registry
|
||||||
|
? resolveManifestCommandAliasOwnerInRegistry({
|
||||||
|
command: normalizedPluginId,
|
||||||
|
registry: options.registry,
|
||||||
|
})
|
||||||
|
: options?.resolveCommandAliasOwner?.({
|
||||||
|
command: normalizedPluginId,
|
||||||
|
config,
|
||||||
|
...(options?.registry ? { registry: options.registry } : {}),
|
||||||
|
});
|
||||||
|
const parentPluginId = commandAlias?.pluginId;
|
||||||
|
if (parentPluginId) {
|
||||||
|
if (allow.length > 0 && !allow.includes(parentPluginId)) {
|
||||||
|
return (
|
||||||
|
`"${normalizedPluginId}" is not a plugin; it is a command provided by the ` +
|
||||||
|
`"${parentPluginId}" plugin. Add "${parentPluginId}" to \`plugins.allow\` ` +
|
||||||
|
`instead of "${normalizedPluginId}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (config?.plugins?.entries?.[parentPluginId]?.enabled === false) {
|
||||||
|
return (
|
||||||
|
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
||||||
|
`\`plugins.entries.${parentPluginId}.enabled=false\`. Re-enable that entry if you want ` +
|
||||||
|
"the bundled plugin command surface."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (commandAlias.kind === "runtime-slash") {
|
||||||
|
const cliHint = commandAlias.cliCommand
|
||||||
|
? `Use \`openclaw ${commandAlias.cliCommand}\` for related CLI operations, or `
|
||||||
|
: "Use ";
|
||||||
|
return (
|
||||||
|
`"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` +
|
||||||
|
`It is provided by the "${parentPluginId}" plugin. ` +
|
||||||
|
`${cliHint}\`/${normalizedPluginId}\` in a chat session.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allow.length > 0 && !allow.includes(normalizedPluginId)) {
|
||||||
|
if (parentPluginId && allow.includes(parentPluginId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
||||||
|
`\`plugins.allow\` excludes "${normalizedPluginId}". Add "${normalizedPluginId}" to ` +
|
||||||
|
`\`plugins.allow\` if you want that bundled plugin CLI surface.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (config?.plugins?.entries?.[normalizedPluginId]?.enabled === false) {
|
||||||
|
return (
|
||||||
|
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
||||||
|
`\`plugins.entries.${normalizedPluginId}.enabled=false\`. Re-enable that entry if you want ` +
|
||||||
|
"the bundled plugin CLI surface."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import type { PluginManifestRegistry } from "../plugins/manifest-registry.js";
|
import type { PluginManifestCommandAliasRegistry } from "../plugins/manifest-command-aliases.js";
|
||||||
import {
|
import {
|
||||||
rewriteUpdateFlagArgv,
|
rewriteUpdateFlagArgv,
|
||||||
resolveMissingPluginCommandMessage,
|
resolveMissingPluginCommandMessage,
|
||||||
@@ -8,44 +8,24 @@ import {
|
|||||||
shouldStartCrestodianForModernOnboard,
|
shouldStartCrestodianForModernOnboard,
|
||||||
shouldUseBrowserHelpFastPath,
|
shouldUseBrowserHelpFastPath,
|
||||||
shouldUseRootHelpFastPath,
|
shouldUseRootHelpFastPath,
|
||||||
} from "./run-main.js";
|
} from "./run-main-policy.js";
|
||||||
|
|
||||||
const memoryWikiCommandAliasRegistry: PluginManifestRegistry = {
|
const memoryWikiCommandAliasRegistry: PluginManifestCommandAliasRegistry = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
id: "memory-wiki",
|
id: "memory-wiki",
|
||||||
channels: [],
|
|
||||||
providers: [],
|
|
||||||
cliBackends: [],
|
|
||||||
skills: [],
|
|
||||||
hooks: [],
|
|
||||||
origin: "bundled",
|
|
||||||
rootDir: "/tmp/memory-wiki",
|
|
||||||
source: "bundled",
|
|
||||||
manifestPath: "/tmp/memory-wiki/openclaw.plugin.json",
|
|
||||||
commandAliases: [{ name: "wiki" }],
|
commandAliases: [{ name: "wiki" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
diagnostics: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const memoryCoreCommandAliasRegistry: PluginManifestRegistry = {
|
const memoryCoreCommandAliasRegistry: PluginManifestCommandAliasRegistry = {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
id: "memory-core",
|
id: "memory-core",
|
||||||
channels: [],
|
|
||||||
providers: [],
|
|
||||||
cliBackends: [],
|
|
||||||
skills: [],
|
|
||||||
hooks: [],
|
|
||||||
origin: "bundled",
|
|
||||||
rootDir: "/tmp/memory-core",
|
|
||||||
source: "bundled",
|
|
||||||
manifestPath: "/tmp/memory-core/openclaw.plugin.json",
|
|
||||||
commandAliases: [{ name: "dreaming", kind: "runtime-slash", cliCommand: "memory" }],
|
commandAliases: [{ name: "dreaming", kind: "runtime-slash", cliCommand: "memory" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
diagnostics: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("rewriteUpdateFlagArgv", () => {
|
describe("rewriteUpdateFlagArgv", () => {
|
||||||
|
|||||||
@@ -20,23 +20,36 @@ import {
|
|||||||
finalizeDebugProxyCapture,
|
finalizeDebugProxyCapture,
|
||||||
initializeDebugProxyCapture,
|
initializeDebugProxyCapture,
|
||||||
} from "../proxy-capture/runtime.js";
|
} from "../proxy-capture/runtime.js";
|
||||||
import {
|
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
||||||
normalizeLowercaseStringOrEmpty,
|
|
||||||
normalizeOptionalLowercaseString,
|
|
||||||
normalizeOptionalString,
|
|
||||||
} from "../shared/string-coerce.js";
|
|
||||||
import { resolveCliArgvInvocation } from "./argv-invocation.js";
|
import { resolveCliArgvInvocation } from "./argv-invocation.js";
|
||||||
import {
|
import {
|
||||||
shouldRegisterPrimaryCommandOnly,
|
shouldRegisterPrimaryCommandOnly,
|
||||||
shouldSkipPluginCommandRegistration,
|
shouldSkipPluginCommandRegistration,
|
||||||
} from "./command-registration-policy.js";
|
} from "./command-registration-policy.js";
|
||||||
import { shouldEnsureCliPathForCommandPath } from "./command-startup-policy.js";
|
|
||||||
import { maybeRunCliInContainer, parseCliContainerArgs } from "./container-target.js";
|
import { maybeRunCliInContainer, parseCliContainerArgs } from "./container-target.js";
|
||||||
import { applyCliProfileEnv, parseCliProfileArgs } from "./profile.js";
|
import { applyCliProfileEnv, parseCliProfileArgs } from "./profile.js";
|
||||||
import { createCliProgress } from "./progress.js";
|
import { createCliProgress } from "./progress.js";
|
||||||
import { tryRouteCli } from "./route.js";
|
import { tryRouteCli } from "./route.js";
|
||||||
|
import {
|
||||||
|
resolveMissingPluginCommandMessage as resolveMissingPluginCommandMessageFromPolicy,
|
||||||
|
rewriteUpdateFlagArgv,
|
||||||
|
shouldEnsureCliPath,
|
||||||
|
shouldStartCrestodianForBareRoot,
|
||||||
|
shouldStartCrestodianForModernOnboard,
|
||||||
|
shouldUseBrowserHelpFastPath,
|
||||||
|
shouldUseRootHelpFastPath,
|
||||||
|
} from "./run-main-policy.js";
|
||||||
import { normalizeWindowsArgv } from "./windows-argv.js";
|
import { normalizeWindowsArgv } from "./windows-argv.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
rewriteUpdateFlagArgv,
|
||||||
|
shouldEnsureCliPath,
|
||||||
|
shouldStartCrestodianForBareRoot,
|
||||||
|
shouldStartCrestodianForModernOnboard,
|
||||||
|
shouldUseBrowserHelpFastPath,
|
||||||
|
shouldUseRootHelpFastPath,
|
||||||
|
} from "./run-main-policy.js";
|
||||||
|
|
||||||
async function closeCliMemoryManagers(): Promise<void> {
|
async function closeCliMemoryManagers(): Promise<void> {
|
||||||
if (!hasMemoryRuntime()) {
|
if (!hasMemoryRuntime()) {
|
||||||
return;
|
return;
|
||||||
@@ -49,129 +62,15 @@ async function closeCliMemoryManagers(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rewriteUpdateFlagArgv(argv: string[]): string[] {
|
|
||||||
const index = argv.indexOf("--update");
|
|
||||||
if (index === -1) {
|
|
||||||
return argv;
|
|
||||||
}
|
|
||||||
|
|
||||||
const next = [...argv];
|
|
||||||
next.splice(index, 1, "update");
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldEnsureCliPath(argv: string[]): boolean {
|
|
||||||
const invocation = resolveCliArgvInvocation(argv);
|
|
||||||
if (invocation.hasHelpOrVersion || shouldStartCrestodianForBareRoot(argv)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return shouldEnsureCliPathForCommandPath(invocation.commandPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldUseRootHelpFastPath(argv: string[]): boolean {
|
|
||||||
const invocation = resolveCliArgvInvocation(argv);
|
|
||||||
return (
|
|
||||||
process.env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH !== "1" &&
|
|
||||||
(invocation.isRootHelpInvocation ||
|
|
||||||
(invocation.commandPath.length === 1 &&
|
|
||||||
invocation.commandPath[0] === "help" &&
|
|
||||||
invocation.hasHelpOrVersion))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldUseBrowserHelpFastPath(argv: string[]): boolean {
|
|
||||||
if (process.env.OPENCLAW_DISABLE_CLI_STARTUP_HELP_FAST_PATH === "1") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const invocation = resolveCliArgvInvocation(argv);
|
|
||||||
return (
|
|
||||||
invocation.commandPath.length === 1 &&
|
|
||||||
invocation.commandPath[0] === "browser" &&
|
|
||||||
invocation.hasHelpOrVersion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldStartCrestodianForBareRoot(argv: string[]): boolean {
|
|
||||||
const invocation = resolveCliArgvInvocation(argv);
|
|
||||||
return invocation.commandPath.length === 0 && !invocation.hasHelpOrVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function shouldStartCrestodianForModernOnboard(argv: string[]): boolean {
|
|
||||||
const invocation = resolveCliArgvInvocation(argv);
|
|
||||||
return (
|
|
||||||
invocation.commandPath[0] === "onboard" &&
|
|
||||||
argv.includes("--modern") &&
|
|
||||||
!invocation.hasHelpOrVersion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveMissingPluginCommandMessage(
|
export function resolveMissingPluginCommandMessage(
|
||||||
pluginId: string,
|
pluginId: string,
|
||||||
config?: OpenClawConfig,
|
config?: OpenClawConfig,
|
||||||
options?: { registry?: PluginManifestCommandAliasRegistry },
|
options?: { registry?: PluginManifestCommandAliasRegistry },
|
||||||
): string | null {
|
): string | null {
|
||||||
const normalizedPluginId = normalizeLowercaseStringOrEmpty(pluginId);
|
return resolveMissingPluginCommandMessageFromPolicy(pluginId, config, {
|
||||||
if (!normalizedPluginId) {
|
...(options?.registry ? { registry: options.registry } : {}),
|
||||||
return null;
|
resolveCommandAliasOwner: resolveManifestCommandAliasOwner,
|
||||||
}
|
|
||||||
const allow =
|
|
||||||
Array.isArray(config?.plugins?.allow) && config.plugins.allow.length > 0
|
|
||||||
? config.plugins.allow
|
|
||||||
.filter((entry): entry is string => typeof entry === "string")
|
|
||||||
.map((entry) => normalizeOptionalLowercaseString(entry))
|
|
||||||
.filter(Boolean)
|
|
||||||
: [];
|
|
||||||
const commandAlias = resolveManifestCommandAliasOwner({
|
|
||||||
command: normalizedPluginId,
|
|
||||||
config,
|
|
||||||
registry: options?.registry,
|
|
||||||
});
|
});
|
||||||
const parentPluginId = commandAlias?.pluginId;
|
|
||||||
if (parentPluginId) {
|
|
||||||
if (allow.length > 0 && !allow.includes(parentPluginId)) {
|
|
||||||
return (
|
|
||||||
`"${normalizedPluginId}" is not a plugin; it is a command provided by the ` +
|
|
||||||
`"${parentPluginId}" plugin. Add "${parentPluginId}" to \`plugins.allow\` ` +
|
|
||||||
`instead of "${normalizedPluginId}".`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (config?.plugins?.entries?.[parentPluginId]?.enabled === false) {
|
|
||||||
return (
|
|
||||||
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
|
||||||
`\`plugins.entries.${parentPluginId}.enabled=false\`. Re-enable that entry if you want ` +
|
|
||||||
"the bundled plugin command surface."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (commandAlias.kind === "runtime-slash") {
|
|
||||||
const cliHint = commandAlias.cliCommand
|
|
||||||
? `Use \`openclaw ${commandAlias.cliCommand}\` for related CLI operations, or `
|
|
||||||
: "Use ";
|
|
||||||
return (
|
|
||||||
`"${normalizedPluginId}" is a runtime slash command (/${normalizedPluginId}), not a CLI command. ` +
|
|
||||||
`It is provided by the "${parentPluginId}" plugin. ` +
|
|
||||||
`${cliHint}\`/${normalizedPluginId}\` in a chat session.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allow.length > 0 && !allow.includes(normalizedPluginId)) {
|
|
||||||
if (parentPluginId && allow.includes(parentPluginId)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
|
||||||
`\`plugins.allow\` excludes "${normalizedPluginId}". Add "${normalizedPluginId}" to ` +
|
|
||||||
`\`plugins.allow\` if you want that bundled plugin CLI surface.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (config?.plugins?.entries?.[normalizedPluginId]?.enabled === false) {
|
|
||||||
return (
|
|
||||||
`The \`openclaw ${normalizedPluginId}\` command is unavailable because ` +
|
|
||||||
`\`plugins.entries.${normalizedPluginId}.enabled=false\`. Re-enable that entry if you want ` +
|
|
||||||
"the bundled plugin CLI surface."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldLoadCliDotEnv(env: NodeJS.ProcessEnv = process.env): boolean {
|
function shouldLoadCliDotEnv(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
collectProviderApiKeysForExecution,
|
collectProviderApiKeysForExecution,
|
||||||
executeWithApiKeyRotation,
|
executeWithApiKeyRotation,
|
||||||
} from "../agents/api-key-rotation.js";
|
} from "../agents/api-key-rotation.js";
|
||||||
import { requireApiKey, resolveApiKeyForProvider } from "../agents/model-auth.js";
|
|
||||||
import {
|
import {
|
||||||
mergeModelProviderRequestOverrides,
|
mergeModelProviderRequestOverrides,
|
||||||
sanitizeConfiguredModelProviderRequest,
|
sanitizeConfiguredModelProviderRequest,
|
||||||
@@ -46,6 +45,26 @@ import type {
|
|||||||
import { estimateBase64Size, resolveVideoMaxBase64Bytes } from "./video.js";
|
import { estimateBase64Size, resolveVideoMaxBase64Bytes } from "./video.js";
|
||||||
|
|
||||||
export type ProviderRegistry = Map<string, MediaUnderstandingProvider>;
|
export type ProviderRegistry = Map<string, MediaUnderstandingProvider>;
|
||||||
|
type ResolveApiKeyForProvider = typeof import("../agents/model-auth.js").resolveApiKeyForProvider;
|
||||||
|
type RequireApiKey = typeof import("../agents/model-auth.js").requireApiKey;
|
||||||
|
|
||||||
|
let cachedModelAuth: {
|
||||||
|
resolveApiKeyForProvider: ResolveApiKeyForProvider;
|
||||||
|
requireApiKey: RequireApiKey;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
async function loadModelAuth() {
|
||||||
|
cachedModelAuth ??= await import("../agents/model-auth.js");
|
||||||
|
return cachedModelAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLiteralProviderApiKey(params: {
|
||||||
|
cfg: OpenClawConfig;
|
||||||
|
providerId: string;
|
||||||
|
}): string | null {
|
||||||
|
const value = params.cfg.models?.providers?.[params.providerId]?.apiKey;
|
||||||
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
function sanitizeProviderHeaders(
|
function sanitizeProviderHeaders(
|
||||||
headers: Record<string, unknown> | undefined,
|
headers: Record<string, unknown> | undefined,
|
||||||
@@ -394,6 +413,20 @@ async function resolveProviderExecutionAuth(params: {
|
|||||||
entry: MediaUnderstandingModelConfig;
|
entry: MediaUnderstandingModelConfig;
|
||||||
agentDir?: string;
|
agentDir?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const literalApiKey = resolveLiteralProviderApiKey({
|
||||||
|
cfg: params.cfg,
|
||||||
|
providerId: params.providerId,
|
||||||
|
});
|
||||||
|
if (literalApiKey) {
|
||||||
|
return {
|
||||||
|
apiKeys: collectProviderApiKeysForExecution({
|
||||||
|
provider: params.providerId,
|
||||||
|
primaryApiKey: literalApiKey,
|
||||||
|
}),
|
||||||
|
providerConfig: params.cfg.models?.providers?.[params.providerId],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const { requireApiKey, resolveApiKeyForProvider } = await loadModelAuth();
|
||||||
const auth = await resolveApiKeyForProvider({
|
const auth = await resolveApiKeyForProvider({
|
||||||
provider: params.providerId,
|
provider: params.providerId,
|
||||||
cfg: params.cfg,
|
cfg: params.cfg,
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import { constants as fsConstants } from "node:fs";
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { hasAvailableAuthForProvider } from "../agents/model-auth.js";
|
|
||||||
import {
|
|
||||||
findModelInCatalog,
|
|
||||||
loadModelCatalog,
|
|
||||||
modelSupportsVision,
|
|
||||||
} from "../agents/model-catalog.js";
|
|
||||||
import { findNormalizedProviderValue } from "../agents/provider-id.js";
|
import { findNormalizedProviderValue } from "../agents/provider-id.js";
|
||||||
import type { MsgContext } from "../auto-reply/templating.js";
|
import type { MsgContext } from "../auto-reply/templating.js";
|
||||||
import {
|
import {
|
||||||
@@ -58,12 +52,45 @@ export { createMediaAttachmentCache, normalizeMediaAttachments } from "./runner.
|
|||||||
export type { ActiveMediaModel } from "./active-model.types.js";
|
export type { ActiveMediaModel } from "./active-model.types.js";
|
||||||
|
|
||||||
type ProviderRegistry = Map<string, MediaUnderstandingProvider>;
|
type ProviderRegistry = Map<string, MediaUnderstandingProvider>;
|
||||||
|
type HasAvailableAuthForProvider =
|
||||||
|
typeof import("../agents/model-auth.js").hasAvailableAuthForProvider;
|
||||||
|
type ModelCatalogApi = typeof import("../agents/model-catalog.js");
|
||||||
|
type ModelCatalog = Awaited<ReturnType<ModelCatalogApi["loadModelCatalog"]>>;
|
||||||
|
|
||||||
export type RunCapabilityResult = {
|
export type RunCapabilityResult = {
|
||||||
outputs: MediaUnderstandingOutput[];
|
outputs: MediaUnderstandingOutput[];
|
||||||
decision: MediaUnderstandingDecision;
|
decision: MediaUnderstandingDecision;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cachedHasAvailableAuthForProvider: HasAvailableAuthForProvider | null = null;
|
||||||
|
let cachedModelCatalogApi: ModelCatalogApi | null = null;
|
||||||
|
|
||||||
|
async function loadModelCatalogApi(): Promise<ModelCatalogApi> {
|
||||||
|
cachedModelCatalogApi ??= await import("../agents/model-catalog.js");
|
||||||
|
return cachedModelCatalogApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveLiteralProviderApiKey(
|
||||||
|
cfg: OpenClawConfig | undefined,
|
||||||
|
providerId: string,
|
||||||
|
): string | null {
|
||||||
|
const value = cfg?.models?.providers?.[providerId]?.apiKey;
|
||||||
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function hasProviderAuthAvailable(params: {
|
||||||
|
provider: string;
|
||||||
|
cfg?: OpenClawConfig;
|
||||||
|
agentDir?: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
if (resolveLiteralProviderApiKey(params.cfg, params.provider)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cachedHasAvailableAuthForProvider ??= (await import("../agents/model-auth.js"))
|
||||||
|
.hasAvailableAuthForProvider;
|
||||||
|
return await cachedHasAvailableAuthForProvider(params);
|
||||||
|
}
|
||||||
|
|
||||||
function resolveConfiguredKeyProviderOrder(params: {
|
function resolveConfiguredKeyProviderOrder(params: {
|
||||||
cfg: OpenClawConfig;
|
cfg: OpenClawConfig;
|
||||||
providerRegistry: ProviderRegistry;
|
providerRegistry: ProviderRegistry;
|
||||||
@@ -113,11 +140,13 @@ function resolveConfiguredImageModel(params: {
|
|||||||
|
|
||||||
function resolveCatalogImageModelId(params: {
|
function resolveCatalogImageModelId(params: {
|
||||||
providerId: string;
|
providerId: string;
|
||||||
catalog: Awaited<ReturnType<typeof loadModelCatalog>>;
|
catalog: ModelCatalog;
|
||||||
|
modelSupportsVision: ModelCatalogApi["modelSupportsVision"];
|
||||||
}): string | undefined {
|
}): string | undefined {
|
||||||
const matches = params.catalog.filter(
|
const matches = params.catalog.filter(
|
||||||
(entry) =>
|
(entry) =>
|
||||||
normalizeMediaProviderId(entry.provider) === params.providerId && modelSupportsVision(entry),
|
normalizeMediaProviderId(entry.provider) === params.providerId &&
|
||||||
|
params.modelSupportsVision(entry),
|
||||||
);
|
);
|
||||||
if (matches.length === 0) {
|
if (matches.length === 0) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -135,6 +164,7 @@ async function explicitImageModelVisionStatus(params: {
|
|||||||
if (configured?.id?.trim() === params.model && configured.input?.includes("image")) {
|
if (configured?.id?.trim() === params.model && configured.input?.includes("image")) {
|
||||||
return "supported";
|
return "supported";
|
||||||
}
|
}
|
||||||
|
const { findModelInCatalog, loadModelCatalog, modelSupportsVision } = await loadModelCatalogApi();
|
||||||
const catalog = await loadModelCatalog({ config: params.cfg });
|
const catalog = await loadModelCatalog({ config: params.cfg });
|
||||||
const entry = findModelInCatalog(catalog, params.providerId, params.model);
|
const entry = findModelInCatalog(catalog, params.providerId, params.model);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
@@ -171,10 +201,12 @@ async function resolveAutoImageModelId(params: {
|
|||||||
if (defaultModel) {
|
if (defaultModel) {
|
||||||
return defaultModel;
|
return defaultModel;
|
||||||
}
|
}
|
||||||
|
const { loadModelCatalog, modelSupportsVision } = await loadModelCatalogApi();
|
||||||
const catalog = await loadModelCatalog({ config: params.cfg });
|
const catalog = await loadModelCatalog({ config: params.cfg });
|
||||||
return resolveCatalogImageModelId({
|
return resolveCatalogImageModelId({
|
||||||
providerId: params.providerId,
|
providerId: params.providerId,
|
||||||
catalog,
|
catalog,
|
||||||
|
modelSupportsVision,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +489,7 @@ async function resolveKeyEntry(params: {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!(await hasAvailableAuthForProvider({
|
!(await hasProviderAuthAvailable({
|
||||||
provider: providerId,
|
provider: providerId,
|
||||||
cfg,
|
cfg,
|
||||||
agentDir,
|
agentDir,
|
||||||
@@ -649,7 +681,7 @@ async function resolveActiveModelEntry(params: {
|
|||||||
if (params.capability === "video" && !provider.describeVideo) {
|
if (params.capability === "video" && !provider.describeVideo) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const hasAuth = await hasAvailableAuthForProvider({
|
const hasAuth = await hasProviderAuthAvailable({
|
||||||
provider: providerId,
|
provider: providerId,
|
||||||
cfg: params.cfg,
|
cfg: params.cfg,
|
||||||
agentDir: params.agentDir,
|
agentDir: params.agentDir,
|
||||||
@@ -824,6 +856,8 @@ export async function runCapability(params: {
|
|||||||
activeProvider &&
|
activeProvider &&
|
||||||
!hasExplicitImageUnderstandingConfig({ cfg, config })
|
!hasExplicitImageUnderstandingConfig({ cfg, config })
|
||||||
) {
|
) {
|
||||||
|
const { findModelInCatalog, loadModelCatalog, modelSupportsVision } =
|
||||||
|
await loadModelCatalogApi();
|
||||||
const catalog = await loadModelCatalog({ config: cfg });
|
const catalog = await loadModelCatalog({ config: cfg });
|
||||||
const entry = findModelInCatalog(catalog, activeProvider, params.activeModel?.model ?? "");
|
const entry = findModelInCatalog(catalog, activeProvider, params.activeModel?.model ?? "");
|
||||||
if (modelSupportsVision(entry)) {
|
if (modelSupportsVision(entry)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user