mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-29 02:41:07 +00:00
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>
This commit is contained in:
@@ -30,7 +30,6 @@ export const BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES = {
|
||||
openrouter: ["OPENROUTER_API_KEY"],
|
||||
perplexity: ["PERPLEXITY_API_KEY", "OPENROUTER_API_KEY"],
|
||||
qianfan: ["QIANFAN_API_KEY"],
|
||||
"qwen-portal": ["QWEN_OAUTH_TOKEN", "QWEN_PORTAL_API_KEY"],
|
||||
sglang: ["SGLANG_API_KEY"],
|
||||
synthetic: ["SYNTHETIC_API_KEY"],
|
||||
tavily: ["TAVILY_API_KEY"],
|
||||
|
||||
@@ -34,10 +34,6 @@ describe("bundled provider auth env vars", () => {
|
||||
"PERPLEXITY_API_KEY",
|
||||
"OPENROUTER_API_KEY",
|
||||
]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES["qwen-portal"]).toEqual([
|
||||
"QWEN_OAUTH_TOKEN",
|
||||
"QWEN_PORTAL_API_KEY",
|
||||
]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES.tavily).toEqual(["TAVILY_API_KEY"]);
|
||||
expect(BUNDLED_PROVIDER_AUTH_ENV_VAR_CANDIDATES["minimax-portal"]).toEqual([
|
||||
"MINIMAX_OAUTH_TOKEN",
|
||||
|
||||
@@ -52,7 +52,6 @@ export const BUNDLED_ENABLED_BY_DEFAULT = new Set<string>([
|
||||
"openrouter",
|
||||
"phone-control",
|
||||
"qianfan",
|
||||
"qwen-portal-auth",
|
||||
"sglang",
|
||||
"synthetic",
|
||||
"talk-voice",
|
||||
|
||||
@@ -1,18 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
createAuthTestLifecycle,
|
||||
createExitThrowingRuntime,
|
||||
createWizardPrompter,
|
||||
readAuthProfilesForAgent,
|
||||
requireOpenClawAgentDir,
|
||||
setupAuthTestEnv,
|
||||
} from "../../../test/helpers/auth-wizard.js";
|
||||
import { clearRuntimeAuthProfileStoreSnapshots } from "../../agents/auth-profiles/store.js";
|
||||
import { resolvePreferredProviderForAuthChoice } from "../../plugins/provider-auth-choice-preference.js";
|
||||
import { runProviderPluginAuthMethod } from "../../plugins/provider-auth-choice.js";
|
||||
import { buildProviderPluginMethodChoice } from "../provider-wizard.js";
|
||||
import { requireProviderContractProvider, uniqueProviderContractProviders } from "./registry.js";
|
||||
import { registerProviders, requireProvider } from "./testkit.js";
|
||||
|
||||
type ResolvePluginProviders =
|
||||
typeof import("../../plugins/provider-auth-choice.runtime.js").resolvePluginProviders;
|
||||
@@ -20,53 +10,19 @@ type ResolveProviderPluginChoice =
|
||||
typeof import("../../plugins/provider-auth-choice.runtime.js").resolveProviderPluginChoice;
|
||||
type RunProviderModelSelectedHook =
|
||||
typeof import("../../plugins/provider-auth-choice.runtime.js").runProviderModelSelectedHook;
|
||||
const loginQwenPortalOAuthMock = vi.hoisted(() => vi.fn());
|
||||
const githubCopilotLoginCommandMock = vi.hoisted(() => vi.fn());
|
||||
const resolvePluginProvidersMock = vi.hoisted(() => vi.fn<ResolvePluginProviders>(() => []));
|
||||
const resolveProviderPluginChoiceMock = vi.hoisted(() => vi.fn<ResolveProviderPluginChoice>());
|
||||
const runProviderModelSelectedHookMock = vi.hoisted(() =>
|
||||
vi.fn<RunProviderModelSelectedHook>(async () => {}),
|
||||
);
|
||||
import qwenPortalPlugin from "../../../extensions/qwen-portal-auth/index.js";
|
||||
|
||||
vi.mock("../../../extensions/qwen-portal-auth/oauth.js", () => ({
|
||||
loginQwenPortalOAuth: loginQwenPortalOAuthMock,
|
||||
}));
|
||||
vi.mock("../../../extensions/github-copilot/login.js", () => ({
|
||||
githubCopilotLoginCommand: githubCopilotLoginCommandMock,
|
||||
}));
|
||||
vi.mock("../../plugins/provider-auth-choice.runtime.js", () => ({
|
||||
resolvePluginProviders: resolvePluginProvidersMock,
|
||||
resolveProviderPluginChoice: resolveProviderPluginChoiceMock,
|
||||
runProviderModelSelectedHook: runProviderModelSelectedHookMock,
|
||||
}));
|
||||
|
||||
type StoredAuthProfile = {
|
||||
type?: string;
|
||||
provider?: string;
|
||||
access?: string;
|
||||
refresh?: string;
|
||||
key?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
describe("provider auth-choice contract", () => {
|
||||
const lifecycle = createAuthTestLifecycle([
|
||||
"OPENCLAW_STATE_DIR",
|
||||
"OPENCLAW_AGENT_DIR",
|
||||
"PI_CODING_AGENT_DIR",
|
||||
]);
|
||||
let activeStateDir: string | null = null;
|
||||
|
||||
async function setupTempState() {
|
||||
if (activeStateDir) {
|
||||
await lifecycle.cleanup();
|
||||
}
|
||||
const env = await setupAuthTestEnv("openclaw-provider-auth-choice-");
|
||||
activeStateDir = env.stateDir;
|
||||
lifecycle.setStateDir(env.stateDir);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resolvePluginProvidersMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReturnValue(uniqueProviderContractProviders);
|
||||
@@ -90,22 +46,17 @@ describe("provider auth-choice contract", () => {
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
loginQwenPortalOAuthMock.mockReset();
|
||||
githubCopilotLoginCommandMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReset();
|
||||
resolvePluginProvidersMock.mockReturnValue([]);
|
||||
resolveProviderPluginChoiceMock.mockReset();
|
||||
resolveProviderPluginChoiceMock.mockReturnValue(null);
|
||||
runProviderModelSelectedHookMock.mockReset();
|
||||
clearRuntimeAuthProfileStoreSnapshots();
|
||||
await lifecycle.cleanup();
|
||||
activeStateDir = null;
|
||||
});
|
||||
|
||||
it("maps provider-plugin choices through the shared preferred-provider fallback resolver", async () => {
|
||||
const pluginFallbackScenarios = [
|
||||
"github-copilot",
|
||||
"qwen-portal",
|
||||
"minimax-portal",
|
||||
"modelstudio",
|
||||
"ollama",
|
||||
@@ -131,114 +82,4 @@ describe("provider auth-choice contract", () => {
|
||||
);
|
||||
expect(resolvePluginProvidersMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs qwen portal auth through the shared plugin auth-method helper", async () => {
|
||||
await setupTempState();
|
||||
const qwenProvider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
loginQwenPortalOAuthMock.mockResolvedValueOnce({
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
resourceUrl: "portal.qwen.ai",
|
||||
});
|
||||
|
||||
const note = vi.fn(async () => {});
|
||||
const result = await runProviderPluginAuthMethod({
|
||||
config: {},
|
||||
prompter: createWizardPrompter({ note }),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
method: qwenProvider.auth[0],
|
||||
allowSecretRefPrompt: false,
|
||||
});
|
||||
|
||||
expect(result.config.auth?.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
provider: "qwen-portal",
|
||||
mode: "oauth",
|
||||
});
|
||||
expect(result.config.models?.providers?.["qwen-portal"]).toMatchObject({
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
models: [],
|
||||
});
|
||||
expect(result.config.agents?.defaults?.models).toMatchObject({
|
||||
"qwen-portal/coder-model": { alias: "qwen" },
|
||||
"qwen-portal/vision-model": {},
|
||||
});
|
||||
expect(result.defaultModel).toBe("qwen-portal/coder-model");
|
||||
expect(note).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Qwen OAuth tokens auto-refresh."),
|
||||
"Provider notes",
|
||||
);
|
||||
|
||||
const stored = await readAuthProfilesForAgent<{ profiles?: Record<string, StoredAuthProfile> }>(
|
||||
requireOpenClawAgentDir(),
|
||||
);
|
||||
expect(stored.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
});
|
||||
});
|
||||
|
||||
it("returns qwen portal default-model overrides for deferred callers", async () => {
|
||||
await setupTempState();
|
||||
const qwenProvider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
loginQwenPortalOAuthMock.mockResolvedValueOnce({
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
resourceUrl: "portal.qwen.ai",
|
||||
});
|
||||
|
||||
const result = await runProviderPluginAuthMethod({
|
||||
config: {},
|
||||
prompter: createWizardPrompter({}),
|
||||
runtime: createExitThrowingRuntime(),
|
||||
method: qwenProvider.auth[0],
|
||||
allowSecretRefPrompt: false,
|
||||
});
|
||||
|
||||
expect(githubCopilotLoginCommandMock).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
config: {
|
||||
agents: {
|
||||
defaults: {
|
||||
models: {
|
||||
"qwen-portal/coder-model": {
|
||||
alias: "qwen",
|
||||
},
|
||||
"qwen-portal/vision-model": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
profiles: {
|
||||
"qwen-portal:default": {
|
||||
provider: "qwen-portal",
|
||||
mode: "oauth",
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
"qwen-portal": {
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultModel: "qwen-portal/coder-model",
|
||||
});
|
||||
|
||||
const stored = await readAuthProfilesForAgent<{
|
||||
profiles?: Record<string, StoredAuthProfile>;
|
||||
}>(requireOpenClawAgentDir());
|
||||
expect(stored.profiles?.["qwen-portal:default"]).toMatchObject({
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,8 +12,6 @@ import { registerProviders, requireProvider } from "./testkit.js";
|
||||
|
||||
type LoginOpenAICodexOAuth =
|
||||
(typeof import("openclaw/plugin-sdk/provider-auth-login"))["loginOpenAICodexOAuth"];
|
||||
type LoginQwenPortalOAuth =
|
||||
(typeof import("../../../extensions/qwen-portal-auth/oauth.js"))["loginQwenPortalOAuth"];
|
||||
type GithubCopilotLoginCommand =
|
||||
(typeof import("openclaw/plugin-sdk/provider-auth-login"))["githubCopilotLoginCommand"];
|
||||
type CreateVpsAwareHandlers =
|
||||
@@ -24,7 +22,6 @@ type ListProfilesForProvider =
|
||||
typeof import("openclaw/plugin-sdk/agent-runtime").listProfilesForProvider;
|
||||
|
||||
const loginOpenAICodexOAuthMock = vi.hoisted(() => vi.fn<LoginOpenAICodexOAuth>());
|
||||
const loginQwenPortalOAuthMock = vi.hoisted(() => vi.fn<LoginQwenPortalOAuth>());
|
||||
const githubCopilotLoginCommandMock = vi.hoisted(() => vi.fn<GithubCopilotLoginCommand>());
|
||||
const ensureAuthProfileStoreMock = vi.hoisted(() => vi.fn<EnsureAuthProfileStore>());
|
||||
const listProfilesForProviderMock = vi.hoisted(() => vi.fn<ListProfilesForProvider>());
|
||||
@@ -47,13 +44,8 @@ vi.mock("openclaw/plugin-sdk/agent-runtime", async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../extensions/qwen-portal-auth/oauth.js", () => ({
|
||||
loginQwenPortalOAuth: loginQwenPortalOAuthMock,
|
||||
}));
|
||||
|
||||
import githubCopilotPlugin from "../../../extensions/github-copilot/index.js";
|
||||
import openAIPlugin from "../../../extensions/openai/index.js";
|
||||
import qwenPortalPlugin from "../../../extensions/qwen-portal-auth/index.js";
|
||||
|
||||
function buildPrompter(): WizardPrompter {
|
||||
const progress: WizardProgress = {
|
||||
@@ -114,7 +106,6 @@ describe("provider auth contract", () => {
|
||||
|
||||
afterEach(() => {
|
||||
loginOpenAICodexOAuthMock.mockReset();
|
||||
loginQwenPortalOAuthMock.mockReset();
|
||||
githubCopilotLoginCommandMock.mockReset();
|
||||
ensureAuthProfileStoreMock.mockReset();
|
||||
listProfilesForProviderMock.mockReset();
|
||||
@@ -377,50 +368,6 @@ describe("provider auth contract", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps Qwen portal OAuth auth results provider-owned", async () => {
|
||||
const provider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
loginQwenPortalOAuthMock.mockResolvedValueOnce({
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
resourceUrl: "portal.qwen.ai",
|
||||
});
|
||||
|
||||
const result = await provider.auth[0]?.run(buildAuthContext() as never);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
profiles: [
|
||||
{
|
||||
profileId: "qwen-portal:default",
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: 1_700_000_000_000,
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultModel: "qwen-portal/coder-model",
|
||||
configPatch: {
|
||||
models: {
|
||||
providers: {
|
||||
"qwen-portal": {
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result?.notes).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining("auto-refresh"),
|
||||
expect.stringContaining("Base URL defaults"),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps GitHub Copilot device auth results provider-owned", async () => {
|
||||
const provider = requireProvider(registerProviders(githubCopilotPlugin), "github-copilot");
|
||||
authStore.profiles["github-copilot:github"] = {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
|
||||
import { QWEN_OAUTH_MARKER } from "../../agents/model-auth-markers.js";
|
||||
import type { ModelDefinitionConfig } from "../../config/types.models.js";
|
||||
import { registerProviders, requireProvider } from "./testkit.js";
|
||||
|
||||
@@ -12,7 +11,6 @@ const ensureAuthProfileStoreMock = vi.hoisted(() => vi.fn());
|
||||
const listProfilesForProviderMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
let runProviderCatalog: typeof import("../provider-discovery.js").runProviderCatalog;
|
||||
let qwenPortalProvider: Awaited<ReturnType<typeof requireProvider>>;
|
||||
let githubCopilotProvider: Awaited<ReturnType<typeof requireProvider>>;
|
||||
let ollamaProvider: Awaited<ReturnType<typeof requireProvider>>;
|
||||
let vllmProvider: Awaited<ReturnType<typeof requireProvider>>;
|
||||
@@ -53,21 +51,6 @@ function setRuntimeAuthStore(store?: AuthProfileStore) {
|
||||
);
|
||||
}
|
||||
|
||||
function setQwenPortalOauthSnapshot() {
|
||||
setRuntimeAuthStore({
|
||||
version: 1,
|
||||
profiles: {
|
||||
"qwen-portal:default": {
|
||||
type: "oauth",
|
||||
provider: "qwen-portal",
|
||||
access: "access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() + 60_000,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setGithubCopilotProfileSnapshot() {
|
||||
setRuntimeAuthStore({
|
||||
version: 1,
|
||||
@@ -169,7 +152,6 @@ describe("provider discovery contract", () => {
|
||||
|
||||
({ runProviderCatalog } = await import("../provider-discovery.js"));
|
||||
const [
|
||||
{ default: qwenPortalPlugin },
|
||||
{ default: githubCopilotPlugin },
|
||||
{ default: ollamaPlugin },
|
||||
{ default: vllmPlugin },
|
||||
@@ -178,7 +160,6 @@ describe("provider discovery contract", () => {
|
||||
{ default: modelStudioPlugin },
|
||||
{ default: cloudflareAiGatewayPlugin },
|
||||
] = await Promise.all([
|
||||
import("../../../extensions/qwen-portal-auth/index.js"),
|
||||
import("../../../extensions/github-copilot/index.js"),
|
||||
import("../../../extensions/ollama/index.js"),
|
||||
import("../../../extensions/vllm/index.js"),
|
||||
@@ -187,7 +168,6 @@ describe("provider discovery contract", () => {
|
||||
import("../../../extensions/modelstudio/index.js"),
|
||||
import("../../../extensions/cloudflare-ai-gateway/index.js"),
|
||||
]);
|
||||
qwenPortalProvider = requireProvider(registerProviders(qwenPortalPlugin), "qwen-portal");
|
||||
githubCopilotProvider = requireProvider(
|
||||
registerProviders(githubCopilotPlugin),
|
||||
"github-copilot",
|
||||
@@ -215,42 +195,6 @@ describe("provider discovery contract", () => {
|
||||
listProfilesForProviderMock.mockReset();
|
||||
});
|
||||
|
||||
it("keeps qwen portal oauth marker fallback provider-owned", async () => {
|
||||
setQwenPortalOauthSnapshot();
|
||||
|
||||
await expect(
|
||||
runCatalog({
|
||||
provider: qwenPortalProvider,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
provider: {
|
||||
baseUrl: "https://portal.qwen.ai/v1",
|
||||
apiKey: QWEN_OAUTH_MARKER,
|
||||
api: "openai-completions",
|
||||
models: [
|
||||
expect.objectContaining({ id: "coder-model", name: "Qwen Coder" }),
|
||||
expect.objectContaining({ id: "vision-model", name: "Qwen Vision" }),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps qwen portal env api keys higher priority than oauth markers", async () => {
|
||||
setQwenPortalOauthSnapshot();
|
||||
|
||||
await expect(
|
||||
runCatalog({
|
||||
provider: qwenPortalProvider,
|
||||
env: { QWEN_PORTAL_API_KEY: "env-key" } as NodeJS.ProcessEnv,
|
||||
resolveProviderApiKey: () => ({ apiKey: "env-key" }),
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
provider: {
|
||||
apiKey: "env-key",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps GitHub Copilot catalog disabled without env tokens or profiles", async () => {
|
||||
await expect(runCatalog({ provider: githubCopilotProvider })).resolves.toBeNull();
|
||||
});
|
||||
|
||||
@@ -27,7 +27,6 @@ import opencodeGoPlugin from "../../../extensions/opencode-go/index.js";
|
||||
import opencodePlugin from "../../../extensions/opencode/index.js";
|
||||
import openrouterPlugin from "../../../extensions/openrouter/index.js";
|
||||
import qianfanPlugin from "../../../extensions/qianfan/index.js";
|
||||
import qwenPortalAuthPlugin from "../../../extensions/qwen-portal-auth/index.js";
|
||||
import sglangPlugin from "../../../extensions/sglang/index.js";
|
||||
import syntheticPlugin from "../../../extensions/synthetic/index.js";
|
||||
import togetherPlugin from "../../../extensions/together/index.js";
|
||||
@@ -378,7 +377,6 @@ const bundledProviderPlugins = dedupePlugins([
|
||||
opencodeGoPlugin,
|
||||
openrouterPlugin,
|
||||
qianfanPlugin,
|
||||
qwenPortalAuthPlugin,
|
||||
sglangPlugin,
|
||||
syntheticPlugin,
|
||||
togetherPlugin,
|
||||
|
||||
@@ -3,7 +3,6 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import openAIPlugin from "../../../extensions/openai/index.js";
|
||||
import qwenPortalPlugin from "../../../extensions/qwen-portal-auth/index.js";
|
||||
import { createProviderUsageFetch, makeResponse } from "../../test-utils/provider-usage-fetch.js";
|
||||
import type { ProviderPlugin, ProviderRuntimeModel } from "../types.js";
|
||||
import { requireProviderContractProvider as requireBundledProviderContractProvider } from "./registry.js";
|
||||
@@ -17,14 +16,8 @@ const getOAuthProvidersMock = vi.hoisted(() =>
|
||||
{ id: "anthropic", envApiKey: "ANTHROPIC_API_KEY", oauthTokenEnv: "ANTHROPIC_OAUTH_TOKEN" }, // pragma: allowlist secret
|
||||
{ id: "google", envApiKey: "GOOGLE_API_KEY", oauthTokenEnv: "GOOGLE_OAUTH_TOKEN" }, // pragma: allowlist secret
|
||||
{ id: "openai-codex", envApiKey: "OPENAI_API_KEY", oauthTokenEnv: "OPENAI_OAUTH_TOKEN" }, // pragma: allowlist secret
|
||||
{
|
||||
id: "qwen-portal",
|
||||
envApiKey: "QWEN_PORTAL_API_KEY",
|
||||
oauthTokenEnv: "QWEN_PORTAL_OAUTH_TOKEN",
|
||||
}, // pragma: allowlist secret
|
||||
]),
|
||||
);
|
||||
const refreshQwenPortalCredentialsMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@mariozechner/pi-ai/oauth", async () => {
|
||||
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai/oauth")>(
|
||||
@@ -37,14 +30,6 @@ vi.mock("@mariozechner/pi-ai/oauth", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../../extensions/qwen-portal-auth/refresh.js", async () => {
|
||||
const actual = await vi.importActual<object>("../../../extensions/qwen-portal-auth/refresh.js");
|
||||
return {
|
||||
...actual,
|
||||
refreshQwenPortalCredentials: refreshQwenPortalCredentialsMock,
|
||||
};
|
||||
});
|
||||
|
||||
function createModel(overrides: Partial<ProviderRuntimeModel> & Pick<ProviderRuntimeModel, "id">) {
|
||||
return {
|
||||
id: overrides.id,
|
||||
@@ -64,9 +49,6 @@ function requireProviderContractProvider(providerId: string): ProviderPlugin {
|
||||
if (providerId === "openai-codex") {
|
||||
return requireProvider(registerProviders(openAIPlugin), providerId);
|
||||
}
|
||||
if (providerId === "qwen-portal") {
|
||||
return requireProvider(registerProviders(qwenPortalPlugin), providerId);
|
||||
}
|
||||
return requireBundledProviderContractProvider(providerId);
|
||||
}
|
||||
|
||||
@@ -74,7 +56,6 @@ describe("provider runtime contract", () => {
|
||||
beforeEach(() => {
|
||||
getOAuthApiKeyMock.mockReset();
|
||||
getOAuthProvidersMock.mockClear();
|
||||
refreshQwenPortalCredentialsMock.mockReset();
|
||||
}, CONTRACT_SETUP_TIMEOUT_MS);
|
||||
|
||||
describe("anthropic", () => {
|
||||
@@ -633,29 +614,6 @@ describe("provider runtime contract", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("qwen-portal", () => {
|
||||
it("owns OAuth refresh", async () => {
|
||||
const provider = requireProviderContractProvider("qwen-portal");
|
||||
const credential = {
|
||||
type: "oauth" as const,
|
||||
provider: "qwen-portal",
|
||||
access: "stale-access-token",
|
||||
refresh: "refresh-token",
|
||||
expires: Date.now() - 60_000,
|
||||
};
|
||||
const refreshed = {
|
||||
...credential,
|
||||
access: "fresh-access-token",
|
||||
expires: Date.now() + 60_000,
|
||||
};
|
||||
|
||||
refreshQwenPortalCredentialsMock.mockReset();
|
||||
refreshQwenPortalCredentialsMock.mockResolvedValueOnce(refreshed);
|
||||
|
||||
await expect(provider.refreshOAuth?.(credential)).resolves.toEqual(refreshed);
|
||||
});
|
||||
});
|
||||
|
||||
describe("zai", () => {
|
||||
it("owns glm-5 forward-compat resolution", () => {
|
||||
const provider = requireProviderContractProvider("zai");
|
||||
|
||||
Reference in New Issue
Block a user