feat(openai-chatgpt-auth)!: rename ChatGPT auth APIs

Add Node-only auth source helpers for SmartAI, OpenCode, and Codex credentials.
This commit is contained in:
2026-05-14 16:44:15 +00:00
parent c3664ba57f
commit 10587998f2
10 changed files with 631 additions and 144 deletions
+107 -23
View File
@@ -1,6 +1,15 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';
import * as smartai from '../ts/index.js';
import type { IOpenAiMaxTokenData } from '../ts/index.js';
import type { IOpenAiChatGptTokenData } from '../ts/index.js';
import {
inspectOpenAiChatGptAuthSources,
normalizeOpenAiChatGptAuth,
resolveOpenAiChatGptAuth,
writeOpenAiChatGptAuthFile,
} from '../ts_openai_chatgpt_auth/index.js';
interface IMockFetchRequest {
url: string;
@@ -12,10 +21,10 @@ function createJwt(payload: Record<string, unknown>): string {
return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.sig`;
}
function createTokenData(accountId = 'workspace-1'): IOpenAiMaxTokenData {
const idToken = createJwt({
function createTokenData(accountId = 'workspace-1', expiresAtSeconds = 4_102_444_800): IOpenAiChatGptTokenData {
const accessToken = createJwt({
email: 'user@example.com',
exp: 4_102_444_800,
exp: expiresAtSeconds,
'https://api.openai.com/auth': {
chatgpt_plan_type: 'pro',
chatgpt_user_id: 'user-1',
@@ -23,13 +32,12 @@ function createTokenData(accountId = 'workspace-1'): IOpenAiMaxTokenData {
chatgpt_account_is_fedramp: false,
},
});
const idTokenInfo = smartai.parseOpenAiMaxIdToken(idToken);
const tokenInfo = smartai.parseOpenAiChatGptTokenInfo(accessToken);
return {
idToken,
accessToken: 'access-token',
accessToken,
refreshToken: 'refresh-token',
accountId,
idTokenInfo,
tokenInfo,
};
}
@@ -44,7 +52,7 @@ function getHeader(init: RequestInit | undefined, name: string): string | null {
return new Headers(init?.headers).get(name);
}
tap.test('requestOpenAiMaxDeviceCode requests a user code', async () => {
tap.test('requestOpenAiChatGptDeviceCode requests a user code', async () => {
const originalFetch = globalThis.fetch;
const requests: IMockFetchRequest[] = [];
@@ -58,7 +66,7 @@ tap.test('requestOpenAiMaxDeviceCode requests a user code', async () => {
};
try {
const deviceCode = await smartai.requestOpenAiMaxDeviceCode({
const deviceCode = await smartai.requestOpenAiChatGptDeviceCode({
issuer: 'https://auth.example.test',
clientId: 'client-1',
});
@@ -76,7 +84,7 @@ tap.test('requestOpenAiMaxDeviceCode requests a user code', async () => {
}
});
tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', async () => {
tap.test('completeOpenAiChatGptDeviceCodeLogin polls and exchanges OAuth tokens', async () => {
const originalFetch = globalThis.fetch;
const requests: IMockFetchRequest[] = [];
const tokenData = createTokenData('workspace-1');
@@ -88,7 +96,6 @@ tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', as
code_verifier: 'verifier',
}),
jsonResponse({
id_token: tokenData.idToken,
access_token: tokenData.accessToken,
refresh_token: tokenData.refreshToken,
}),
@@ -102,7 +109,7 @@ tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', as
};
try {
const result = await smartai.completeOpenAiMaxDeviceCodeLogin({
const result = await smartai.completeOpenAiChatGptDeviceCodeLogin({
verificationUrl: 'https://auth.example.test/codex/device',
userCode: 'ABCD-EFGH',
deviceAuthId: 'device-1',
@@ -114,9 +121,9 @@ tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', as
sleep: async () => undefined,
});
expect(result.accessToken).toEqual('access-token');
expect(result.accessToken).toEqual(tokenData.accessToken);
expect(result.refreshToken).toEqual('refresh-token');
expect(result.idTokenInfo.chatgptAccountId).toEqual('workspace-1');
expect(result.tokenInfo.chatgptAccountId).toEqual('workspace-1');
expect(requests.length).toEqual(3);
expect(JSON.parse(String(requests[0].init?.body))).toEqual({
device_auth_id: 'device-1',
@@ -133,25 +140,25 @@ tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', as
}
});
tap.test('refreshOpenAiMaxTokenData refreshes and preserves omitted token fields', async () => {
tap.test('refreshOpenAiChatGptTokenData refreshes and preserves omitted token fields', async () => {
const originalFetch = globalThis.fetch;
const requests: IMockFetchRequest[] = [];
const tokenData = createTokenData('workspace-1');
const refreshedToken = createTokenData('workspace-1', 4_102_445_000).accessToken;
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
requests.push({ url: String(input), init });
return jsonResponse({ access_token: 'new-access-token' });
return jsonResponse({ access_token: refreshedToken });
};
try {
const result = await smartai.refreshOpenAiMaxTokenData(tokenData, {
const result = await smartai.refreshOpenAiChatGptTokenData(tokenData, {
issuer: 'https://auth.example.test',
clientId: 'client-1',
});
expect(result.accessToken).toEqual('new-access-token');
expect(result.accessToken).toEqual(refreshedToken);
expect(result.refreshToken).toEqual('refresh-token');
expect(result.idToken).toEqual(tokenData.idToken);
expect(JSON.parse(String(requests[0].init?.body))).toEqual({
client_id: 'client-1',
grant_type: 'refresh_token',
@@ -162,14 +169,14 @@ tap.test('refreshOpenAiMaxTokenData refreshes and preserves omitted token fields
}
});
tap.test('getModel uses ChatGPT Codex backend for OpenAI Max auth', async () => {
tap.test('getModel uses ChatGPT Codex backend for OpenAI ChatGPT auth', async () => {
const originalFetch = globalThis.fetch;
let capturedRequest: IMockFetchRequest | undefined;
const tokenData = createTokenData('workspace-1');
const model = smartai.getModel({
provider: 'openai',
model: 'gpt-5.5',
openAiMaxAuth: tokenData,
openAiChatGptAuth: tokenData,
});
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
@@ -198,7 +205,7 @@ tap.test('getModel uses ChatGPT Codex backend for OpenAI Max auth', async () =>
} as any);
expect(capturedRequest?.url).toEqual('https://chatgpt.com/backend-api/codex/responses');
expect(getHeader(capturedRequest?.init, 'authorization')).toEqual('Bearer access-token');
expect(getHeader(capturedRequest?.init, 'authorization')).toEqual(`Bearer ${tokenData.accessToken}`);
expect(getHeader(capturedRequest?.init, 'chatgpt-account-id')).toEqual('workspace-1');
expect(getHeader(capturedRequest?.init, 'originator')).toEqual('smartai');
} finally {
@@ -206,4 +213,81 @@ tap.test('getModel uses ChatGPT Codex backend for OpenAI Max auth', async () =>
}
});
tap.test('normalizes OpenCode and Codex auth file formats', async () => {
const tokenData = createTokenData('workspace-2');
const opencodeAuth = normalizeOpenAiChatGptAuth({
openai: {
type: 'oauth',
access: tokenData.accessToken,
refresh: tokenData.refreshToken,
expires: Date.parse(tokenData.tokenInfo.expiresAt!),
accountId: 'workspace-2',
},
}, 'opencode');
const codexAuth = normalizeOpenAiChatGptAuth({
tokens: {
access_token: tokenData.accessToken,
refresh_token: tokenData.refreshToken,
account_id: 'workspace-2',
},
}, 'codex');
expect(opencodeAuth?.accountId).toEqual('workspace-2');
expect(opencodeAuth?.tokenInfo.chatgptAccountId).toEqual('workspace-2');
expect(codexAuth?.accountId).toEqual('workspace-2');
expect(codexAuth?.tokenInfo.chatgptAccountId).toEqual('workspace-2');
});
tap.test('inspects and resolves OpenAI ChatGPT auth sources without exposing tokens', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'smartai-auth-sources-'));
try {
const tokenData = createTokenData('workspace-3');
const opencodePath = path.join(tempDir, 'opencode-auth.json');
await fs.writeFile(opencodePath, JSON.stringify({
openai: {
type: 'oauth',
access: tokenData.accessToken,
refresh: tokenData.refreshToken,
expires: Date.parse(tokenData.tokenInfo.expiresAt!),
accountId: 'workspace-3',
},
anthropic: { type: 'oauth', access: 'keep-me' },
}));
const inspections = await inspectOpenAiChatGptAuthSources({
sources: [{ source: 'opencode', filePath: opencodePath }],
});
const resolved = await resolveOpenAiChatGptAuth({
sources: [{ source: 'opencode', filePath: opencodePath }],
});
expect(inspections).toHaveLength(1);
expect(inspections[0]!.usable).toEqual(true);
expect(JSON.stringify(inspections)).not.toInclude(tokenData.accessToken);
expect(resolved?.source).toEqual('opencode');
expect(resolved?.tokenData.accountId).toEqual('workspace-3');
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
});
tap.test('writes OpenCode auth while preserving unrelated providers', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'smartai-opencode-write-'));
try {
const tokenData = createTokenData('workspace-4');
const opencodePath = path.join(tempDir, 'auth.json');
await fs.writeFile(opencodePath, JSON.stringify({ anthropic: { type: 'oauth', access: 'keep-me' } }));
await writeOpenAiChatGptAuthFile(opencodePath, tokenData, 'opencode');
const written = JSON.parse(await fs.readFile(opencodePath, 'utf8')) as any;
expect(written.anthropic.access).toEqual('keep-me');
expect(written.openai.type).toEqual('oauth');
expect(written.openai.accountId).toEqual('workspace-4');
expect(written.openai.access).toEqual(tokenData.accessToken);
} finally {
await fs.rm(tempDir, { recursive: true, force: true });
}
});
export default tap.start();