mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 21:03:34 +00:00
fix(providers): cover ClawRouter runtime auth paths
This commit is contained in:
@@ -1,9 +1,51 @@
|
||||
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
|
||||
import { capturePluginRegistration } from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { clearLiveCatalogCacheForTests } from "openclaw/plugin-sdk/provider-catalog-live-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const providerAuthRuntimeMocks = vi.hoisted(() => ({
|
||||
resolveApiKeyForProvider: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => providerAuthRuntimeMocks);
|
||||
|
||||
import plugin from "./index.js";
|
||||
|
||||
const LIVE_CATALOG = {
|
||||
providers: [
|
||||
{
|
||||
id: "openai",
|
||||
displayName: "OpenAI",
|
||||
openaiCompatible: true,
|
||||
nativeBaseUrl: "/v1/native/openai",
|
||||
routes: [
|
||||
{
|
||||
path: "/v1/responses",
|
||||
methods: ["POST"],
|
||||
requestFormat: "openai.responses",
|
||||
},
|
||||
],
|
||||
models: [
|
||||
{
|
||||
id: "openai/gpt-5.5-mini",
|
||||
upstream: "gpt-5.5-mini",
|
||||
capabilities: ["llm.responses"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("clawrouter provider plugin", () => {
|
||||
beforeEach(() => {
|
||||
clearLiveCatalogCacheForTests();
|
||||
providerAuthRuntimeMocks.resolveApiKeyForProvider.mockReset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it("registers managed proxy-key auth and transport routing hooks", () => {
|
||||
const captured = capturePluginRegistration(plugin);
|
||||
const provider = captured.providers[0];
|
||||
@@ -17,6 +59,7 @@ describe("clawrouter provider plugin", () => {
|
||||
buildReplayPolicy: expect.any(Function),
|
||||
normalizeResolvedModel: expect.any(Function),
|
||||
sanitizeReplayHistory: expect.any(Function),
|
||||
wrapSimpleCompletionStreamFn: expect.any(Function),
|
||||
wrapStreamFn: expect.any(Function),
|
||||
});
|
||||
expect(provider?.auth[0]).toMatchObject({
|
||||
@@ -24,6 +67,7 @@ describe("clawrouter provider plugin", () => {
|
||||
label: "ClawRouter proxy key",
|
||||
kind: "api_key",
|
||||
});
|
||||
expect(provider?.wrapSimpleCompletionStreamFn).toBe(provider?.wrapStreamFn);
|
||||
});
|
||||
|
||||
it("attaches the resolved proxy key only when dispatching a request", () => {
|
||||
@@ -66,6 +110,53 @@ describe("clawrouter provider plugin", () => {
|
||||
expect(calls[1]?.headers).toBeUndefined();
|
||||
});
|
||||
|
||||
it("resolves managed secret refs before credential-scoped discovery", async () => {
|
||||
providerAuthRuntimeMocks.resolveApiKeyForProvider.mockResolvedValue({
|
||||
apiKey: "resolved-proxy-key",
|
||||
mode: "api-key",
|
||||
source: "models.json secretref",
|
||||
});
|
||||
const fetchMock = vi.fn(async () => Response.json(LIVE_CATALOG));
|
||||
vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch);
|
||||
const provider = capturePluginRegistration(plugin).providers[0];
|
||||
|
||||
const result = await provider?.catalog?.run({
|
||||
config: { models: {} },
|
||||
agentDir: "/agent",
|
||||
workspaceDir: "/workspace",
|
||||
env: {},
|
||||
resolveProviderAuth: () => ({
|
||||
apiKey: "secretref-managed",
|
||||
discoveryApiKey: undefined,
|
||||
mode: "api_key",
|
||||
source: "profile",
|
||||
profileId: "clawrouter-profile",
|
||||
}),
|
||||
resolveProviderApiKey: () => ({
|
||||
apiKey: "secretref-managed",
|
||||
discoveryApiKey: undefined,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result || !("provider" in result)) {
|
||||
throw new Error("expected ClawRouter catalog provider result");
|
||||
}
|
||||
expect(result.provider.apiKey).toBe("secretref-managed");
|
||||
expect(result.provider.models.map((model) => model.id)).toEqual(["openai/gpt-5.5-mini"]);
|
||||
expect(providerAuthRuntimeMocks.resolveApiKeyForProvider).toHaveBeenCalledWith({
|
||||
provider: "clawrouter",
|
||||
cfg: { models: {} },
|
||||
agentDir: "/agent",
|
||||
workspaceDir: "/workspace",
|
||||
profileId: "clawrouter-profile",
|
||||
lockedProfile: true,
|
||||
});
|
||||
const fetchCall = fetchMock.mock.calls[0] as unknown as [string, RequestInit] | undefined;
|
||||
expect(new Headers(fetchCall?.[1]?.headers).get("Authorization")).toBe(
|
||||
"Bearer resolved-proxy-key",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes configured ClawRouter roots to the API base URL", () => {
|
||||
const provider = capturePluginRegistration(plugin).providers[0];
|
||||
const normalized = provider?.normalizeConfig?.({
|
||||
|
||||
@@ -58,9 +58,27 @@ export default definePluginEntry({
|
||||
catalog: {
|
||||
order: "simple",
|
||||
run: async (ctx) => {
|
||||
const auth = ctx.resolveProviderApiKey(PROVIDER_ID);
|
||||
const apiKey = auth.apiKey ?? auth.discoveryApiKey;
|
||||
if (!apiKey) {
|
||||
const auth = ctx.resolveProviderAuth(PROVIDER_ID);
|
||||
let discoveryApiKey = auth.discoveryApiKey;
|
||||
if (!discoveryApiKey) {
|
||||
try {
|
||||
const { resolveApiKeyForProvider } =
|
||||
await import("openclaw/plugin-sdk/provider-auth-runtime");
|
||||
discoveryApiKey = (
|
||||
await resolveApiKeyForProvider({
|
||||
provider: PROVIDER_ID,
|
||||
cfg: ctx.config,
|
||||
...(ctx.agentDir ? { agentDir: ctx.agentDir } : {}),
|
||||
...(ctx.workspaceDir ? { workspaceDir: ctx.workspaceDir } : {}),
|
||||
...(auth.profileId ? { profileId: auth.profileId, lockedProfile: true } : {}),
|
||||
})
|
||||
)?.apiKey;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
const apiKey = auth.apiKey ?? discoveryApiKey;
|
||||
if (!apiKey || !discoveryApiKey) {
|
||||
return null;
|
||||
}
|
||||
const configuredBaseUrl = ctx.config.models?.providers?.[PROVIDER_ID]?.baseUrl;
|
||||
@@ -68,7 +86,7 @@ export default definePluginEntry({
|
||||
return {
|
||||
provider: await buildClawRouterProviderConfig({
|
||||
apiKey,
|
||||
discoveryApiKey: auth.discoveryApiKey,
|
||||
discoveryApiKey,
|
||||
baseUrl: configuredBaseUrl,
|
||||
}),
|
||||
};
|
||||
@@ -82,6 +100,7 @@ export default definePluginEntry({
|
||||
return baseUrl !== providerConfig.baseUrl ? { ...providerConfig, baseUrl } : undefined;
|
||||
},
|
||||
normalizeResolvedModel: ({ model }) => normalizeClawRouterResolvedModel(model),
|
||||
wrapSimpleCompletionStreamFn: wrapClawRouterProviderStream,
|
||||
wrapStreamFn: wrapClawRouterProviderStream,
|
||||
buildReplayPolicy: ({ modelApi, modelId }) => {
|
||||
if (modelApi === "anthropic-messages") {
|
||||
|
||||
Reference in New Issue
Block a user