import { resolveClawdbotAgentDir } from "../agents/agent-paths.js"; import { resolveDefaultAgentId, resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; import { upsertAuthProfile } from "../agents/auth-profiles.js"; import { normalizeProviderId } from "../agents/model-selection.js"; import { resolveDefaultAgentWorkspaceDir } from "../agents/workspace.js"; import type { ClawdbotConfig } from "../config/config.js"; import { resolvePluginProviders } from "../plugins/providers.js"; import type { ProviderAuthMethod, ProviderPlugin } from "../plugins/types.js"; import type { ApplyAuthChoiceParams, ApplyAuthChoiceResult } from "./auth-choice.apply.js"; import { applyAuthProfileConfig } from "./onboard-auth.js"; import { openUrl } from "./onboard-helpers.js"; import { createVpsAwareOAuthHandlers } from "./oauth-flow.js"; import { isRemoteEnvironment } from "./oauth-env.js"; const PLUGIN_ID = "qwen-portal-auth"; const PROVIDER_ID = "qwen-portal"; function enableBundledPlugin(cfg: ClawdbotConfig): ClawdbotConfig { const existingEntry = cfg.plugins?.entries?.[PLUGIN_ID]; return { ...cfg, plugins: { ...cfg.plugins, entries: { ...cfg.plugins?.entries, [PLUGIN_ID]: { ...existingEntry, enabled: true, }, }, }, }; } function resolveProviderMatch( providers: ProviderPlugin[], rawProvider: string, ): ProviderPlugin | null { const normalized = normalizeProviderId(rawProvider); return ( providers.find((provider) => normalizeProviderId(provider.id) === normalized) ?? providers.find( (provider) => provider.aliases?.some((alias) => normalizeProviderId(alias) === normalized) ?? false, ) ?? null ); } function pickAuthMethod(provider: ProviderPlugin, rawMethod?: string): ProviderAuthMethod | null { const raw = rawMethod?.trim(); if (!raw) return null; const normalized = raw.toLowerCase(); return ( provider.auth.find((method) => method.id.toLowerCase() === normalized) ?? provider.auth.find((method) => method.label.toLowerCase() === normalized) ?? null ); } function isPlainRecord(value: unknown): value is Record { return Boolean(value && typeof value === "object" && !Array.isArray(value)); } function mergeConfigPatch(base: T, patch: unknown): T { if (!isPlainRecord(base) || !isPlainRecord(patch)) { return patch as T; } const next: Record = { ...base }; for (const [key, value] of Object.entries(patch)) { const existing = next[key]; if (isPlainRecord(existing) && isPlainRecord(value)) { next[key] = mergeConfigPatch(existing, value); } else { next[key] = value; } } return next as T; } function applyDefaultModel(cfg: ClawdbotConfig, model: string): ClawdbotConfig { const models = { ...cfg.agents?.defaults?.models }; models[model] = models[model] ?? {}; const existingModel = cfg.agents?.defaults?.model; return { ...cfg, agents: { ...cfg.agents, defaults: { ...cfg.agents?.defaults, models, model: { ...(existingModel && typeof existingModel === "object" && "fallbacks" in existingModel ? { fallbacks: (existingModel as { fallbacks?: string[] }).fallbacks } : undefined), primary: model, }, }, }, }; } export async function applyAuthChoiceQwenPortal( params: ApplyAuthChoiceParams, ): Promise { if (params.authChoice !== "qwen-portal") return null; let nextConfig = enableBundledPlugin(params.config); const agentId = params.agentId ?? resolveDefaultAgentId(nextConfig); const defaultAgentId = resolveDefaultAgentId(nextConfig); const agentDir = params.agentDir ?? (agentId === defaultAgentId ? resolveClawdbotAgentDir() : resolveAgentDir(nextConfig, agentId)); const workspaceDir = resolveAgentWorkspaceDir(nextConfig, agentId) ?? resolveDefaultAgentWorkspaceDir(); const providers = resolvePluginProviders({ config: nextConfig, workspaceDir }); const provider = resolveProviderMatch(providers, PROVIDER_ID); if (!provider) { await params.prompter.note( "Qwen auth plugin is not available. Run `clawdbot plugins enable qwen-portal-auth` and re-run the wizard.", "Qwen", ); return { config: nextConfig }; } const method = pickAuthMethod(provider, "device") ?? provider.auth[0]; if (!method) { await params.prompter.note("Qwen auth method missing.", "Qwen"); return { config: nextConfig }; } const isRemote = isRemoteEnvironment(); const result = await method.run({ config: nextConfig, agentDir, workspaceDir, prompter: params.prompter, runtime: params.runtime, isRemote, openUrl: async (url) => { await openUrl(url); }, oauth: { createVpsAwareHandlers: (opts) => createVpsAwareOAuthHandlers(opts), }, }); if (result.configPatch) { nextConfig = mergeConfigPatch(nextConfig, result.configPatch); } for (const profile of result.profiles) { upsertAuthProfile({ profileId: profile.profileId, credential: profile.credential, agentDir, }); nextConfig = applyAuthProfileConfig(nextConfig, { profileId: profile.profileId, provider: profile.credential.provider, mode: profile.credential.type === "token" ? "token" : profile.credential.type, ...("email" in profile.credential && profile.credential.email ? { email: profile.credential.email } : {}), }); } let agentModelOverride: string | undefined; if (result.defaultModel) { if (params.setDefaultModel) { nextConfig = applyDefaultModel(nextConfig, result.defaultModel); await params.prompter.note(`Default model set to ${result.defaultModel}`, "Model configured"); } else if (params.agentId) { agentModelOverride = result.defaultModel; await params.prompter.note( `Default model set to ${result.defaultModel} for agent "${params.agentId}".`, "Model configured", ); } } if (result.notes && result.notes.length > 0) { await params.prompter.note(result.notes.join("\n"), "Provider notes"); } return { config: nextConfig, agentModelOverride }; }