feat(openai-auth): add OpenAI Max device-code authentication and unified prompt caching helpers
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as smartai from '../ts/index.js';
|
||||
import type { IOpenAiMaxTokenData } from '../ts/index.js';
|
||||
|
||||
interface IMockFetchRequest {
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
}
|
||||
|
||||
function createJwt(payload: Record<string, unknown>): string {
|
||||
const encode = (value: Record<string, unknown>) => Buffer.from(JSON.stringify(value)).toString('base64url');
|
||||
return `${encode({ alg: 'none', typ: 'JWT' })}.${encode(payload)}.sig`;
|
||||
}
|
||||
|
||||
function createTokenData(accountId = 'workspace-1'): IOpenAiMaxTokenData {
|
||||
const idToken = createJwt({
|
||||
email: 'user@example.com',
|
||||
exp: 4_102_444_800,
|
||||
'https://api.openai.com/auth': {
|
||||
chatgpt_plan_type: 'pro',
|
||||
chatgpt_user_id: 'user-1',
|
||||
chatgpt_account_id: accountId,
|
||||
chatgpt_account_is_fedramp: false,
|
||||
},
|
||||
});
|
||||
const idTokenInfo = smartai.parseOpenAiMaxIdToken(idToken);
|
||||
return {
|
||||
idToken,
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
accountId,
|
||||
idTokenInfo,
|
||||
};
|
||||
}
|
||||
|
||||
function jsonResponse(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), {
|
||||
status,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
});
|
||||
}
|
||||
|
||||
function getHeader(init: RequestInit | undefined, name: string): string | null {
|
||||
return new Headers(init?.headers).get(name);
|
||||
}
|
||||
|
||||
tap.test('requestOpenAiMaxDeviceCode requests a user code', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const requests: IMockFetchRequest[] = [];
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
requests.push({ url: String(input), init });
|
||||
return jsonResponse({
|
||||
device_auth_id: 'device-1',
|
||||
usercode: 'ABCD-EFGH',
|
||||
interval: '2',
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
const deviceCode = await smartai.requestOpenAiMaxDeviceCode({
|
||||
issuer: 'https://auth.example.test',
|
||||
clientId: 'client-1',
|
||||
});
|
||||
|
||||
expect(deviceCode).toEqual({
|
||||
verificationUrl: 'https://auth.example.test/codex/device',
|
||||
userCode: 'ABCD-EFGH',
|
||||
deviceAuthId: 'device-1',
|
||||
intervalSeconds: 2,
|
||||
});
|
||||
expect(requests[0].url).toEqual('https://auth.example.test/api/accounts/deviceauth/usercode');
|
||||
expect(JSON.parse(String(requests[0].init?.body))).toEqual({ client_id: 'client-1' });
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('completeOpenAiMaxDeviceCodeLogin polls and exchanges OAuth tokens', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const requests: IMockFetchRequest[] = [];
|
||||
const tokenData = createTokenData('workspace-1');
|
||||
const responses = [
|
||||
jsonResponse({}, 403),
|
||||
jsonResponse({
|
||||
authorization_code: 'auth-code',
|
||||
code_challenge: 'challenge',
|
||||
code_verifier: 'verifier',
|
||||
}),
|
||||
jsonResponse({
|
||||
id_token: tokenData.idToken,
|
||||
access_token: tokenData.accessToken,
|
||||
refresh_token: tokenData.refreshToken,
|
||||
}),
|
||||
];
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
requests.push({ url: String(input), init });
|
||||
const response = responses.shift();
|
||||
if (!response) throw new Error('Unexpected fetch call');
|
||||
return response;
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await smartai.completeOpenAiMaxDeviceCodeLogin({
|
||||
verificationUrl: 'https://auth.example.test/codex/device',
|
||||
userCode: 'ABCD-EFGH',
|
||||
deviceAuthId: 'device-1',
|
||||
intervalSeconds: 1,
|
||||
}, {
|
||||
issuer: 'https://auth.example.test',
|
||||
clientId: 'client-1',
|
||||
forcedChatGptWorkspaceId: 'workspace-1',
|
||||
sleep: async () => undefined,
|
||||
});
|
||||
|
||||
expect(result.accessToken).toEqual('access-token');
|
||||
expect(result.refreshToken).toEqual('refresh-token');
|
||||
expect(result.idTokenInfo.chatgptAccountId).toEqual('workspace-1');
|
||||
expect(requests.length).toEqual(3);
|
||||
expect(JSON.parse(String(requests[0].init?.body))).toEqual({
|
||||
device_auth_id: 'device-1',
|
||||
user_code: 'ABCD-EFGH',
|
||||
});
|
||||
const tokenExchangeBody = new URLSearchParams(String(requests[2].init?.body));
|
||||
expect(tokenExchangeBody.get('grant_type')).toEqual('authorization_code');
|
||||
expect(tokenExchangeBody.get('code')).toEqual('auth-code');
|
||||
expect(tokenExchangeBody.get('redirect_uri')).toEqual('https://auth.example.test/deviceauth/callback');
|
||||
expect(tokenExchangeBody.get('client_id')).toEqual('client-1');
|
||||
expect(tokenExchangeBody.get('code_verifier')).toEqual('verifier');
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('refreshOpenAiMaxTokenData refreshes and preserves omitted token fields', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
const requests: IMockFetchRequest[] = [];
|
||||
const tokenData = createTokenData('workspace-1');
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
requests.push({ url: String(input), init });
|
||||
return jsonResponse({ access_token: 'new-access-token' });
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await smartai.refreshOpenAiMaxTokenData(tokenData, {
|
||||
issuer: 'https://auth.example.test',
|
||||
clientId: 'client-1',
|
||||
});
|
||||
|
||||
expect(result.accessToken).toEqual('new-access-token');
|
||||
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',
|
||||
refresh_token: 'refresh-token',
|
||||
});
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('getModel uses ChatGPT Codex backend for OpenAI Max 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,
|
||||
});
|
||||
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
capturedRequest = { url: String(input), init };
|
||||
return jsonResponse({
|
||||
id: 'resp-1',
|
||||
created_at: 1,
|
||||
model: 'gpt-5.5',
|
||||
output: [{
|
||||
type: 'message',
|
||||
role: 'assistant',
|
||||
id: 'msg-1',
|
||||
content: [{ type: 'output_text', text: 'ok', annotations: [] }],
|
||||
}],
|
||||
usage: {
|
||||
input_tokens: 1,
|
||||
output_tokens: 1,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
await model.doGenerate({
|
||||
prompt: [{ role: 'user', content: [{ type: 'text', text: 'hello' }] }],
|
||||
inputFormat: 'prompt',
|
||||
} 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, 'chatgpt-account-id')).toEqual('workspace-1');
|
||||
expect(getHeader(capturedRequest?.init, 'originator')).toEqual('smartai');
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
Reference in New Issue
Block a user