fix minimax oauth failed and upgrade openclaw to 2.26 (#206)

This commit is contained in:
paisley
2026-02-27 19:05:56 +08:00
committed by GitHub
Unverified
parent f70d5b0c28
commit 0fb1a1a78d
5 changed files with 495 additions and 897 deletions

View File

@@ -1167,8 +1167,8 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
: 'openai-completions';
let baseUrl = provider.baseUrl || defaultBaseUrl;
if ((provider.type === 'minimax-portal' || provider.type === 'minimax-portal-cn') && baseUrl && !baseUrl.endsWith('/anthropic')) {
baseUrl = baseUrl.replace(/\/$/, '') + '/anthropic';
if ((provider.type === 'minimax-portal' || provider.type === 'minimax-portal-cn') && baseUrl) {
baseUrl = baseUrl.replace(/\/v1$/, '').replace(/\/anthropic$/, '').replace(/\/$/, '') + '/anthropic';
}
// To ensure the OpenClaw Gateway's internal token refresher works,
@@ -1180,10 +1180,27 @@ function registerProviderHandlers(gatewayManager: GatewayManager): void {
setOpenClawDefaultModelWithOverride(targetProviderKey, undefined, {
baseUrl,
api,
authHeader: targetProviderKey === 'minimax-portal' ? true : undefined,
// Relies on OpenClaw Gateway native auth-profiles syncing
apiKeyEnv: targetProviderKey === 'minimax-portal' ? 'minimax-oauth' : 'qwen-oauth',
});
logger.info(`Configured openclaw.json for OAuth provider "${provider.type}"`);
// Also write models.json directly so pi-ai picks up the correct baseUrl and
// authHeader immediately, without waiting for Gateway to sync openclaw.json.
try {
const defaultModelId = provider.model?.split('/').pop();
updateAgentModelProvider(targetProviderKey, {
baseUrl,
api,
authHeader: targetProviderKey === 'minimax-portal' ? true : undefined,
apiKey: targetProviderKey === 'minimax-portal' ? 'minimax-oauth' : 'qwen-oauth',
models: defaultModelId ? [{ id: defaultModelId, name: defaultModelId }] : [],
});
} catch (err) {
logger.warn(`Failed to update models.json for OAuth provider "${targetProviderKey}":`, err);
}
}
// For custom/ollama providers, also update the per-agent models.json

View File

@@ -131,7 +131,7 @@ class DeviceOAuthManager extends EventEmitter {
expires: token.expires,
// MiniMax returns a per-account resourceUrl as the API base URL
resourceUrl: token.resourceUrl,
// MiniMax uses Anthropic Messages API format
// Revert back to anthropic-messages
api: 'anthropic-messages',
region,
});
@@ -217,17 +217,15 @@ class DeviceOAuthManager extends EventEmitter {
// This mirrors what the OpenClaw plugin's configPatch does after CLI login.
// The baseUrl comes from token.resourceUrl (per-account URL from the OAuth server)
// or falls back to the provider's default public endpoint.
// Note: MiniMax Anthropic-compatible API requires the /anthropic suffix.
const defaultBaseUrl = providerType === 'minimax-portal'
? 'https://api.minimax.io/anthropic'
: (providerType === 'minimax-portal-cn' ? 'https://api.minimaxi.com/anthropic' : 'https://portal.qwen.ai/v1');
let baseUrl = token.resourceUrl || defaultBaseUrl;
// If MiniMax returned a resourceUrl (e.g. https://api.minimax.io) but no /anthropic suffix,
// we must append it because we use the 'anthropic-messages' API mode
if (providerType.startsWith('minimax-portal') && baseUrl && !baseUrl.endsWith('/anthropic')) {
baseUrl = baseUrl.replace(/\/$/, '') + '/anthropic';
// Ensure the base URL ends with /anthropic
if (providerType.startsWith('minimax-portal') && baseUrl) {
baseUrl = baseUrl.replace(/\/v1$/, '').replace(/\/anthropic$/, '').replace(/\/$/, '') + '/anthropic';
}
try {
@@ -235,11 +233,10 @@ class DeviceOAuthManager extends EventEmitter {
setOpenClawDefaultModelWithOverride(tokenProviderId, undefined, {
baseUrl,
api: token.api,
// Tells OpenClaw's anthropic adapter to use `Authorization: Bearer` instead of `x-api-key`
authHeader: providerType.startsWith('minimax-portal') ? true : undefined,
// OAuth placeholder — tells Gateway to resolve credentials
// from auth-profiles.json (type: 'oauth') instead of a static API key.
// This matches what the OpenClaw plugin's configPatch writes:
// minimax-portal → apiKey: 'minimax-oauth'
// qwen-portal → apiKey: 'qwen-oauth'
apiKeyEnv: tokenProviderId === 'minimax-portal' ? 'minimax-oauth' : 'qwen-oauth',
});
} catch (err) {

View File

@@ -153,6 +153,32 @@ export function saveOAuthTokenToOpenClaw(
console.log(`Saved OAuth token for provider "${provider}" to OpenClaw auth-profiles (agents: ${agentIds.join(', ')})`);
}
/**
* Retrieve an OAuth token from OpenClaw's auth-profiles.json.
* Useful when the Gateway does not natively inject the Authorization header.
*
* @param provider - Provider type (e.g., 'minimax-portal')
* @param agentId - Optional single agent ID to read from, defaults to 'main'
* @returns The OAuth token access string or null if not found
*/
export function getOAuthTokenFromOpenClaw(
provider: string,
agentId = 'main'
): string | null {
try {
const store = readAuthProfiles(agentId);
const profileId = `${provider}:default`;
const profile = store.profiles[profileId];
if (profile && profile.type === 'oauth' && 'access' in profile) {
return (profile as OAuthProfileEntry).access;
}
} catch (err) {
console.warn(`[getOAuthToken] Failed to read token for ${provider}:`, err);
}
return null;
}
/**
* Save a provider API key to OpenClaw's auth-profiles.json
* This writes the key in the format OpenClaw expects so the gateway
@@ -278,7 +304,25 @@ export function removeProviderFromOpenClaw(provider: string): void {
}
}
// 2. Remove from openclaw.json
// 2. Remove from models.json (per-agent model registry used by pi-ai directly)
for (const agentId of agentIds) {
const modelsPath = join(homedir(), '.openclaw', 'agents', agentId, 'agent', 'models.json');
try {
if (existsSync(modelsPath)) {
const data = JSON.parse(readFileSync(modelsPath, 'utf-8')) as Record<string, unknown>;
const providers = data.providers as Record<string, unknown> | undefined;
if (providers && providers[provider]) {
delete providers[provider];
writeFileSync(modelsPath, JSON.stringify(data, null, 2), 'utf-8');
console.log(`Removed models.json entry for provider "${provider}" (agent "${agentId}")`);
}
}
} catch (err) {
console.warn(`Failed to remove provider ${provider} from models.json (agent "${agentId}"):`, err);
}
}
// 3. Remove from openclaw.json
const configPath = join(homedir(), '.openclaw', 'openclaw.json');
try {
if (existsSync(configPath)) {
@@ -447,6 +491,7 @@ interface RuntimeProviderConfigOverride {
api?: string;
apiKeyEnv?: string;
headers?: Record<string, string>;
authHeader?: boolean;
}
/**
@@ -573,6 +618,9 @@ export function setOpenClawDefaultModelWithOverride(
if (override.headers && Object.keys(override.headers).length > 0) {
nextProvider.headers = override.headers;
}
if (override.authHeader !== undefined) {
nextProvider.authHeader = override.authHeader;
}
providers[provider] = nextProvider;
models.providers = providers;
@@ -766,6 +814,8 @@ export function updateAgentModelProvider(
api?: string;
models?: Array<{ id: string; name: string }>;
apiKey?: string;
/** When true, pi-ai sends Authorization: Bearer instead of x-api-key */
authHeader?: boolean;
}
): void {
const agentIds = discoverAgentIds();
@@ -804,6 +854,7 @@ export function updateAgentModelProvider(
if (entry.api !== undefined) existing.api = entry.api;
if (mergedModels.length > 0) existing.models = mergedModels;
if (entry.apiKey !== undefined) existing.apiKey = entry.apiKey;
if (entry.authHeader !== undefined) existing.authHeader = entry.authHeader;
providers[providerType] = existing;
data.providers = providers;

View File

@@ -84,7 +84,7 @@
"i18next": "^25.8.11",
"jsdom": "^28.1.0",
"lucide-react": "^0.563.0",
"openclaw": "2026.2.24",
"openclaw": "2026.2.26",
"png2icons": "^2.0.1",
"postcss": "^8.5.6",
"react": "^19.2.4",
@@ -107,4 +107,4 @@
"zx": "^8.8.5"
},
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8"
}
}

1299
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff