fix(minimax): resolve portal OAuth token via resolveProviderAuth with oauthMarker (#79731)

Root cause: resolvePortalCatalog manually called ensureAuthProfileStore +
listProfilesForProvider to detect OAuth profiles, then returned the raw
MINIMAX_OAUTH_MARKER string as the catalog apiKey. The request layer has
no handler for the marker string — it reached the provider as a literal
API key value, causing "No API key found" even with valid OAuth tokens.

Fix: use ctx.resolveProviderAuth(PORTAL_PROVIDER_ID, { oauthMarker })
matching the pattern used by the chutes extension. The resolver returns
the marker when an OAuth profile exists, which the request layer correctly
intercepts and replaces with the live access token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hclsys
2026-05-09 16:55:41 +08:00
committed by Radek Sienkiewicz
parent 0120fd3a60
commit 39953ab72a
2 changed files with 35 additions and 11 deletions

View File

@@ -4,6 +4,7 @@ import {
registerProviderPlugin,
requireRegisteredProvider,
} from "openclaw/plugin-sdk/plugin-test-runtime";
import { MINIMAX_OAUTH_MARKER } from "openclaw/plugin-sdk/provider-auth";
import { describe, expect, it, vi } from "vitest";
import { registerMinimaxProviders } from "./provider-registration.js";
import { createMiniMaxWebSearchProvider } from "./src/minimax-web-search-provider.js";
@@ -325,6 +326,33 @@ describe("minimax provider hooks", () => {
expect(result?.windows).toEqual([{ label: "5h", usedPercent: 2, resetAt: undefined }]);
});
it("portal catalog resolves OAuth token via resolveProviderAuth with oauthMarker", async () => {
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,
id: "minimax",
name: "MiniMax Provider",
});
const portalProvider = requireRegisteredProvider(providers, "minimax-portal");
const resolveProviderAuth = vi.fn(() => ({
apiKey: MINIMAX_OAUTH_MARKER,
mode: "oauth" as const,
source: "profile" as const,
}));
const result = await portalProvider.catalog?.run({
config: {},
resolveProviderAuth,
resolveProviderApiKey: () => ({ apiKey: undefined }),
} as never);
expect(resolveProviderAuth).toHaveBeenCalledWith("minimax-portal", {
oauthMarker: MINIMAX_OAUTH_MARKER,
});
expect(result).not.toBeNull();
if (result && "provider" in result) {
expect(result.provider.apiKey).toBe(MINIMAX_OAUTH_MARKER);
}
});
it("writes api and authHeader into the MiniMax portal OAuth config patch", async () => {
const { providers } = await registerProviderPlugin({
plugin: minimaxProviderPlugin,

View File

@@ -6,11 +6,7 @@ import type {
ProviderAuthResult,
ProviderCatalogContext,
} from "openclaw/plugin-sdk/plugin-entry";
import {
MINIMAX_OAUTH_MARKER,
ensureAuthProfileStore,
listProfilesForProvider,
} from "openclaw/plugin-sdk/provider-auth";
import { MINIMAX_OAUTH_MARKER } from "openclaw/plugin-sdk/provider-auth";
import { buildOauthProviderAuthResult } from "openclaw/plugin-sdk/provider-auth";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth-api-key";
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
@@ -100,13 +96,13 @@ function resolveApiCatalog(ctx: ProviderCatalogContext) {
function resolvePortalCatalog(ctx: ProviderCatalogContext) {
const explicitProvider = ctx.config.models?.providers?.[PORTAL_PROVIDER_ID];
const envApiKey = ctx.resolveProviderApiKey(PORTAL_PROVIDER_ID).apiKey;
const authStore = ensureAuthProfileStore(ctx.agentDir, {
allowKeychainPrompt: false,
});
const hasProfiles = listProfilesForProvider(authStore, PORTAL_PROVIDER_ID).length > 0;
const explicitApiKey = normalizeOptionalString(explicitProvider?.apiKey);
const apiKey = envApiKey ?? explicitApiKey ?? (hasProfiles ? MINIMAX_OAUTH_MARKER : undefined);
// resolveProviderAuth handles OAuth profiles via oauthMarker, returning the
// sentinel so the request layer can swap in the live access token (#79731).
const { apiKey: profileApiKey } = ctx.resolveProviderAuth(PORTAL_PROVIDER_ID, {
oauthMarker: MINIMAX_OAUTH_MARKER,
});
const apiKey = explicitApiKey ?? profileApiKey;
if (!apiKey) {
return null;
}