mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
refactor(models): share auth helpers and forward-compat list fallbacks
This commit is contained in:
@@ -24,6 +24,20 @@ const ANTIGRAVITY_OPUS_THINKING_TEMPLATE_MODEL_IDS = [
|
||||
"claude-opus-4.5-thinking",
|
||||
] as const;
|
||||
|
||||
export const ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES = [
|
||||
{
|
||||
id: ANTIGRAVITY_OPUS_46_THINKING_MODEL_ID,
|
||||
templatePrefixes: [
|
||||
"google-antigravity/claude-opus-4-5-thinking",
|
||||
"google-antigravity/claude-opus-4.5-thinking",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: ANTIGRAVITY_OPUS_46_MODEL_ID,
|
||||
templatePrefixes: ["google-antigravity/claude-opus-4-5", "google-antigravity/claude-opus-4.5"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
function resolveOpenAICodexGpt53FallbackModel(
|
||||
provider: string,
|
||||
modelId: string,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js";
|
||||
import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js";
|
||||
import { resolveOpenClawAgentDir } from "../agents/agent-paths.js";
|
||||
import {
|
||||
@@ -8,7 +6,6 @@ import {
|
||||
resolveAgentWorkspaceDir,
|
||||
} from "../agents/agent-scope.js";
|
||||
import { upsertAuthProfile } from "../agents/auth-profiles.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js";
|
||||
import { enablePluginInConfig } from "../plugins/enable.js";
|
||||
import { resolvePluginProviders } from "../plugins/providers.js";
|
||||
@@ -16,6 +13,12 @@ import { isRemoteEnvironment } from "./oauth-env.js";
|
||||
import { createVpsAwareOAuthHandlers } from "./oauth-flow.js";
|
||||
import { applyAuthProfileConfig } from "./onboard-auth.js";
|
||||
import { openUrl } from "./onboard-helpers.js";
|
||||
import {
|
||||
applyDefaultModel,
|
||||
mergeConfigPatch,
|
||||
pickAuthMethod,
|
||||
resolveProviderMatch,
|
||||
} from "./provider-auth-helpers.js";
|
||||
|
||||
export type PluginProviderAuthChoiceOptions = {
|
||||
authChoice: string;
|
||||
@@ -25,78 +28,6 @@ export type PluginProviderAuthChoiceOptions = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
function resolveProviderMatch(
|
||||
providers: ProviderPlugin[],
|
||||
rawProvider: string,
|
||||
): ProviderPlugin | null {
|
||||
const normalized = normalizeProviderId(rawProvider);
|
||||
return (
|
||||
providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
|
||||
providers.find(
|
||||
(provider) =>
|
||||
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false,
|
||||
) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null {
|
||||
const raw = rawMethod?.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const normalized = raw.toLowerCase();
|
||||
return (
|
||||
provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
|
||||
provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
if (!isPlainRecord(base) || !isPlainRecord(patch)) {
|
||||
return patch as T;
|
||||
}
|
||||
|
||||
const next: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
const existing = next[key];
|
||||
if (isPlainRecord(existing) && isPlainRecord(value)) {
|
||||
next[key] = mergeConfigPatch(existing, value);
|
||||
} else {
|
||||
next[key] = value;
|
||||
}
|
||||
}
|
||||
return next as T;
|
||||
}
|
||||
|
||||
function applyDefaultModel(cfg: OpenClawConfig, model: string): OpenClawConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[model] = models[model] ?? {};
|
||||
|
||||
const existingModel = cfg.agents?.defaults?.model;
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
model: {
|
||||
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
||||
? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks }
|
||||
: undefined),
|
||||
primary: model,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function applyAuthChoicePluginProvider(
|
||||
params: ApplyAuthChoiceParams,
|
||||
options: PluginProviderAuthChoiceOptions,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { confirm as clackConfirm, select as clackSelect, text as clackText } from "@clack/prompts";
|
||||
import type { AuthProfileCredential } from "../../agents/auth-profiles/types.js";
|
||||
import type {
|
||||
ProviderAuthMethod,
|
||||
ProviderAuthResult,
|
||||
ProviderPlugin,
|
||||
} from "../../plugins/types.js";
|
||||
import type { ProviderAuthResult, ProviderPlugin } from "../../plugins/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import {
|
||||
resolveAgentDir,
|
||||
@@ -16,7 +12,7 @@ import { normalizeProviderId } from "../../agents/model-selection.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../../agents/workspace.js";
|
||||
import { formatCliCommand } from "../../cli/command-format.js";
|
||||
import { parseDurationMs } from "../../cli/parse-duration.js";
|
||||
import { readConfigFileSnapshot, type OpenClawConfig } from "../../config/config.js";
|
||||
import { readConfigFileSnapshot } from "../../config/config.js";
|
||||
import { logConfigUpdated } from "../../config/logging.js";
|
||||
import { resolvePluginProviders } from "../../plugins/providers.js";
|
||||
import { stylePromptHint, stylePromptMessage } from "../../terminal/prompt-style.js";
|
||||
@@ -26,6 +22,12 @@ import { isRemoteEnvironment } from "../oauth-env.js";
|
||||
import { createVpsAwareOAuthHandlers } from "../oauth-flow.js";
|
||||
import { applyAuthProfileConfig } from "../onboard-auth.js";
|
||||
import { openUrl } from "../onboard-helpers.js";
|
||||
import {
|
||||
applyDefaultModel,
|
||||
mergeConfigPatch,
|
||||
pickAuthMethod,
|
||||
resolveProviderMatch,
|
||||
} from "../provider-auth-helpers.js";
|
||||
import { updateConfig } from "./shared.js";
|
||||
|
||||
const confirm = (params: Parameters<typeof clackConfirm>[0]) =>
|
||||
@@ -239,25 +241,6 @@ type LoginOptions = {
|
||||
setDefault?: boolean;
|
||||
};
|
||||
|
||||
function resolveProviderMatch(
|
||||
providers: ProviderPlugin[],
|
||||
rawProvider?: string,
|
||||
): ProviderPlugin | null {
|
||||
const raw = rawProvider?.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const normalized = normalizeProviderId(raw);
|
||||
return (
|
||||
providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
|
||||
providers.find(
|
||||
(provider) =>
|
||||
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false,
|
||||
) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveRequestedLoginProviderOrThrow(
|
||||
providers: ProviderPlugin[],
|
||||
rawProvider?: string,
|
||||
@@ -280,63 +263,6 @@ export function resolveRequestedLoginProviderOrThrow(
|
||||
);
|
||||
}
|
||||
|
||||
function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null {
|
||||
const raw = rawMethod?.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const normalized = raw.toLowerCase();
|
||||
return (
|
||||
provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
|
||||
provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
if (!isPlainRecord(base) || !isPlainRecord(patch)) {
|
||||
return patch as T;
|
||||
}
|
||||
|
||||
const next: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
const existing = next[key];
|
||||
if (isPlainRecord(existing) && isPlainRecord(value)) {
|
||||
next[key] = mergeConfigPatch(existing, value);
|
||||
} else {
|
||||
next[key] = value;
|
||||
}
|
||||
}
|
||||
return next as T;
|
||||
}
|
||||
|
||||
function applyDefaultModel(cfg: OpenClawConfig, model: string): OpenClawConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[model] = models[model] ?? {};
|
||||
|
||||
const existingModel = cfg.agents?.defaults?.model;
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
model: {
|
||||
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
||||
? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks }
|
||||
: undefined),
|
||||
primary: model,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function credentialMode(credential: AuthProfileCredential): "api_key" | "oauth" | "token" {
|
||||
if (credential.type === "api_key") {
|
||||
return "api_key";
|
||||
|
||||
@@ -8,7 +8,9 @@ const mocks = vi.hoisted(() => {
|
||||
models: { providers: {} },
|
||||
}),
|
||||
ensureAuthProfileStore: vi.fn().mockReturnValue({ version: 1, profiles: {}, order: {} }),
|
||||
loadModelRegistry: vi.fn().mockResolvedValue({ models: [], availableKeys: new Set() }),
|
||||
loadModelRegistry: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ models: [], availableKeys: new Set(), registry: {} }),
|
||||
resolveConfiguredEntries: vi.fn().mockReturnValue({
|
||||
entries: [
|
||||
{
|
||||
@@ -20,21 +22,16 @@ const mocks = vi.hoisted(() => {
|
||||
],
|
||||
}),
|
||||
printModelTable,
|
||||
resolveModel: vi.fn().mockReturnValue({
|
||||
model: {
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.3-codex-spark",
|
||||
name: "GPT-5.3 Codex Spark",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text"],
|
||||
contextWindow: 272000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
},
|
||||
error: undefined,
|
||||
authStorage: {} as never,
|
||||
modelRegistry: {} as never,
|
||||
resolveForwardCompatModel: vi.fn().mockReturnValue({
|
||||
provider: "openai-codex",
|
||||
id: "gpt-5.3-codex-spark",
|
||||
name: "GPT-5.3 Codex Spark",
|
||||
api: "openai-codex-responses",
|
||||
baseUrl: "https://chatgpt.com/backend-api",
|
||||
input: ["text"],
|
||||
contextWindow: 272000,
|
||||
maxTokens: 128000,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -68,14 +65,18 @@ vi.mock("./list.table.js", () => ({
|
||||
printModelTable: mocks.printModelTable,
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/pi-embedded-runner/model.js", () => ({
|
||||
resolveModel: mocks.resolveModel,
|
||||
}));
|
||||
vi.mock("../../agents/model-forward-compat.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../agents/model-forward-compat.js")>();
|
||||
return {
|
||||
...actual,
|
||||
resolveForwardCompatModel: mocks.resolveForwardCompatModel,
|
||||
};
|
||||
});
|
||||
|
||||
import { modelsListCommand } from "./list.list-command.js";
|
||||
|
||||
describe("modelsListCommand forward-compat", () => {
|
||||
it("does not mark configured codex spark as missing when resolveModel can build a fallback", async () => {
|
||||
it("does not mark configured codex spark as missing when forward-compat can build a fallback", async () => {
|
||||
const runtime = { log: vi.fn(), error: vi.fn() };
|
||||
|
||||
await modelsListCommand({ json: true }, runtime as never);
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
||||
import type { ModelRegistry } from "../../agents/pi-model-discovery.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type { ModelRow } from "./list.types.js";
|
||||
import { ensureAuthProfileStore } from "../../agents/auth-profiles.js";
|
||||
import { resolveForwardCompatModel } from "../../agents/model-forward-compat.js";
|
||||
import { parseModelRef } from "../../agents/model-selection.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { resolveConfiguredEntries } from "./list.configured.js";
|
||||
import { formatErrorWithStack } from "./list.errors.js";
|
||||
import { loadModelRegistry, toModelRow } from "./list.registry.js";
|
||||
import { printModelTable } from "./list.table.js";
|
||||
import { DEFAULT_PROVIDER, ensureFlagCompatibility, modelKey } from "./shared.js";
|
||||
import { DEFAULT_PROVIDER, ensureFlagCompatibility, isLocalBaseUrl, modelKey } from "./shared.js";
|
||||
|
||||
export async function modelsListCommand(
|
||||
opts: {
|
||||
@@ -33,10 +35,12 @@ export async function modelsListCommand(
|
||||
})();
|
||||
|
||||
let models: Model<Api>[] = [];
|
||||
let modelRegistry: ModelRegistry | undefined;
|
||||
let availableKeys: Set<string> | undefined;
|
||||
let availabilityErrorMessage: string | undefined;
|
||||
try {
|
||||
const loaded = await loadModelRegistry(cfg);
|
||||
modelRegistry = loaded.registry;
|
||||
models = loaded.models;
|
||||
availableKeys = loaded.availableKeys;
|
||||
availabilityErrorMessage = loaded.availabilityErrorMessage;
|
||||
@@ -58,22 +62,6 @@ export async function modelsListCommand(
|
||||
|
||||
const rows: ModelRow[] = [];
|
||||
|
||||
const isLocalBaseUrl = (baseUrl: string) => {
|
||||
try {
|
||||
const url = new URL(baseUrl);
|
||||
const host = url.hostname.toLowerCase();
|
||||
return (
|
||||
host === "localhost" ||
|
||||
host === "127.0.0.1" ||
|
||||
host === "0.0.0.0" ||
|
||||
host === "::1" ||
|
||||
host.endsWith(".local")
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (opts.all) {
|
||||
const sorted = [...models].toSorted((a, b) => {
|
||||
const p = a.provider.localeCompare(b.provider);
|
||||
@@ -109,7 +97,18 @@ export async function modelsListCommand(
|
||||
if (providerFilter && entry.ref.provider.toLowerCase() !== providerFilter) {
|
||||
continue;
|
||||
}
|
||||
const model = modelByKey.get(entry.key);
|
||||
let model = modelByKey.get(entry.key);
|
||||
if (!model && modelRegistry) {
|
||||
const forwardCompat = resolveForwardCompatModel(
|
||||
entry.ref.provider,
|
||||
entry.ref.model,
|
||||
modelRegistry,
|
||||
);
|
||||
if (forwardCompat) {
|
||||
model = forwardCompat;
|
||||
modelByKey.set(entry.key, forwardCompat);
|
||||
}
|
||||
}
|
||||
if (opts.local && model && !isLocalBaseUrl(model.baseUrl)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
resolveAwsSdkEnvVarName,
|
||||
resolveEnvApiKey,
|
||||
} from "../../agents/model-auth.js";
|
||||
import { resolveForwardCompatModel } from "../../agents/model-forward-compat.js";
|
||||
import {
|
||||
ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES,
|
||||
resolveForwardCompatModel,
|
||||
} from "../../agents/model-forward-compat.js";
|
||||
import { ensureOpenClawModelsJson } from "../../agents/models-config.js";
|
||||
import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js";
|
||||
import {
|
||||
@@ -18,23 +21,7 @@ import {
|
||||
MODEL_AVAILABILITY_UNAVAILABLE_CODE,
|
||||
shouldFallbackToAuthHeuristics,
|
||||
} from "./list.errors.js";
|
||||
import { modelKey } from "./shared.js";
|
||||
|
||||
const isLocalBaseUrl = (baseUrl: string) => {
|
||||
try {
|
||||
const url = new URL(baseUrl);
|
||||
const host = url.hostname.toLowerCase();
|
||||
return (
|
||||
host === "localhost" ||
|
||||
host === "127.0.0.1" ||
|
||||
host === "0.0.0.0" ||
|
||||
host === "::1" ||
|
||||
host.endsWith(".local")
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
import { isLocalBaseUrl, modelKey } from "./shared.js";
|
||||
|
||||
const hasAuthForProvider = (
|
||||
provider: string,
|
||||
@@ -147,34 +134,17 @@ export async function loadModelRegistry(cfg: OpenClawConfig) {
|
||||
|
||||
type SynthesizedForwardCompat = {
|
||||
key: string;
|
||||
templatePrefixes: string[];
|
||||
templatePrefixes: readonly string[];
|
||||
};
|
||||
|
||||
function appendAntigravityForwardCompatModels(
|
||||
models: Model<Api>[],
|
||||
modelRegistry: ModelRegistry,
|
||||
): { models: Model<Api>[]; synthesizedForwardCompat: SynthesizedForwardCompat[] } {
|
||||
const candidates = [
|
||||
{
|
||||
id: "claude-opus-4-6-thinking",
|
||||
templatePrefixes: [
|
||||
"google-antigravity/claude-opus-4-5-thinking",
|
||||
"google-antigravity/claude-opus-4.5-thinking",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "claude-opus-4-6",
|
||||
templatePrefixes: [
|
||||
"google-antigravity/claude-opus-4-5",
|
||||
"google-antigravity/claude-opus-4.5",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const nextModels = [...models];
|
||||
const synthesizedForwardCompat: SynthesizedForwardCompat[] = [];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
for (const candidate of ANTIGRAVITY_OPUS_46_FORWARD_COMPAT_CANDIDATES) {
|
||||
const key = modelKey("google-antigravity", candidate.id);
|
||||
const hasForwardCompat = nextModels.some((model) => modelKey(model.provider, model.id) === key);
|
||||
if (hasForwardCompat) {
|
||||
@@ -196,7 +166,10 @@ function appendAntigravityForwardCompatModels(
|
||||
return { models: nextModels, synthesizedForwardCompat };
|
||||
}
|
||||
|
||||
function hasAvailableTemplate(availableKeys: Set<string>, templatePrefixes: string[]): boolean {
|
||||
function hasAvailableTemplate(
|
||||
availableKeys: Set<string>,
|
||||
templatePrefixes: readonly string[],
|
||||
): boolean {
|
||||
for (const key of availableKeys) {
|
||||
if (templatePrefixes.some((prefix) => key.startsWith(prefix))) {
|
||||
return true;
|
||||
|
||||
@@ -43,6 +43,22 @@ export const formatMs = (value?: number | null) => {
|
||||
return `${Math.round(value / 100) / 10}s`;
|
||||
};
|
||||
|
||||
export const isLocalBaseUrl = (baseUrl: string) => {
|
||||
try {
|
||||
const url = new URL(baseUrl);
|
||||
const host = url.hostname.toLowerCase();
|
||||
return (
|
||||
host === "localhost" ||
|
||||
host === "127.0.0.1" ||
|
||||
host === "0.0.0.0" ||
|
||||
host === "::1" ||
|
||||
host.endsWith(".local")
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export async function updateConfig(
|
||||
mutator: (cfg: OpenClawConfig) => OpenClawConfig,
|
||||
): Promise<OpenClawConfig> {
|
||||
|
||||
82
src/commands/provider-auth-helpers.ts
Normal file
82
src/commands/provider-auth-helpers.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js";
|
||||
import { normalizeProviderId } from "../agents/model-selection.js";
|
||||
|
||||
export function resolveProviderMatch(
|
||||
providers: ProviderPlugin[],
|
||||
rawProvider?: string,
|
||||
): ProviderPlugin | null {
|
||||
const raw = rawProvider?.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const normalized = normalizeProviderId(raw);
|
||||
return (
|
||||
providers.find((provider) => normalizeProviderId(provider.id) === normalized) ??
|
||||
providers.find(
|
||||
(provider) =>
|
||||
provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false,
|
||||
) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
export function pickAuthMethod(
|
||||
provider: ProviderPlugin,
|
||||
rawMethod?: string,
|
||||
): ProviderAuthMethod | null {
|
||||
const raw = rawMethod?.trim();
|
||||
if (!raw) {
|
||||
return null;
|
||||
}
|
||||
const normalized = raw.toLowerCase();
|
||||
return (
|
||||
provider.auth.find((method) => method.id.toLowerCase() === normalized) ??
|
||||
provider.auth.find((method) => method.label.toLowerCase() === normalized) ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
function isPlainRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
||||
}
|
||||
|
||||
export function mergeConfigPatch<T>(base: T, patch: unknown): T {
|
||||
if (!isPlainRecord(base) || !isPlainRecord(patch)) {
|
||||
return patch as T;
|
||||
}
|
||||
|
||||
const next: Record<string, unknown> = { ...base };
|
||||
for (const [key, value] of Object.entries(patch)) {
|
||||
const existing = next[key];
|
||||
if (isPlainRecord(existing) && isPlainRecord(value)) {
|
||||
next[key] = mergeConfigPatch(existing, value);
|
||||
} else {
|
||||
next[key] = value;
|
||||
}
|
||||
}
|
||||
return next as T;
|
||||
}
|
||||
|
||||
export function applyDefaultModel(cfg: OpenClawConfig, model: string): OpenClawConfig {
|
||||
const models = { ...cfg.agents?.defaults?.models };
|
||||
models[model] = models[model] ?? {};
|
||||
|
||||
const existingModel = cfg.agents?.defaults?.model;
|
||||
return {
|
||||
...cfg,
|
||||
agents: {
|
||||
...cfg.agents,
|
||||
defaults: {
|
||||
...cfg.agents?.defaults,
|
||||
models,
|
||||
model: {
|
||||
...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel
|
||||
? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks }
|
||||
: undefined),
|
||||
primary: model,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user