fix: keep provider discovery on mockable lazy runtime paths

This commit is contained in:
Peter Steinberger
2026-03-28 10:38:45 +00:00
parent ff01d749fc
commit e34a770b8a
9 changed files with 103 additions and 21 deletions

View File

@@ -1,6 +1,6 @@
import { ensureAuthProfileStore as ensureAuthProfileStoreImpl } from "./auth-profiles.js";
import { ensureAuthProfileStore as ensureAuthProfileStoreImpl } from "./auth-profiles/store.js";
type EnsureAuthProfileStore = typeof import("./auth-profiles.js").ensureAuthProfileStore;
type EnsureAuthProfileStore = typeof import("./auth-profiles/store.js").ensureAuthProfileStore;
export function ensureAuthProfileStore(
...args: Parameters<EnsureAuthProfileStore>

View File

@@ -2,8 +2,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
const normalizeProviderModelIdWithPluginMock = vi.fn();
vi.mock("../plugins/provider-runtime.js", () => ({
normalizeProviderModelIdWithPlugin: (params: unknown) =>
vi.mock("./provider-model-normalization.runtime.js", () => ({
normalizeProviderModelIdWithRuntime: (params: unknown) =>
normalizeProviderModelIdWithPluginMock(params),
}));

View File

@@ -8,8 +8,6 @@ import {
import { createSubsystemLogger } from "../logging/subsystem.js";
import { normalizeGoogleModelId } from "../plugin-sdk/google.js";
import { normalizeXaiModelId } from "../plugin-sdk/xai.js";
import { resolveRuntimeCliBackends } from "../plugins/cli-backends.runtime.js";
import { normalizeProviderModelIdWithPlugin } from "../plugins/provider-runtime.js";
import { sanitizeForLog } from "../terminal/ansi.js";
import {
resolveAgentConfig,
@@ -26,9 +24,34 @@ import {
normalizeProviderId,
normalizeProviderIdForAuth,
} from "./provider-id.js";
import { normalizeProviderModelIdWithRuntime } from "./provider-model-normalization.runtime.js";
let log: ReturnType<typeof createSubsystemLogger> | null = null;
type CliBackendRuntimeModule = typeof import("../plugins/cli-backends.runtime.js");
const CLI_BACKEND_RUNTIME_CANDIDATES = [
"../plugins/cli-backends.runtime.js",
"../plugins/cli-backends.runtime.ts",
] as const;
let cliBackendRuntimeModule: CliBackendRuntimeModule | undefined;
function loadCliBackendRuntime(): CliBackendRuntimeModule | null {
if (cliBackendRuntimeModule) {
return cliBackendRuntimeModule;
}
for (const candidate of CLI_BACKEND_RUNTIME_CANDIDATES) {
try {
cliBackendRuntimeModule = require(candidate) as CliBackendRuntimeModule;
return cliBackendRuntimeModule;
} catch {
// Try source/runtime candidates in order.
}
}
return null;
}
function getLog(): ReturnType<typeof createSubsystemLogger> {
log ??= createSubsystemLogger("model-selection");
return log;
@@ -84,9 +107,8 @@ export {
export function isCliProvider(provider: string, cfg?: OpenClawConfig): boolean {
const normalized = normalizeProviderId(provider);
if (
resolveRuntimeCliBackends().some((backend) => normalizeProviderId(backend.id) === normalized)
) {
const cliBackends = loadCliBackendRuntime()?.resolveRuntimeCliBackends() ?? [];
if (cliBackends.some((backend) => normalizeProviderId(backend.id) === normalized)) {
return true;
}
const backends = cfg?.agents?.defaults?.cliBackends ?? {};
@@ -139,7 +161,7 @@ function normalizeProviderModelId(provider: string, model: string): string {
}
}
return (
normalizeProviderModelIdWithPlugin({
normalizeProviderModelIdWithRuntime({
provider,
context: {
provider,

View File

@@ -10,7 +10,7 @@ import {
resolvePluginDiscoveryProviders,
runProviderCatalog,
} from "../plugins/provider-discovery.js";
import { ensureAuthProfileStore } from "./auth-profiles.js";
import { ensureAuthProfileStore } from "./auth-profiles/store.js";
import type {
ProviderApiKeyResolver,
ProviderAuthResolver,
@@ -107,7 +107,7 @@ async function resolvePluginImplicitProviders(
order: import("../plugins/types.js").ProviderDiscoveryOrder,
): Promise<Record<string, ProviderConfig> | undefined> {
const onlyPluginIds = resolveLiveProviderDiscoveryFilter(ctx.env);
const providers = resolvePluginDiscoveryProviders({
const providers = await resolvePluginDiscoveryProviders({
config: ctx.config,
workspaceDir: ctx.workspaceDir,
env: ctx.env,

View File

@@ -0,0 +1,39 @@
import { createRequire } from "node:module";
type ProviderRuntimeModule = Pick<
typeof import("../plugins/provider-runtime.js"),
"normalizeProviderModelIdWithPlugin"
>;
const require = createRequire(import.meta.url);
const PROVIDER_RUNTIME_CANDIDATES = [
"../plugins/provider-runtime.js",
"../plugins/provider-runtime.ts",
] as const;
let providerRuntimeModule: ProviderRuntimeModule | undefined;
function loadProviderRuntime(): ProviderRuntimeModule | null {
if (providerRuntimeModule) {
return providerRuntimeModule;
}
for (const candidate of PROVIDER_RUNTIME_CANDIDATES) {
try {
providerRuntimeModule = require(candidate) as ProviderRuntimeModule;
return providerRuntimeModule;
} catch {
// Try source/runtime candidates in order.
}
}
return null;
}
export function normalizeProviderModelIdWithRuntime(params: {
provider: string;
context: {
provider: string;
modelId: string;
};
}): string | undefined {
return loadProviderRuntime()?.normalizeProviderModelIdWithPlugin(params);
}

View File

@@ -32,7 +32,10 @@ function modelPricingCacheKey(provider: string, model: string): string {
function shouldNormalizeCachedPricingLookup(provider: string): boolean {
const normalized = normalizeProviderId(provider);
return (
normalized === "anthropic" || normalized === "openrouter" || WRAPPER_PROVIDERS.has(normalized)
normalized === "anthropic" ||
normalized === "openrouter" ||
normalized === "xai" ||
WRAPPER_PROVIDERS.has(normalized)
);
}

View File

@@ -51,7 +51,6 @@ const WRAPPER_PROVIDERS = new Set([
"openrouter",
"vercel-ai-gateway",
]);
const log = createSubsystemLogger("gateway").child("model-pricing");
let refreshTimer: ReturnType<typeof setTimeout> | null = null;

View File

@@ -0,0 +1,15 @@
import type { OpenClawConfig } from "../config/config.js";
import { resolvePluginProviders } from "./providers.runtime.js";
import type { ProviderPlugin } from "./types.js";
export function resolvePluginDiscoveryProvidersRuntime(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
}): ProviderPlugin[] {
return resolvePluginProviders({
...params,
bundledProviderAllowlistCompat: true,
});
}

View File

@@ -1,25 +1,29 @@
import { normalizeProviderId } from "../agents/model-selection.js";
import type { OpenClawConfig } from "../config/config.js";
import type { ModelProviderConfig } from "../config/types.js";
import { resolvePluginProviders } from "./providers.runtime.js";
import type { ProviderDiscoveryOrder, ProviderPlugin } from "./types.js";
const DISCOVERY_ORDER: readonly ProviderDiscoveryOrder[] = ["simple", "profile", "paired", "late"];
let providerRuntimePromise: Promise<typeof import("./provider-discovery.runtime.js")> | undefined;
function loadProviderRuntime() {
providerRuntimePromise ??= import("./provider-discovery.runtime.js");
return providerRuntimePromise;
}
function resolveProviderCatalogHook(provider: ProviderPlugin) {
return provider.catalog ?? provider.discovery;
}
export function resolvePluginDiscoveryProviders(params: {
export async function resolvePluginDiscoveryProviders(params: {
config?: OpenClawConfig;
workspaceDir?: string;
env?: NodeJS.ProcessEnv;
onlyPluginIds?: string[];
}): ProviderPlugin[] {
return resolvePluginProviders({
...params,
bundledProviderAllowlistCompat: true,
}).filter((provider) => resolveProviderCatalogHook(provider));
}): Promise<ProviderPlugin[]> {
return (await loadProviderRuntime())
.resolvePluginDiscoveryProvidersRuntime(params)
.filter((provider) => resolveProviderCatalogHook(provider));
}
export function groupPluginDiscoveryProvidersByOrder(