mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(ai): let apps name themselves in OpenAI logins (#10433)
LoginOptions.agentName replaces pi's name in the Sign in with ChatGPT agent_name_hint and the Codex browser login originator.
This commit is contained in:
parent
f6127a1bf9
commit
9ad083102a
5 changed files with 59 additions and 6 deletions
|
|
@ -250,7 +250,7 @@ async function loginOpenAIChatGPT(
|
|||
const authorizationUrl = new URL(AUTHORIZE_URL);
|
||||
authorizationUrl.search = new URLSearchParams({
|
||||
client_id: DYNAMIC_CLIENT_ID,
|
||||
agent_name_hint: AGENT_NAME_HINT,
|
||||
agent_name_hint: options?.agentName ?? AGENT_NAME_HINT,
|
||||
ext_agent_host_id: hostId,
|
||||
response_type: "code",
|
||||
redirect_uri: REDIRECT_URI,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version
|
|||
}
|
||||
|
||||
import { getProviderEnvValue } from "../../utils/provider-env.ts";
|
||||
import type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from "../types.ts";
|
||||
import type { LoginOptions, OAuthAuth, OAuthCredential, ProviderAuthInteraction } from "../types.ts";
|
||||
import { startOAuthCallbackServer, waitForCallbackOrManualInput } from "./callback-server.ts";
|
||||
import { pollOAuthDeviceCodeFlow } from "./device-code.ts";
|
||||
import { generatePKCE } from "./pkce.ts";
|
||||
|
|
@ -356,8 +356,11 @@ async function loginOpenAICodexDeviceCode(interaction: ProviderAuthInteraction):
|
|||
);
|
||||
}
|
||||
|
||||
async function loginOpenAICodex(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {
|
||||
const { verifier, state, url } = await createAuthorizationFlow();
|
||||
async function loginOpenAICodex(
|
||||
interaction: ProviderAuthInteraction,
|
||||
options?: LoginOptions,
|
||||
): Promise<OAuthCredential> {
|
||||
const { verifier, state, url } = await createAuthorizationFlow(options?.agentName);
|
||||
// Port 1455 is shared with the Codex CLI; when it is taken, fall back to the pasted redirect URL.
|
||||
const callback = await startOAuthCallbackServer({
|
||||
providerName: "OpenAI",
|
||||
|
|
@ -407,7 +410,7 @@ export const openaiCodexOAuth: OAuthAuth = {
|
|||
name: "OpenAI (ChatGPT Plus/Pro)",
|
||||
isSubscription: true,
|
||||
|
||||
async login(interaction) {
|
||||
async login(interaction, options) {
|
||||
const method = await interaction.prompt({
|
||||
type: "select",
|
||||
message: "Select OpenAI Codex login method:",
|
||||
|
|
@ -424,7 +427,7 @@ export const openaiCodexOAuth: OAuthAuth = {
|
|||
throw new Error(`Unknown OpenAI Codex login method: ${method}`);
|
||||
}
|
||||
|
||||
return loginOpenAICodex(interaction);
|
||||
return loginOpenAICodex(interaction, options);
|
||||
},
|
||||
|
||||
refresh: (credential, signal) => refreshOpenAICodexToken(credential.refresh, signal),
|
||||
|
|
|
|||
|
|
@ -206,6 +206,11 @@ export interface LoginOptions {
|
|||
* the ID on first use and must return the same ID on every later call.
|
||||
*/
|
||||
getDeviceId?: () => string;
|
||||
/**
|
||||
* Name this app introduces itself with during login, e.g. OpenAI's agent name hint and
|
||||
* Codex originator. Defaults to pi's own name.
|
||||
*/
|
||||
agentName?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -108,6 +108,23 @@ describe("OpenAI ChatGPT OAuth", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("uses the app's agent name as the name hint", async () => {
|
||||
let authorizeUrl: URL | undefined;
|
||||
stubTokenEndpoint(tokenResponse());
|
||||
|
||||
await openaiChatGPTOAuth.login(
|
||||
loginInteraction({
|
||||
callbackClientId: "oaiapp_issued",
|
||||
onAuthorize: (url) => {
|
||||
authorizeUrl = url;
|
||||
},
|
||||
}),
|
||||
{ getDeviceId: () => DEVICE_ID, agentName: "my-app" },
|
||||
);
|
||||
|
||||
expect(authorizeUrl?.searchParams.get("agent_name_hint")).toBe("my-app");
|
||||
});
|
||||
|
||||
it("rejects registration without an issued client ID", async () => {
|
||||
const fetchMock = stubTokenEndpoint(tokenResponse());
|
||||
|
||||
|
|
|
|||
|
|
@ -485,6 +485,34 @@ describe("OpenAI Codex OAuth", () => {
|
|||
expect(consoleError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("uses the app's agent name as the browser login originator", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () =>
|
||||
jsonResponse({ access_token: createAccessToken("acct"), refresh_token: "refresh", expires_in: 3600 }),
|
||||
),
|
||||
);
|
||||
|
||||
let authUrl = "";
|
||||
await openaiCodexOAuth.login(
|
||||
{
|
||||
signal: neverAbortedSignal,
|
||||
notify: (event) => {
|
||||
if (event.type === "auth_url") authUrl = event.url;
|
||||
},
|
||||
prompt: async (prompt) => {
|
||||
if (prompt.type === "select") return "browser";
|
||||
if (prompt.type !== "manual_code") throw new Error(`Unexpected prompt: ${prompt.type}`);
|
||||
const state = new URL(authUrl).searchParams.get("state");
|
||||
return `http://localhost:1455/auth/callback?code=pasted-code&state=${state}`;
|
||||
},
|
||||
},
|
||||
{ agentName: "my-app" },
|
||||
);
|
||||
|
||||
expect(new URL(authUrl).searchParams.get("originator")).toBe("my-app");
|
||||
});
|
||||
|
||||
it("falls back to the pasted redirect URL when the fixed callback port is taken", async () => {
|
||||
// Port 1455 is registered with OpenAI; the Codex CLI may hold it. Occupy it unless it already is.
|
||||
const blocker = createServer();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue