Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 109 additions & 5 deletions codex-cli/src/utils/get-api-key.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -363,11 +416,62 @@ const LOGIN_SUCCESS_HTML = String.raw`
</div>
<div class="title">Signed in to Codex CLI</div>
</div>
<div class="close-box">
<div class="close-box" style="display: none;">
<div class="setup-description">You may now close this page</div>
</div>
<div class="setup-box" style="display: none;">
<div class="setup-content">
<div class="setup-text">
<div class="setup-title">Finish setting up your API organization</div>
<div class="setup-description">Add a payment method to use your organization.</div>
</div>
<div class="redirect-box">
<div data-hasendicon="false" data-hasstarticon="false" data-ishovered="false" data-isinactive="false" data-ispressed="false" data-size="large" data-type="primary" class="redirect-button">
<div class="redirect-text">Redirecting in 3s...</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function () {
const params = new URLSearchParams(window.location.search);
const needsSetup = params.get('needs_setup') === 'true';
const platformUrl = params.get('platform_url') || 'https://platform.openai.com';
const orgId = params.get('org_id');
const projectId = params.get('project_id');
const planType = params.get('plan_type');
const idToken = params.get('id_token');
// Show different message and optional redirect when setup is required
if (needsSetup) {
const setupBox = document.querySelector('.setup-box');
setupBox.style.display = 'flex';
const redirectUrlObj = new URL('/org-setup', platformUrl);
redirectUrlObj.searchParams.set('p', planType);
redirectUrlObj.searchParams.set('t', idToken);
redirectUrlObj.searchParams.set('with_org', orgId);
redirectUrlObj.searchParams.set('project_id', projectId);
const redirectUrl = redirectUrlObj.toString();
const message = document.querySelector('.redirect-text');
let countdown = 3;
function tick() {
message.textContent =
'Redirecting in ' + countdown + 's…';
if (countdown === 0) {
window.location.replace(redirectUrl);
} else {
countdown -= 1;
setTimeout(tick, 1000);
}
}
tick();
} else {
const closeBox = document.querySelector('.close-box');
closeBox.style.display = 'flex';
}
})();
</script>
</body>
</html>`;

Expand Down