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]>
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest';
|
|
import { ensureContentType, convertSseToJson } from '../lib/request/response-handler.js';
|
|
|
|
describe('Response Handler Module', () => {
|
|
describe('ensureContentType', () => {
|
|
it('should preserve existing content-type', () => {
|
|
const headers = new Headers();
|
|
headers.set('content-type', 'application/json');
|
|
const result = ensureContentType(headers);
|
|
expect(result.get('content-type')).toBe('application/json');
|
|
});
|
|
|
|
it('should add default content-type if missing', () => {
|
|
const headers = new Headers();
|
|
const result = ensureContentType(headers);
|
|
expect(result.get('content-type')).toBe('text/event-stream; charset=utf-8');
|
|
});
|
|
|
|
it('should not modify original headers', () => {
|
|
const headers = new Headers();
|
|
const result = ensureContentType(headers);
|
|
expect(headers.has('content-type')).toBe(false);
|
|
expect(result.has('content-type')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('convertSseToJson', () => {
|
|
it('should throw error if response has no body', async () => {
|
|
const response = new Response(null);
|
|
const headers = new Headers();
|
|
|
|
await expect(convertSseToJson(response, headers)).rejects.toThrow(
|
|
'Response has no body'
|
|
);
|
|
});
|
|
|
|
it('should parse SSE stream with response.done event', async () => {
|
|
const sseContent = `data: {"type":"response.started"}
|
|
data: {"type":"response.done","response":{"id":"resp_123","output":"test"}}
|
|
`;
|
|
const response = new Response(sseContent);
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
const body = await result.json();
|
|
|
|
expect(body).toEqual({ id: 'resp_123', output: 'test' });
|
|
expect(result.headers.get('content-type')).toBe('application/json; charset=utf-8');
|
|
});
|
|
|
|
it('should parse SSE stream with response.completed event', async () => {
|
|
const sseContent = `data: {"type":"response.started"}
|
|
data: {"type":"response.completed","response":{"id":"resp_456","output":"done"}}
|
|
`;
|
|
const response = new Response(sseContent);
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
const body = await result.json();
|
|
|
|
expect(body).toEqual({ id: 'resp_456', output: 'done' });
|
|
});
|
|
|
|
it('should return original text if no final response found', async () => {
|
|
const sseContent = `data: {"type":"response.started"}
|
|
data: {"type":"chunk","delta":"text"}
|
|
`;
|
|
const response = new Response(sseContent);
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
const text = await result.text();
|
|
|
|
expect(text).toBe(sseContent);
|
|
});
|
|
|
|
it('should skip malformed JSON in SSE stream', async () => {
|
|
const sseContent = `data: not-json
|
|
data: {"type":"response.done","response":{"id":"resp_789"}}
|
|
`;
|
|
const response = new Response(sseContent);
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
const body = await result.json();
|
|
|
|
expect(body).toEqual({ id: 'resp_789' });
|
|
});
|
|
|
|
it('should handle empty SSE stream', async () => {
|
|
const response = new Response('');
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
const text = await result.text();
|
|
|
|
expect(text).toBe('');
|
|
});
|
|
|
|
it('should preserve response status and statusText', async () => {
|
|
const sseContent = `data: {"type":"response.done","response":{"id":"x"}}`;
|
|
const response = new Response(sseContent, {
|
|
status: 200,
|
|
statusText: 'OK',
|
|
});
|
|
const headers = new Headers();
|
|
|
|
const result = await convertSseToJson(response, headers);
|
|
|
|
expect(result.status).toBe(200);
|
|
expect(result.statusText).toBe('OK');
|
|
});
|
|
});
|
|
});
|