## ๐ฏ Overview Complete TypeScript rewrite with comprehensive testing, modular architecture, and enhanced user experience. Adds 9 pre-configured model variants matching Codex CLI presets and a beautiful OAuth success page. ## โจ New Features ### Configuration System - **9 Model Variants**: Pre-configured reasoning levels (Low/Medium/High) for both gpt-5-codex and gpt-5, plus Minimal variants - **Full Config Example**: `config/full-opencode.json` with all variants ready to use - **Minimal Config Example**: `config/minimal-opencode.json` for basic setup - **Config Documentation**: Comprehensive guide in `config/README.md` ### OAuth Enhancement - **Animated Success Page**: Beautiful terminal-style UI with matrix rain effect, animated checkmark, and glowing effects - **Interactive Elements**: Mouse-responsive matrix rain and parallax effects - **Professional Branding**: OpenCode logo with pulse animations ### Developer Experience - **TypeScript Rewrite**: Complete migration from .mjs to .ts with strict typing - **93 Comprehensive Tests**: Full test coverage using Vitest - **Modular Architecture**: 10 focused helper functions, each < 40 lines - **Centralized Constants**: All magic values in `lib/constants.ts` - **CI/CD Pipeline**: GitHub Actions workflow for automated testing ### Documentation - **AGENTS.md**: Complete guide for AI assistants maintaining the codebase - **Enhanced README**: Updated with new configuration options and examples - **Inline JSDoc**: Comprehensive documentation for all public functions ## ๐๏ธ Technical Changes ### Architecture Improvements - Extracted 10 focused helper functions from monolithic fetch() - Centralized all constants and error messages - Separated concerns: auth, browser, codex, server, etc. - Type-safe discriminated unions for result types ### Build System - TypeScript compilation with declarations and source maps - Automatic HTML asset copying in build process - Test files excluded from distribution bundle - Clean dist/ output with only production code ### Testing Infrastructure - Vitest test runner with UI mode - 93 tests across 7 test files - Mock-based testing for external dependencies - Coverage reporting capability ## ๐ New Structure ``` opencode-openai-codex-auth/ โโโ index.ts # Main plugin (TypeScript) โโโ lib/ โ โโโ auth.ts # OAuth logic โ โโโ browser.ts # Platform-specific browser opening โ โโโ codex.ts # Codex instructions โ โโโ server.ts # OAuth callback server โ โโโ fetch-helpers.ts # 10 focused helpers โ โโโ constants.ts # All magic values โ โโโ types.ts # TypeScript types โ โโโ oauth-success.html # Animated success page โ โโโ ... โโโ config/ โ โโโ full-opencode.json # Complete config with variants โ โโโ minimal-opencode.json # Basic config โ โโโ README.md # Config docs โโโ test/ # 93 comprehensive tests โโโ AGENTS.md # AI maintenance guide โโโ tsconfig.json # TypeScript config ``` ## ๐ Migration Notes ### Breaking Changes - Plugin now requires TypeScript compilation (dist/ folder) - Configuration structure enhanced (backward compatible) - OAuth success page replaces plain HTML response ### Upgrade Path 1. Use new config from `config/full-opencode.json` for best experience 2. Or continue with minimal config (uses plugin defaults) 3. Rebuild plugin: `npm run build` ## ๐ Metrics - **Lines of Code**: Modular design with functions < 70 lines - **Test Coverage**: 93 tests covering all functionality - **Type Safety**: Strict TypeScript with no `any` types - **Dependencies**: Still minimal (only @openauthjs/openauth) - **Model Variants**: 9 pre-configured options for users ## ๐จ User Experience - **Codex CLI Parity**: Match official CLI reasoning presets - **Beautiful OAuth**: Professional animated success page - **Clear Documentation**: Step-by-step guides and examples - **Flexible Config**: From minimal to comprehensive setups Co-authored-by: Hanzo Dev <[email protected]>
18 KiB
AI Agent Maintenance Guide
This guide is specifically written for AI coding assistants (Claude, GPT-4, etc.) maintaining the OpenAI Codex OAuth plugin. It provides quick reference for common tasks and patterns to follow.
๐ฏ Core Philosophy
This codebase follows these principles:
- Single Responsibility - Each function does one thing well
- Centralized Configuration - All magic values in
lib/constants.ts - Type Safety First - Strict TypeScript throughout
- Test Everything - 93 tests covering all functionality
- Clear Naming - Function names describe exactly what they do
- Comprehensive JSDoc - Every public function documented
๐ Codebase Structure
Quick Navigation
index.ts โ Main plugin entry (213 lines)
โโโ exports OpenAIAuthPlugin
โโโ contains fetch() implementation (42 lines, 7 steps)
โโโ contains authorize() OAuth flow
lib/
โโโ constants.ts โ ALL magic values, URLs, error messages
โโโ types.ts โ TypeScript type definitions
โโโ fetch-helpers.ts โ 10 focused helper functions
โโโ auth.ts โ OAuth/PKCE authentication
โโโ request-transformer.ts โ Request body transformation
โโโ response-handler.ts โ SSE to JSON conversion
โโโ browser.ts โ Platform-specific browser opening
โโโ codex.ts โ Codex instructions fetching
โโโ logger.ts โ Debug logging utilities
โโโ server.ts โ Local OAuth callback server
test/
โโโ fetch-helpers.test.ts โ 15 tests for helper functions
โโโ browser.test.ts โ 4 tests for platform detection
โโโ auth.test.ts โ 16 tests for OAuth flow
โโโ request-transformer.test.ts โ 30 tests for transformations
โโโ response-handler.test.ts โ 10 tests for SSE conversion
โโโ config.test.ts โ 13 tests for configuration
โโโ logger.test.ts โ 5 tests for logging
config/
โโโ full-opencode.json โ Complete configuration example
โโโ minimal-opencode.json โ Minimal configuration example
โโโ README.md โ Configuration documentation
๐ง Common Modification Scenarios
1. Adding a New Header
Location: lib/fetch-helpers.ts โ createCodexHeaders()
export function createCodexHeaders(
init: RequestInit | undefined,
accountId: string,
accessToken: string
): Headers {
const headers = new Headers(init?.headers ?? {});
// Add your new header here
headers.set("X-New-Header", "value");
return headers;
}
Test Location: test/fetch-helpers.test.ts
it('should include new header', () => {
const headers = createCodexHeaders(undefined, accountId, accessToken);
expect(headers.get('X-New-Header')).toBe('value');
});
2. Adding a New Constant
Location: lib/constants.ts
// Add to appropriate category
export const NEW_CONSTANT_GROUP = {
KEY1: "value1",
KEY2: "value2",
} as const;
Pattern: Always use as const for type safety and group related constants together.
3. Modifying URL Rewriting Logic
Location: lib/fetch-helpers.ts โ rewriteUrlForCodex()
export function rewriteUrlForCodex(url: string): string {
// Add new URL transformations here
return url
.replace(URL_PATHS.RESPONSES, URL_PATHS.CODEX_RESPONSES)
.replace(URL_PATHS.NEW_PATH, URL_PATHS.NEW_CODEX_PATH); // New transformation
}
Test Location: test/fetch-helpers.test.ts
it('should rewrite new path correctly', () => {
const url = 'https://example.com/new-path';
expect(rewriteUrlForCodex(url)).toBe('https://example.com/codex/new-path');
});
4. Adding Request Transformation Logic
Location: lib/request-transformer.ts โ transformRequestBody()
This function already handles:
- Reasoning effort configuration
- Reasoning summary configuration
- Text verbosity configuration
- System message injection
Pattern: Follow existing configuration pattern in transformRequestBody().
5. Modifying Token Refresh Logic
Location: lib/fetch-helpers.ts โ shouldRefreshToken() and refreshAndUpdateToken()
// Check condition
export function shouldRefreshToken(auth: Auth): boolean {
return auth.type !== "oauth"
|| !auth.access
|| auth.expires < Date.now()
|| yourNewCondition; // Add here
}
// Refresh implementation
export async function refreshAndUpdateToken(
currentAuth: Auth,
client: OpencodeClient
): Promise<{ success: true; auth: Auth } | { success: false; response: Response }> {
// Modification goes here
}
6. Adding a New Platform for Browser Opening
Location: lib/browser.ts โ getBrowserOpener()
Step 1: Add constant to lib/constants.ts
export const PLATFORM_OPENERS = {
darwin: "open",
win32: "start",
linux: "xdg-open",
freebsd: "xdg-open", // Add new platform
} as const;
Step 2: Update getBrowserOpener()
export function getBrowserOpener(): string {
const platform = process.platform;
if (platform === "darwin") return PLATFORM_OPENERS.darwin;
if (platform === "win32") return PLATFORM_OPENERS.win32;
if (platform === "freebsd") return PLATFORM_OPENERS.freebsd; // Add here
return PLATFORM_OPENERS.linux;
}
Step 3: Add test in test/browser.test.ts
it('should handle freebsd platform', () => {
const originalPlatform = process.platform;
Object.defineProperty(process, 'platform', { value: 'freebsd' });
expect(getBrowserOpener()).toBe(PLATFORM_OPENERS.freebsd);
Object.defineProperty(process, 'platform', { value: originalPlatform });
});
7. Adding Error Messages
Location: lib/constants.ts โ ERROR_MESSAGES
export const ERROR_MESSAGES = {
NO_ACCOUNT_ID: "Failed to extract accountId from token",
TOKEN_REFRESH_FAILED: "Failed to refresh token, authentication required",
NEW_ERROR: "Your new error message", // Add here
} as const;
Usage:
console.error(`[${PLUGIN_NAME}] ${ERROR_MESSAGES.NEW_ERROR}`);
8. Adding Configuration Options
Location: lib/types.ts โ ConfigOptions
export interface ConfigOptions {
reasoningEffort?: "minimal" | "low" | "medium" | "high";
reasoningSummary?: "auto" | "concise" | "detailed";
textVerbosity?: "low" | "medium" | "high";
newOption?: "value1" | "value2"; // Add here
include?: string[];
}
Then update: lib/request-transformer.ts to handle the new option.
๐งช Testing Requirements
When to Add Tests
ALWAYS add tests when:
- Adding new functions
- Modifying existing logic
- Adding new constants (if they affect behavior)
- Adding new configuration options
- Changing URL rewriting
- Modifying header creation
- Updating token refresh logic
Test Structure Pattern
describe('Module Name', () => {
describe('functionName', () => {
it('should handle normal case', () => {
// Arrange
const input = createTestInput();
// Act
const result = functionName(input);
// Assert
expect(result).toBe(expectedValue);
});
it('should handle edge case', () => {
// Test edge case
});
it('should handle error case', () => {
// Test error handling
});
});
});
Running Tests
npm test # Run all tests
npm test -- --watch # Watch mode
npm test -- --ui # Visual UI
npm test -- --coverage # Coverage report
Requirement: All 93 tests must pass before committing.
๐ Code Patterns to Follow
1. Function Naming
โ Good - Verb + Noun, describes what it does:
shouldRefreshToken()
createCodexHeaders()
extractRequestUrl()
rewriteUrlForCodex()
handleErrorResponse()
โ Bad - Vague or unclear:
checkToken() // Check what about token?
makeHeaders() // Make what kind?
getUrl() // Get from where?
2. JSDoc Comments
Always include for public functions:
/**
* Brief description of what function does
*
* @param paramName - Description of parameter
* @param anotherParam - Description of another parameter
* @returns Description of return value
*
* @example
* const result = functionName(input);
*/
export function functionName(paramName: Type, anotherParam: Type): ReturnType {
// implementation
}
3. Error Handling
Pattern: Return result objects instead of throwing
// โ
Good - Discriminated union return type
type Result =
| { success: true; data: Data }
| { success: false; error: Error };
export function doSomething(): Result {
if (errorCondition) {
return { success: false, error: new Error("message") };
}
return { success: true, data: result };
}
// Usage
const result = doSomething();
if (!result.success) {
return result.error;
}
// Use result.data
4. Logging
Pattern: Use constants and plugin name prefix
import { PLUGIN_NAME, ERROR_MESSAGES, LOG_STAGES } from "./constants.js";
// โ
Good
console.error(`[${PLUGIN_NAME}] ${ERROR_MESSAGES.TOKEN_REFRESH_FAILED}`);
logRequest(LOG_STAGES.RESPONSE, data);
// โ Bad
console.error("Failed to refresh token");
console.log("Response:", data);
5. Type Definitions
Pattern: Import types with type keyword
// โ
Good
import type { Auth, OpencodeClient } from "@opencode-ai/sdk";
import type { Plugin, PluginInput } from "@opencode-ai/plugin";
import type { UserConfig, ConfigOptions } from "./types.js";
// โ Bad
import { Auth } from "@opencode-ai/sdk"; // Runtime import when only type is needed
6. Constants
Pattern: Group related constants, use as const
// โ
Good
export const OPENAI_HEADERS = {
ACCOUNT_ID: "ChatGPT-Account-ID",
BETA: "OpenAI-Beta",
SESSION_ID: "OpenAI-Sentinel-Chat-Session-Id",
} as const;
// โ Bad
export const HEADER_ACCOUNT_ID = "ChatGPT-Account-ID";
export const HEADER_BETA = "OpenAI-Beta";
export const HEADER_SESSION_ID = "OpenAI-Sentinel-Chat-Session-Id";
๐ฏ Main Fetch Flow (Critical Understanding)
The fetch() function in index.ts is the heart of the plugin. It follows a strict 7-step flow:
async fetch(input: Request | string | URL, init?: RequestInit): Promise<Response> {
// Step 1: Check and refresh token if needed
const currentAuth = await getAuth();
if (shouldRefreshToken(currentAuth)) {
const refreshResult = await refreshAndUpdateToken(currentAuth, client);
if (!refreshResult.success) return refreshResult.response;
}
// Step 2: Extract and rewrite URL for Codex backend
const originalUrl = extractRequestUrl(input);
const url = rewriteUrlForCodex(originalUrl);
// Step 3: Transform request body with Codex instructions
const transformation = transformRequestForCodex(init, url, CODEX_INSTRUCTIONS, userConfig);
const hasTools = transformation?.body.tools !== undefined;
const requestInit = transformation?.updatedInit ?? init;
// Step 4: Create headers with OAuth and ChatGPT account info
const accessToken = currentAuth.type === "oauth" ? currentAuth.access : "";
const headers = createCodexHeaders(requestInit, accountId, accessToken);
// Step 5: Make request to Codex API
const response = await fetch(url, { ...requestInit, headers });
// Step 6: Log response
logRequest(LOG_STAGES.RESPONSE, { status, ok, statusText, headers });
// Step 7: Handle error or success response
if (!response.ok) return await handleErrorResponse(response);
return await handleSuccessResponse(response, hasTools);
}
Important: Each step is isolated in its own helper function for maintainability.
๐ Where to Look for Specific Functionality
| Need to modify... | Go to... |
|---|---|
| URL rewriting | lib/fetch-helpers.ts โ rewriteUrlForCodex() |
| Headers | lib/fetch-helpers.ts โ createCodexHeaders() |
| Token refresh | lib/fetch-helpers.ts โ refreshAndUpdateToken() |
| Token validation | lib/fetch-helpers.ts โ shouldRefreshToken() |
| Request body transformation | lib/request-transformer.ts โ transformRequestBody() |
| SSE to JSON conversion | lib/response-handler.ts โ convertSseToJson() |
| Error handling | lib/fetch-helpers.ts โ handleErrorResponse() |
| OAuth flow | lib/auth.ts โ createAuthorizationFlow() |
| Browser opening | lib/browser.ts โ openBrowserUrl() |
| Codex instructions | lib/codex.ts โ getCodexInstructions() |
| Any constant/magic value | lib/constants.ts |
| Type definitions | lib/types.ts |
๐ Quick Start for Common Tasks
Task: Fix a Bug in Token Refresh
- Read
lib/fetch-helpers.tsโshouldRefreshToken()andrefreshAndUpdateToken() - Read existing tests in
test/fetch-helpers.test.ts - Add test case reproducing the bug
- Fix the bug in the helper function
- Verify all 93 tests pass:
npm test - Run build:
npm run build
Task: Add Support for New Model Configuration
- Update
lib/types.tsโConfigOptionsinterface - Update
lib/request-transformer.tsโtransformRequestBody() - Add tests in
test/request-transformer.test.ts - Update
config/full-opencode.jsonwith example - Update
config/README.mdwith documentation - Verify all tests pass
Task: Change How URLs are Rewritten
- Update
lib/fetch-helpers.tsโrewriteUrlForCodex() - Update constants in
lib/constants.tsif needed - Add tests in
test/fetch-helpers.test.ts - Verify all tests pass
Task: Improve Error Messages
- Update
lib/constants.tsโERROR_MESSAGES - Update usage in relevant files (search for old message)
- Update tests that check error messages
- Verify all tests pass
โ ๏ธ Critical Rules
- NEVER modify code without reading relevant tests first
- ALWAYS run tests before and after changes
- NEVER use magic strings - add to
lib/constants.ts - ALWAYS add JSDoc to new public functions
- NEVER create functions > 70 lines - extract helpers
- ALWAYS use discriminated unions for result types
- NEVER throw exceptions - return error objects
- ALWAYS import types with
typekeyword when only type is needed - NEVER commit with failing tests
- ALWAYS check TypeScript compilation:
npm run build
๐ Quality Checklist
Before completing any task:
- All 93 tests pass (
npm test) - TypeScript compiles without errors (
npm run build) - New functions have JSDoc comments
- New functionality has tests
- No magic strings (all in
constants.ts) - Functions are < 70 lines
- Clear, descriptive naming
- Types imported with
typekeyword where applicable - No
anytypes used - Error handling follows result pattern
๐ Understanding the Architecture
Why 10 Helper Functions?
The original fetch() was 148 lines. Breaking it into 10 focused functions:
- Reduces cognitive load (each function < 40 lines)
- Enables isolated testing
- Makes modifications surgical instead of risky
- Allows LLMs to load only relevant context
- Documents the flow through function names
Why Centralized Constants?
Having all magic values in lib/constants.ts:
- Single source of truth
- Easy to find and modify
- Type-safe access
- Reveals all configurable values at a glance
- Reduces context needed to understand strings/values
Why Discriminated Unions for Results?
Pattern like { success: true; data } | { success: false; error }:
- Type-safe error handling
- Forces consumers to handle errors
- No unexpected exceptions
- Clear success/failure paths
- Better than throwing exceptions
Why Separate Test Files?
Each module has its own test file:
- Tests colocated with functionality conceptually
- Easy to find relevant tests
- Faster test execution (can run subset)
- Clear coverage per module
- Easier to maintain
๐ฎ Future Extension Points
The architecture makes these additions easy:
- New authentication method โ Add to
lib/auth.ts, follow OAuth pattern - New model variant โ Update
config/examples, no code changes needed - New configuration option โ Update
lib/types.tsandlib/request-transformer.ts - New platform support โ Add to
lib/browser.tsandlib/constants.ts - Additional headers โ Modify
createCodexHeaders()inlib/fetch-helpers.ts - Different response format โ Add handler in
lib/response-handler.ts - Enhanced logging โ Update
lib/logger.ts - Rate limiting โ Add helper in
lib/fetch-helpers.ts, integrate in step flow
๐ Additional Resources
- config/README.md - Configuration examples and documentation
- test/ - Reference implementations showing how to use each module
- lib/types.ts - Complete type definitions for the plugin
๐ค Maintenance Philosophy
This codebase is optimized for AI agent maintenance. When making changes:
- Read first, write second - Understand existing patterns before modifying
- Follow established patterns - Consistency reduces cognitive load
- Test everything - Tests are documentation and safety net
- Keep functions small - < 70 lines, single responsibility
- Document intent - JSDoc explains the "why", code shows the "how"
- Use type system - Let TypeScript catch errors early
- Centralize configuration - DRY principle for all constants
Remember: The goal is maintainability by AI assistants. Every change should make the codebase easier to understand, modify, and extend.