import { OAuthService } from "./oauth.js"; import { OPENAI_CONFIG } from "../constants/oauth.js"; import { getServerCredentials } from "../config/index.js"; import { spinner as createSpinner } from "../utils/ui.js"; /** * OpenAI OAuth Service (Native) * Uses Authorization Code Flow with PKCE (similar to Codex) */ export class OpenAIService extends OAuthService { constructor() { super(OPENAI_CONFIG); } /** * Build OpenAI authorization URL */ buildOpenAIAuthUrl(redirectUri, state, codeChallenge) { const params = new URLSearchParams({ client_id: OPENAI_CONFIG.clientId, response_type: "code", redirect_uri: redirectUri, scope: OPENAI_CONFIG.scope, state: state, code_challenge: codeChallenge, code_challenge_method: OPENAI_CONFIG.codeChallengeMethod, ...OPENAI_CONFIG.extraParams, }); return `${OPENAI_CONFIG.authorizeUrl}?${params.toString()}`; } /** * Exchange OpenAI authorization code for tokens */ async exchangeOpenAICode(code, redirectUri, codeVerifier) { const response = await fetch(OPENAI_CONFIG.tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: OPENAI_CONFIG.clientId, code: code, redirect_uri: redirectUri, code_verifier: codeVerifier, }), }); if (!response.ok) { const error = await response.text(); throw new Error(`Token exchange failed: ${error}`); } return await response.json(); } /** * Save OpenAI tokens to server */ async saveTokens(tokens) { const { server, token, userId } = getServerCredentials(); const response = await fetch(`${server}/api/cli/providers/openai`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, "X-User-Id": userId, }, body: JSON.stringify({ accessToken: tokens.access_token, refreshToken: tokens.refresh_token, expiresIn: tokens.expires_in, idToken: tokens.id_token, scope: tokens.scope, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "Failed to save tokens"); } return await response.json(); } /** * Complete OpenAI OAuth flow */ async connect() { const spinner = createSpinner("Starting OpenAI OAuth...").start(); try { spinner.text = "Starting local server..."; // Authenticate and get authorization code const { code, codeVerifier, redirectUri } = await this.authenticate( "OpenAI", this.buildOpenAIAuthUrl.bind(this) ); spinner.start("Exchanging code for tokens..."); // Exchange code for tokens const tokens = await this.exchangeOpenAICode(code, redirectUri, codeVerifier); spinner.text = "Saving tokens to server..."; // Save tokens to server await this.saveTokens(tokens); spinner.succeed("OpenAI connected successfully!"); return true; } catch (error) { spinner.fail(`Failed: ${error.message}`); throw error; } } }