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) <noreply@anthropic.com>
This commit is contained in:
ShengtongZhu 2026-03-15 19:33:14 +08:00
parent 474a41a3ee
commit e55c4c4044
2 changed files with 27 additions and 7 deletions

View File

@ -463,12 +463,13 @@ describe("guardian index — resolveModelFromConfig", () => {
expect(result.api).toBe("openai-completions"); // default 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", {}); const result = resolveModelFromConfig("anthropic", "claude-haiku-4-5", {});
expect(result).toBeDefined(); expect(result).toBeDefined();
expect(result.provider).toBe("anthropic"); expect(result.provider).toBe("anthropic");
expect(result.modelId).toBe("claude-haiku-4-5"); 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", () => { it("inline config provider with baseUrl is fully resolved", () => {
@ -489,12 +490,12 @@ describe("guardian index — resolveModelFromConfig", () => {
expect(result.apiKey).toBe("custom-key"); 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", { const result = resolveModelFromConfig("anthropic", "claude-haiku-4-5", {
models: { models: {
providers: { providers: {
anthropic: { anthropic: {
baseUrl: "", // empty — treated as missing baseUrl: "", // empty — falls through to pi-ai
api: "anthropic-messages", api: "anthropic-messages",
models: [], 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"); expect(result.api).toBe("anthropic-messages");
}); });
}); });

View File

@ -1,3 +1,4 @@
import { getModels as piGetModels } from "@mariozechner/pi-ai";
import type { OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core"; import type { OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/core"; import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
import { callGuardian } from "./guardian-client.js"; import { callGuardian } from "./guardian-client.js";
@ -334,8 +335,25 @@ function resolveModelFromConfig(
}; };
} }
// No explicit provider config — return partial model. // No explicit provider config — try pi-ai's built-in model database.
// baseUrl and api will be resolved lazily via SDK's resolveProviderInfo. // This covers well-known providers (anthropic, openai, google, etc.)
// that don't need explicit baseUrl config.
try {
const knownModels = piGetModels(provider as Parameters<typeof piGetModels>[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 { return {
provider, provider,
modelId, modelId,