Files
opencode-openai-codex-auth/AGENTS.md
T
Numman AliandHanzo Dev 7dae285752 feat: Major TypeScript rewrite with enhanced configuration and OAuth UI
## ๐ŸŽฏ 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]>
2025-10-03 21:51:59 +01:00

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:

  1. Single Responsibility - Each function does one thing well
  2. Centralized Configuration - All magic values in lib/constants.ts
  3. Type Safety First - Strict TypeScript throughout
  4. Test Everything - 93 tests covering all functionality
  5. Clear Naming - Function names describe exactly what they do
  6. 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

  1. Read lib/fetch-helpers.ts โ†’ shouldRefreshToken() and refreshAndUpdateToken()
  2. Read existing tests in test/fetch-helpers.test.ts
  3. Add test case reproducing the bug
  4. Fix the bug in the helper function
  5. Verify all 93 tests pass: npm test
  6. Run build: npm run build

Task: Add Support for New Model Configuration

  1. Update lib/types.ts โ†’ ConfigOptions interface
  2. Update lib/request-transformer.ts โ†’ transformRequestBody()
  3. Add tests in test/request-transformer.test.ts
  4. Update config/full-opencode.json with example
  5. Update config/README.md with documentation
  6. Verify all tests pass

Task: Change How URLs are Rewritten

  1. Update lib/fetch-helpers.ts โ†’ rewriteUrlForCodex()
  2. Update constants in lib/constants.ts if needed
  3. Add tests in test/fetch-helpers.test.ts
  4. Verify all tests pass

Task: Improve Error Messages

  1. Update lib/constants.ts โ†’ ERROR_MESSAGES
  2. Update usage in relevant files (search for old message)
  3. Update tests that check error messages
  4. Verify all tests pass

โš ๏ธ Critical Rules

  1. NEVER modify code without reading relevant tests first
  2. ALWAYS run tests before and after changes
  3. NEVER use magic strings - add to lib/constants.ts
  4. ALWAYS add JSDoc to new public functions
  5. NEVER create functions > 70 lines - extract helpers
  6. ALWAYS use discriminated unions for result types
  7. NEVER throw exceptions - return error objects
  8. ALWAYS import types with type keyword when only type is needed
  9. NEVER commit with failing tests
  10. 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 type keyword where applicable
  • No any types 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:

  1. New authentication method โ†’ Add to lib/auth.ts, follow OAuth pattern
  2. New model variant โ†’ Update config/ examples, no code changes needed
  3. New configuration option โ†’ Update lib/types.ts and lib/request-transformer.ts
  4. New platform support โ†’ Add to lib/browser.ts and lib/constants.ts
  5. Additional headers โ†’ Modify createCodexHeaders() in lib/fetch-helpers.ts
  6. Different response format โ†’ Add handler in lib/response-handler.ts
  7. Enhanced logging โ†’ Update lib/logger.ts
  8. 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:

  1. Read first, write second - Understand existing patterns before modifying
  2. Follow established patterns - Consistency reduces cognitive load
  3. Test everything - Tests are documentation and safety net
  4. Keep functions small - < 70 lines, single responsibility
  5. Document intent - JSDoc explains the "why", code shows the "how"
  6. Use type system - Let TypeScript catch errors early
  7. 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.