From 3e19e8fd598d09d549962166533b1039c5500084 Mon Sep 17 00:00:00 2001 From: Fouad Matin <169186268+fouad-openai@users.noreply.github.com> Date: Fri, 16 May 2025 17:55:08 -0700 Subject: [PATCH] add: sign in with chatgpt credits (#974) --- codex-cli/src/utils/get-api-key.tsx | 114 ++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 5 deletions(-) diff --git a/codex-cli/src/utils/get-api-key.tsx b/codex-cli/src/utils/get-api-key.tsx index 0dd52f73..ad5ce1c0 100644 --- a/codex-cli/src/utils/get-api-key.tsx +++ b/codex-cli/src/utils/get-api-key.tsx @@ -3,6 +3,7 @@ import type { Request, Response } from "express"; import { ApiKeyPrompt, WaitingForAuth } from "./get-api-key-components"; import { clearTerminal } from "./terminal"; +import chalk from "chalk"; import express from "express"; import fs from "fs/promises"; import { render } from "ink"; @@ -189,10 +190,10 @@ async function handleCallback( ); const chatgptPlanType = accessTokenClaims["https://api.openai.com/auth"]?.chatgpt_plan_type; - let needsSetup = false; - if (chatgptPlanType === "plus" || chatgptPlanType === "pro") { - needsSetup = !completedOnboarding; - } + const isOrgOwner = Boolean( + idTokenClaims["https://api.openai.com/auth"]?.is_org_owner, + ); + const needsSetup = !completedOnboarding && isOrgOwner; // Build the success URL on the same host/port as the callback and // include the required query parameters for the front-end page. @@ -230,6 +231,58 @@ async function handleCallback( console.warn("Unable to save auth file:", err); } + if ( + !needsSetup && + (chatgptPlanType === "plus" || chatgptPlanType === "pro") + ) { + const apiHost = + issuer === "https://auth.openai.com" + ? "https://api.openai.com" + : "https://api.openai.org"; + + try { + const redeemRes = await fetch(`${apiHost}/v1/billing/redeem_credits`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ id_token: tokenData.id_token }), + }); + + if (!redeemRes.ok) { + // eslint-disable-next-line no-console + console.warn( + `Credit redemption request failed: ${redeemRes.status} ${redeemRes.statusText}`, + ); + } else { + // Attempt to parse the JSON response and surface a success message + try { + const redeemData = (await redeemRes.json()) as { + granted_chatgpt_subscriber_api_credits?: number; + }; + const granted = + redeemData?.granted_chatgpt_subscriber_api_credits ?? 0; + if (granted > 0) { + // eslint-disable-next-line no-console + console.log( + chalk.green( + `\u2728 Granted ${chatgptPlanType === "plus" ? "$5" : "$50"} in API credits for being a ChatGPT ${ + chatgptPlanType === "plus" ? "Plus" : "Pro" + } subscriber!`, + ), + ); + } + } catch (parseErr) { + // eslint-disable-next-line no-console + console.warn("Unable to parse credit redemption response:", parseErr); + } + } + } catch (err) { + // eslint-disable-next-line no-console + console.warn("Unable to redeem ChatGPT subscriber API credits:", err); + } + } + return { access_token: exchanged.access_token, success_url: successUrl.toString(), @@ -363,11 +416,62 @@ const LOGIN_SUCCESS_HTML = String.raw`
Signed in to Codex CLI
-
+ +
+ `;