mirror of
https://github.com/hanzoai/opencode-openai-codex-auth.git
synced 2026-09-24 17:01:02 +00:00
## CODEX_MODE Implementation
Added configurable CODEX_MODE that replaces OpenCode system prompts with
a Codex-OpenCode bridge prompt for better Codex CLI parity.
**Key Features:**
- Enabled by default for optimal Codex CLI compatibility
- Configurable via ~/.opencode/openai-codex-auth-config.json
- Environment variable override support (CODEX_MODE=0 or CODEX_MODE=1)
- Priority: env var > config file > default (true)
**Bridge Prompt:**
- ~450 tokens (~90% reduction vs full OpenCode prompt)
- Critical tool replacements (apply_patch → edit, update_plan → todowrite)
- Available tools list and verification checklist
- OpenCode working style guidelines
**Configuration:**
```json
{
"codexMode": true // default
}
```
## Library Reorganization
Restructured lib/ into semantic subfolders:
```
lib/
├── auth/ # OAuth authentication
│ ├── auth.ts
│ ├── browser.ts
│ └── server.ts
├── prompts/ # System prompts
│ ├── codex.ts
│ └── codex-opencode-bridge.ts
├── request/ # Request handling
│ ├── fetch-helpers.ts
│ ├── request-transformer.ts
│ └── response-handler.ts
├── config.ts # Plugin configuration
├── constants.ts
├── logger.ts
├── types.ts
└── oauth-success.html
```
## Files Changed
**New Files:**
- lib/config.ts - Plugin configuration loading
- lib/prompts/codex-opencode-bridge.ts - Bridge prompt
- test/plugin-config.test.ts - 12 new tests
**Modified:**
- lib/request/request-transformer.ts - CODEX_MODE parameter
- lib/request/fetch-helpers.ts - Pass codexMode through pipeline
- index.ts - Load config and determine codexMode
- lib/types.ts - Add PluginConfig type
- README.md - Document CODEX_MODE configuration
**Reorganized:**
- lib/*.ts → lib/{auth,prompts,request}/*.ts
- Updated all imports across codebase and tests
## Testing
- 123 total tests (added 12 new tests for config loading)
- All tests passing
- Comprehensive CODEX_MODE coverage:
- Config file loading (exists, missing, invalid)
- Priority order testing
- Environment variable override
- Bridge prompt vs tool remap behavior
## Documentation
Updated README.md:
- Added "Plugin Configuration" section
- Documented CODEX_MODE setting and priority
- Updated features list (123 tests, CODEX_MODE)
- Updated "How It Works" section
Co-authored-by: Hanzo Dev <[email protected]>
164 lines
4.4 KiB
TypeScript
164 lines
4.4 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { loadPluginConfig, getCodexMode } from '../lib/config.js';
|
|
import type { PluginConfig } from '../lib/types.js';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
|
|
// Mock the fs module
|
|
vi.mock('node:fs', async () => {
|
|
const actual = await vi.importActual<typeof import('node:fs')>('node:fs');
|
|
return {
|
|
...actual,
|
|
existsSync: vi.fn(),
|
|
readFileSync: vi.fn(),
|
|
};
|
|
});
|
|
|
|
describe('Plugin Configuration', () => {
|
|
const mockExistsSync = vi.mocked(fs.existsSync);
|
|
const mockReadFileSync = vi.mocked(fs.readFileSync);
|
|
let originalEnv: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
originalEnv = process.env.CODEX_MODE;
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (originalEnv === undefined) {
|
|
delete process.env.CODEX_MODE;
|
|
} else {
|
|
process.env.CODEX_MODE = originalEnv;
|
|
}
|
|
});
|
|
|
|
describe('loadPluginConfig', () => {
|
|
it('should return default config when file does not exist', () => {
|
|
mockExistsSync.mockReturnValue(false);
|
|
|
|
const config = loadPluginConfig();
|
|
|
|
expect(config).toEqual({ codexMode: true });
|
|
expect(mockExistsSync).toHaveBeenCalledWith(
|
|
path.join(os.homedir(), '.opencode', 'openai-codex-auth-config.json')
|
|
);
|
|
});
|
|
|
|
it('should load config from file when it exists', () => {
|
|
mockExistsSync.mockReturnValue(true);
|
|
mockReadFileSync.mockReturnValue(JSON.stringify({ codexMode: false }));
|
|
|
|
const config = loadPluginConfig();
|
|
|
|
expect(config).toEqual({ codexMode: false });
|
|
});
|
|
|
|
it('should merge user config with defaults', () => {
|
|
mockExistsSync.mockReturnValue(true);
|
|
mockReadFileSync.mockReturnValue(JSON.stringify({}));
|
|
|
|
const config = loadPluginConfig();
|
|
|
|
expect(config).toEqual({ codexMode: true });
|
|
});
|
|
|
|
it('should handle invalid JSON gracefully', () => {
|
|
mockExistsSync.mockReturnValue(true);
|
|
mockReadFileSync.mockReturnValue('invalid json');
|
|
|
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
const config = loadPluginConfig();
|
|
|
|
expect(config).toEqual({ codexMode: true });
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
consoleSpy.mockRestore();
|
|
});
|
|
|
|
it('should handle file read errors gracefully', () => {
|
|
mockExistsSync.mockReturnValue(true);
|
|
mockReadFileSync.mockImplementation(() => {
|
|
throw new Error('Permission denied');
|
|
});
|
|
|
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
const config = loadPluginConfig();
|
|
|
|
expect(config).toEqual({ codexMode: true });
|
|
expect(consoleSpy).toHaveBeenCalled();
|
|
consoleSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
describe('getCodexMode', () => {
|
|
it('should return true by default', () => {
|
|
delete process.env.CODEX_MODE;
|
|
const config: PluginConfig = {};
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should use config value when env var not set', () => {
|
|
delete process.env.CODEX_MODE;
|
|
const config: PluginConfig = { codexMode: false };
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should prioritize env var CODEX_MODE=1 over config', () => {
|
|
process.env.CODEX_MODE = '1';
|
|
const config: PluginConfig = { codexMode: false };
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it('should prioritize env var CODEX_MODE=0 over config', () => {
|
|
process.env.CODEX_MODE = '0';
|
|
const config: PluginConfig = { codexMode: true };
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should handle env var with any value other than "1" as false', () => {
|
|
process.env.CODEX_MODE = 'false';
|
|
const config: PluginConfig = { codexMode: true };
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
|
|
it('should use config codexMode=true when explicitly set', () => {
|
|
delete process.env.CODEX_MODE;
|
|
const config: PluginConfig = { codexMode: true };
|
|
|
|
const result = getCodexMode(config);
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Priority order', () => {
|
|
it('should follow priority: env var > config file > default', () => {
|
|
// Test 1: env var overrides config
|
|
process.env.CODEX_MODE = '0';
|
|
expect(getCodexMode({ codexMode: true })).toBe(false);
|
|
|
|
// Test 2: config overrides default
|
|
delete process.env.CODEX_MODE;
|
|
expect(getCodexMode({ codexMode: false })).toBe(false);
|
|
|
|
// Test 3: default when neither set
|
|
expect(getCodexMode({})).toBe(true);
|
|
});
|
|
});
|
|
});
|