From e55c4c404451a4d02e2f20be15a18adf0db4b7dd Mon Sep 17 00:00:00 2001 From: ShengtongZhu Date: Sun, 15 Mar 2026 19:33:14 +0800 Subject: [PATCH] fix(guardian): resolve well-known provider baseUrl from pi-ai model database When a provider (e.g. anthropic, openai) is not explicitly configured in openclaw.json, fall back to pi-ai's built-in model database to resolve baseUrl and api type. This avoids requiring users to manually configure well-known providers. Co-Authored-By: Claude Opus 4.6 (1M context) --- extensions/guardian/index.test.ts | 12 +++++++----- extensions/guardian/index.ts | 22 ++++++++++++++++++++-- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/extensions/guardian/index.test.ts b/extensions/guardian/index.test.ts index e290ea11351..f191e271ab4 100644 --- a/extensions/guardian/index.test.ts +++ b/extensions/guardian/index.test.ts @@ -463,12 +463,13 @@ describe("guardian index — resolveModelFromConfig", () => { expect(result.api).toBe("openai-completions"); // default }); - it("returns partial model for known providers not in explicit config — pending SDK resolution", () => { + it("resolves known providers from pi-ai built-in database when not in explicit config", () => { const result = resolveModelFromConfig("anthropic", "claude-haiku-4-5", {}); expect(result).toBeDefined(); expect(result.provider).toBe("anthropic"); expect(result.modelId).toBe("claude-haiku-4-5"); - expect(result.baseUrl).toBeUndefined(); // will be resolved via SDK + expect(result.baseUrl).toBe("https://api.anthropic.com"); + expect(result.api).toBe("anthropic-messages"); }); it("inline config provider with baseUrl is fully resolved", () => { @@ -489,12 +490,12 @@ describe("guardian index — resolveModelFromConfig", () => { expect(result.apiKey).toBe("custom-key"); }); - it("preserves api type from config even without baseUrl", () => { + it("falls back to pi-ai database when config has empty baseUrl", () => { const result = resolveModelFromConfig("anthropic", "claude-haiku-4-5", { models: { providers: { anthropic: { - baseUrl: "", // empty — treated as missing + baseUrl: "", // empty — falls through to pi-ai api: "anthropic-messages", models: [], }, @@ -502,7 +503,8 @@ describe("guardian index — resolveModelFromConfig", () => { }, }); - expect(result.baseUrl).toBeUndefined(); + // pi-ai resolves the baseUrl for known providers + expect(result.baseUrl).toBe("https://api.anthropic.com"); expect(result.api).toBe("anthropic-messages"); }); }); diff --git a/extensions/guardian/index.ts b/extensions/guardian/index.ts index d3324b0508d..20f35cdc481 100644 --- a/extensions/guardian/index.ts +++ b/extensions/guardian/index.ts @@ -1,3 +1,4 @@ +import { getModels as piGetModels } from "@mariozechner/pi-ai"; import type { OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core"; import type { OpenClawConfig } from "openclaw/plugin-sdk/core"; import { callGuardian } from "./guardian-client.js"; @@ -334,8 +335,25 @@ function resolveModelFromConfig( }; } - // No explicit provider config — return partial model. - // baseUrl and api will be resolved lazily via SDK's resolveProviderInfo. + // No explicit provider config — try pi-ai's built-in model database. + // This covers well-known providers (anthropic, openai, google, etc.) + // that don't need explicit baseUrl config. + try { + const knownModels = piGetModels(provider as Parameters[0]); + if (knownModels.length > 0) { + const match = knownModels.find((m) => m.id === modelId) ?? knownModels[0]; + return { + provider, + modelId, + baseUrl: match.baseUrl, + api: match.api, + headers: extractStringHeaders(providerConfig?.headers, match.headers), + }; + } + } catch { + // Provider not in pi-ai's database — fall through + } + return { provider, modelId,