Files
openclaw/src/agents/auth-profiles/external-cli-sync.ts
pomelo dad68d319b Remove Qwen OAuth integration (qwen-portal-auth) (#52709)
* Remove Qwen OAuth integration (qwen-portal-auth)

Qwen OAuth via portal.qwen.ai is being deprecated by the Qwen team due
to traffic impact on their primary Qwen Code user base. Users should
migrate to the officially supported Model Studio (Alibaba Cloud Coding
Plan) provider instead.

Ref: https://github.com/openclaw/openclaw/issues/49557

- Delete extensions/qwen-portal-auth/ plugin entirely
- Remove qwen-portal from onboarding auth choices, provider aliases,
  auto-enable list, bundled plugin defaults, and pricing cache
- Remove Qwen CLI credential sync (external-cli-sync, cli-credentials)
- Remove QWEN_OAUTH_MARKER from model auth markers
- Update docs/providers/qwen.md to redirect to Model Studio
- Update model-providers docs (EN + zh-CN) to remove Qwen OAuth section
- Regenerate config and plugin-sdk baselines
- Update all affected tests

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* Clean up residual qwen-portal references after OAuth removal

* Add migration hint for deprecated qwen-portal OAuth provider

* fix: finish qwen oauth removal follow-up

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
2026-03-26 16:32:34 +08:00

142 lines
3.8 KiB
TypeScript

import {
readCodexCliCredentialsCached,
readMiniMaxCliCredentialsCached,
} from "../cli-credentials.js";
import {
EXTERNAL_CLI_SYNC_TTL_MS,
OPENAI_CODEX_DEFAULT_PROFILE_ID,
MINIMAX_CLI_PROFILE_ID,
log,
} from "./constants.js";
import type { AuthProfileStore, OAuthCredential } from "./types.js";
type ExternalCliSyncOptions = {
log?: boolean;
};
type ExternalCliSyncProvider = {
profileId: string;
provider: string;
readCredentials: () => OAuthCredential | null;
};
function areOAuthCredentialsEquivalent(
a: OAuthCredential | undefined,
b: OAuthCredential,
): boolean {
if (!a) {
return false;
}
if (a.type !== "oauth") {
return false;
}
return (
a.provider === b.provider &&
a.access === b.access &&
a.refresh === b.refresh &&
a.expires === b.expires &&
a.email === b.email &&
a.enterpriseUrl === b.enterpriseUrl &&
a.projectId === b.projectId &&
a.accountId === b.accountId
);
}
function hasNewerStoredOAuthCredential(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
return Boolean(
existing &&
existing.provider === incoming.provider &&
Number.isFinite(existing.expires) &&
(!Number.isFinite(incoming.expires) || existing.expires > incoming.expires),
);
}
export function shouldReplaceStoredOAuthCredential(
existing: OAuthCredential | undefined,
incoming: OAuthCredential,
): boolean {
if (!existing || existing.type !== "oauth") {
return true;
}
if (areOAuthCredentialsEquivalent(existing, incoming)) {
return false;
}
return !hasNewerStoredOAuthCredential(existing, incoming);
}
const EXTERNAL_CLI_SYNC_PROVIDERS: ExternalCliSyncProvider[] = [
{
profileId: MINIMAX_CLI_PROFILE_ID,
provider: "minimax-portal",
readCredentials: () => readMiniMaxCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }),
},
{
profileId: OPENAI_CODEX_DEFAULT_PROFILE_ID,
provider: "openai-codex",
readCredentials: () => readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS }),
},
];
/** Sync external CLI credentials into the store for a given provider. */
function syncExternalCliCredentialsForProvider(
store: AuthProfileStore,
providerConfig: ExternalCliSyncProvider,
options: ExternalCliSyncOptions,
): boolean {
const { profileId, provider, readCredentials } = providerConfig;
const existing = store.profiles[profileId];
const creds = readCredentials();
if (!creds) {
return false;
}
const existingOAuth = existing?.type === "oauth" ? existing : undefined;
if (!shouldReplaceStoredOAuthCredential(existingOAuth, creds)) {
if (options.log !== false) {
if (!areOAuthCredentialsEquivalent(existingOAuth, creds) && existingOAuth) {
log.debug(`kept newer stored ${provider} credentials over external cli sync`, {
profileId,
storedExpires: new Date(existingOAuth.expires).toISOString(),
externalExpires: Number.isFinite(creds.expires)
? new Date(creds.expires).toISOString()
: null,
});
}
}
return false;
}
store.profiles[profileId] = creds;
if (options.log !== false) {
log.info(`synced ${provider} credentials from external cli`, {
profileId,
expires: new Date(creds.expires).toISOString(),
});
}
return true;
}
/**
* Sync OAuth credentials from external CLI tools (MiniMax CLI, Codex CLI)
* into the store.
*
* Returns true if any credentials were updated.
*/
export function syncExternalCliCredentials(
store: AuthProfileStore,
options: ExternalCliSyncOptions = {},
): boolean {
let mutated = false;
for (const provider of EXTERNAL_CLI_SYNC_PROVIDERS) {
if (syncExternalCliCredentialsForProvider(store, provider, options)) {
mutated = true;
}
}
return mutated;
}