This release includes comprehensive OpenAI ToS compliance updates and fixes 4 critical bugs that prevented per-model options and multi-turn conversations from working correctly. ## ๐ Compliance & Legal Updates ### Terms of Service & Usage Guidelines - Added comprehensive ToS & Usage Notice to README emphasizing personal use only - Added Rate Limits & Responsible Use section with best practices - Added extensive FAQ section addressing TOS compliance questions - Updated LICENSE with MIT + Usage Disclaimer for personal development use - Added compliance header to index.ts documenting intended use cases ### New Documentation - **CONTRIBUTING.md**: Complete contribution guidelines with compliance requirements - **SECURITY.md**: Security policy, vulnerability reporting, best practices - **docs/privacy.md**: Comprehensive privacy & data handling documentation - **Issue Templates**: Bug reports & feature requests with compliance checklists ### Documentation Updates - Updated docs/index.md with usage notice and trademark disclaimers - Updated docs/getting-started.md with "Before You Begin" compliance notice - Updated docs/troubleshooting.md with subscription & compliance sections - All references updated to "Plus/Pro" (removed Team/Enterprise mentions) ### Key Compliance Principles - โ Emphasizes personal development use with own ChatGPT Plus/Pro subscription - โ Uses OpenAI's official OAuth authentication (same as Codex CLI) - โ Clear prohibition of commercial resale, multi-user services - โ Not a "free API alternative" - uses existing ChatGPT subscription - โ Proper trademark notices and attribution to OpenAI - โ Transparent about data handling and privacy ## ๐ Critical Bug Fixes ### Bug #1: Per-Model Options Ignored (Config Lookup Mismatch) **Severity:** ๐ด HIGH Users configured different reasoning levels for each model variant, but all variants behaved identically because the plugin was looking up normalized model names instead of the original config keys. **Fix:** lib/request/request-transformer.ts:277 - Use original model name for config lookup before normalization - Allows per-model options to work correctly - Impact: Different reasoning levels now properly applied per variant ### Bug #2: Multi-Turn Conversations Fail (AI SDK Compatibility) **Severity:** ๐ด CRITICAL Multi-turn conversations failed with "Item not found" errors because: 1. AI SDK sends `item_reference` (not in Codex API spec) 2. IDs weren't completely stripped for stateless mode (store: false) **Fix:** lib/request/request-transformer.ts:114-135 - Filter out AI SDK `item_reference` items - Strip ALL IDs from remaining items (not just rs_* prefix) - Confirmed ChatGPT backend requires store: false via testing - Full message history preserved for LLM context ### Bug #3: Case-Sensitive Normalization **Severity:** ๐ก MEDIUM **Fix:** lib/request/request-transformer.ts:22-27 - Added toLowerCase() for case-insensitive matching - Backwards compatible with old verbose config names - Handles uppercase/mixed case user input ### Bug #4: GitHub API Rate Limiting **Severity:** ๐ก MEDIUM Plugin checked GitHub on every request, exhausting 60 req/hour limit. 15-minute cache timestamp was stored but never checked. **Fix:** lib/prompts/codex.ts:50-53, lib/prompts/opencode-codex.ts:47-50 - Check cache TTL before GitHub API call - Only fetch if cache >15min old - Prevents rate limit exhaustion during testing ## โจ Enhancements ### Debug Logging System - New environment variable: `DEBUG_CODEX_PLUGIN=1` - Added logDebug() and logWarn() functions - Shows config lookups, ID filtering, options resolution - File: lib/logger.ts ### Optimized Config Structure - Changed to Codex CLI preset names (gpt-5-codex-low, etc.) - Removed redundant `id` field (not used by OpenAI provider) - Added `name` field for friendly TUI display - File: config/full-opencode.json ### GitHub Pages Documentation - Complete documentation restructure for GitHub Pages - User guides: getting-started.md, configuration.md, troubleshooting.md - Developer guides: ARCHITECTURE.md, CONFIG_FLOW.md, CONFIG_FIELDS.md, TESTING.md - Privacy policy, security policy, contribution guidelines ## ๐งช Testing ### Test Coverage - โ 159 unit tests passing (30+ new tests) - โ 14 integration tests passing (actual API verification) - โ All documented scenarios have test coverage ### Integration Tests (scripts/test-all-models.sh) - Tests all 9 custom model configurations - Tests all 4 default OpenCode models - Verifies backwards compatibility with old config format - Uses ENABLE_PLUGIN_REQUEST_LOGGING to verify actual API requests ### Test Results - 14/14 model configuration tests passing - All per-model options verified correct - Multi-turn conversations work without errors - Backwards compatibility confirmed ## ๐ Files Changed ### Modified (7 files) - lib/request/request-transformer.ts: Config lookup & ID filtering fixes - lib/prompts/codex.ts: 15-minute cache TTL - lib/prompts/opencode-codex.ts: 15-minute cache TTL - lib/logger.ts: Debug logging system - config/full-opencode.json: Optimized structure - test/request-transformer.test.ts: 30+ new tests - LICENSE: Added usage disclaimer - index.ts: Added compliance header - README.md: Added ToS, Rate Limits, FAQ sections ### Created (20+ files) - CONTRIBUTING.md: Contribution guidelines - SECURITY.md: Security policy - docs/index.md: GitHub Pages landing - docs/getting-started.md: Installation guide - docs/configuration.md: Advanced config - docs/troubleshooting.md: Debug guide - docs/privacy.md: Privacy policy - docs/development/ARCHITECTURE.md: Technical architecture - docs/development/CONFIG_FLOW.md: Config system internals - docs/development/CONFIG_FIELDS.md: Field usage guide - docs/development/TESTING.md: Testing guide - .github/ISSUE_TEMPLATE/bug_report.md - .github/ISSUE_TEMPLATE/feature_request.md - .github/ISSUE_TEMPLATE/config.yml - scripts/test-all-models.sh: Automated testing ## ๐ฏ Verification All scenarios tested and verified: - โ Default models work without config - โ Custom models with per-variant options - โ Old config format (backwards compatible) - โ Mixed default + custom models - โ Multi-turn conversations (no "item not found" errors) - โ Model switching with different options - โ All normalization patterns - โ Complete ID filtering ## ๐ Documentation Complete documentation suite (100+ KB): - User guides for installation, configuration, troubleshooting - Developer guides for architecture, config system, testing - Privacy policy and security policy - Compliance guidelines and contribution requirements - Release notes in tmp/release-notes/ ## ๐ References - OpenAI Terms of Use: https://openai.com/policies/terms-of-use/ - OpenAI Usage Policies: https://openai.com/policies/usage-policies/ - OpenAI Platform API: https://platform.openai.com/ - OpenAI Codex CLI: https://github.com/openai/codex Co-authored-by: Hanzo Dev <[email protected]>
17 KiB
Complete Test Scenarios
Comprehensive testing matrix for all config scenarios and backwards compatibility.
Test Scenarios Matrix
Scenario 1: Default OpenCode Models (No Custom Config)
Config:
{
"plugin": ["opencode-openai-codex-auth"]
}
Available Models: (from OpenCode's models.dev database)
gpt-5gpt-5-codexgpt-5-minigpt-5-nano
Test Cases:
| User Selects | Plugin Receives | Normalizes To | Config Lookup | API Receives | Result |
|---|---|---|---|---|---|
openai/gpt-5 |
"gpt-5" |
"gpt-5" |
models["gpt-5"] โ undefined |
"gpt-5" |
โ Uses global options |
openai/gpt-5-codex |
"gpt-5-codex" |
"gpt-5-codex" |
models["gpt-5-codex"] โ undefined |
"gpt-5-codex" |
โ Uses global options |
openai/gpt-5-mini |
"gpt-5-mini" |
"gpt-5" |
models["gpt-5-mini"] โ undefined |
"gpt-5" |
โ Uses global options |
openai/gpt-5-nano |
"gpt-5-nano" |
"gpt-5" |
models["gpt-5-nano"] โ undefined |
"gpt-5" |
โ Uses global options |
Expected Behavior:
- โ All models work with global options
- โ Normalized correctly for API
- โ No errors
Scenario 2: Custom Config with Preset Names (New Style)
Config:
{
"plugin": ["opencode-openai-codex-auth"],
"provider": {
"openai": {
"options": {
"reasoningEffort": "medium"
},
"models": {
"gpt-5-codex-low": {
"name": "GPT 5 Codex Low (OAuth)",
"options": { "reasoningEffort": "low" }
},
"gpt-5-codex-high": {
"name": "GPT 5 Codex High (OAuth)",
"options": { "reasoningEffort": "high" }
}
}
}
}
}
Test Cases:
| User Selects | Plugin Receives | Config Lookup | Resolved Options | API Receives | Result |
|---|---|---|---|---|---|
openai/gpt-5-codex-low |
"gpt-5-codex-low" |
Found โ | { reasoningEffort: "low" } |
"gpt-5-codex" |
โ Per-model |
openai/gpt-5-codex-high |
"gpt-5-codex-high" |
Found โ | { reasoningEffort: "high" } |
"gpt-5-codex" |
โ Per-model |
openai/gpt-5-codex |
"gpt-5-codex" |
Not found | { reasoningEffort: "medium" } |
"gpt-5-codex" |
โ Global |
Expected Behavior:
- โ Custom variants use per-model options
- โ
Default
gpt-5-codexuses global options - โ
Both normalize to
"gpt-5-codex"for API
Scenario 3: Old Config (Backwards Compatibility)
Config:
{
"plugin": ["opencode-openai-codex-auth"],
"provider": {
"openai": {
"options": {
"reasoningEffort": "medium"
},
"models": {
"GPT 5 Codex Low (ChatGPT Subscription)": {
"id": "gpt-5-codex",
"options": { "reasoningEffort": "low" }
}
}
}
}
}
Test Cases:
| User Selects | Plugin Receives | Config Lookup | Resolved Options | API Receives | Result |
|---|---|---|---|---|---|
openai/GPT 5 Codex Low (ChatGPT Subscription) |
"GPT 5 Codex Low (ChatGPT Subscription)" |
Found โ | { reasoningEffort: "low" } |
"gpt-5-codex" |
โ Per-model |
Expected Behavior:
- โ Old config keys still work
- โ Per-model options applied correctly
- โ Normalizes correctly for API
Scenario 4: Mixed Config (Default + Custom)
Config:
{
"plugin": ["opencode-openai-codex-auth"],
"provider": {
"openai": {
"models": {
"gpt-5-codex-low": {
"name": "GPT 5 Codex Low (OAuth)",
"options": { "reasoningEffort": "low" }
}
}
}
}
}
Available Models:
gpt-5-codex-low(custom)gpt-5-codex(default from models.dev)gpt-5(default from models.dev)
Test Cases:
| User Selects | Config Lookup | Uses Options | Result |
|---|---|---|---|
openai/gpt-5-codex-low |
Found โ | Per-model | โ Custom config |
openai/gpt-5-codex |
Not found | Global | โ Default model |
openai/gpt-5 |
Not found | Global | โ Default model |
Expected Behavior:
- โ Custom variants use per-model options
- โ Default models use global options
- โ Both types coexist peacefully
Scenario 5: Edge Cases
5a: Model Name with Uppercase
Config:
{
"models": {
"GPT-5-CODEX-HIGH": {
"options": { "reasoningEffort": "high" }
}
}
}
Test:
User selects: openai/GPT-5-CODEX-HIGH
Plugin receives: "GPT-5-CODEX-HIGH"
normalizeModel: "GPT-5-CODEX-HIGH" โ "gpt-5-codex" โ
(includes "codex")
Config lookup: models["GPT-5-CODEX-HIGH"] โ Found โ
API receives: "gpt-5-codex" โ
Result: โ Works (case-insensitive includes())
5b: Model Name with Special Characters
Config:
{
"models": {
"my-gpt5-codex-variant": {
"options": { "reasoningEffort": "high" }
}
}
}
Test:
User selects: openai/my-gpt5-codex-variant
Plugin receives: "my-gpt5-codex-variant"
normalizeModel: "my-gpt5-codex-variant" โ "gpt-5-codex" โ
(includes "codex")
Config lookup: models["my-gpt5-codex-variant"] โ Found โ
API receives: "gpt-5-codex" โ
Result: โ Works (normalization handles it)
5c: No Config, No Model Specified
Config:
{
"plugin": ["opencode-openai-codex-auth"]
}
Test:
User selects: (none - uses OpenCode default)
Plugin receives: undefined or default from OpenCode
normalizeModel: undefined โ "gpt-5" โ
(fallback)
Config lookup: models[undefined] โ undefined
API receives: "gpt-5" โ
Result: โ Works (safe fallback)
5d: Only gpt-5 in Name (No codex)
Config:
{
"models": {
"my-gpt-5-variant": {
"options": { "reasoningEffort": "high" }
}
}
}
Test:
User selects: openai/my-gpt-5-variant
Plugin receives: "my-gpt-5-variant"
normalizeModel: "my-gpt-5-variant" โ "gpt-5" โ
(includes "gpt-5", not "codex")
Config lookup: models["my-gpt-5-variant"] โ Found โ
API receives: "gpt-5" โ
Result: โ Works (correct model selected)
Scenario 6: Multi-Turn Conversation (store:false Test)
Config: Any
Test Sequence:
Turn 1: > write hello to test.txt
Turn 2: > read the file
Turn 3: > what did you write?
Turn 4: > now delete it
What Plugin Should Do:
| Turn | Input Has IDs? | Filter Result | Encrypted Content | Result |
|---|---|---|---|---|
| 1 | No | No filtering needed | Received in response | โ Works |
| 2 | Yes (from Turn 1) | ALL removed โ | Sent back in request | โ Works |
| 3 | Yes (from Turn 1-2) | ALL removed โ | Sent back in request | โ Works |
| 4 | Yes (from Turn 1-3) | ALL removed โ | Sent back in request | โ Works |
Expected Behavior:
- โ No "item not found" errors on any turn
- โ Context preserved via encrypted reasoning
- โ Debug log shows: "Successfully removed all X message IDs"
Backwards Compatibility Testing
Test Matrix
| Plugin Version | Config Format | Expected Result |
|---|---|---|
| Old (<2.1.2) | Long names + id | โ Per-model options broken, ID errors |
| Old (<2.1.2) | Short names | โ Per-model options broken, ID errors |
| New (2.1.2+) | Long names + id | โ ALL FIXED |
| New (2.1.2+) | Short names | โ ALL FIXED |
| New (2.1.2+) | Short names (no id) | โ OPTIMAL |
Backwards Compatibility Tests
Test 1: Old Plugin User Upgrades
Before (Plugin v2.1.1):
{
"models": {
"GPT 5 Codex Low (ChatGPT Subscription)": {
"id": "gpt-5-codex",
"options": { "reasoningEffort": "low" }
}
}
}
After (Plugin v2.1.2):
- Keep same config
- Plugin now finds per-model options โ
- No "item not found" errors โ
Result: โ Works without config changes
Test 2: New User with Recommended Config
Config:
{
"models": {
"gpt-5-codex-low": {
"name": "GPT 5 Codex Low (OAuth)",
"options": { "reasoningEffort": "low" }
}
}
}
Expected:
- CLI:
--model=openai/gpt-5-codex-lowโ - TUI: Shows "GPT 5 Codex Low (OAuth)" โ
- Plugin: Finds and applies per-model options โ
- API: Receives
"gpt-5-codex"โ
Result: โ Optimal experience
Test 3: Minimal Config (No Custom Models)
Config:
{
"plugin": ["opencode-openai-codex-auth"],
"model": "openai/gpt-5-codex"
}
Expected:
- Uses default OpenCode model:
gpt-5-codex - Plugin applies: Global options + Codex defaults
- No errors โ
Result: โ Works out of the box
Debug Logging Test Cases
Enable Debug Mode
DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex-low
Expected Debug Output
Case 1: Custom Model with Config
[openai-codex-plugin] Debug logging ENABLED
[openai-codex-plugin] Model config lookup: "gpt-5-codex-low" โ normalized to "gpt-5-codex" for API {
hasModelSpecificConfig: true,
resolvedConfig: {
reasoningEffort: 'low',
textVerbosity: 'medium',
reasoningSummary: 'auto',
include: ['reasoning.encrypted_content']
}
}
[openai-codex-plugin] Filtering 0 message IDs from input: []
โ
Verify: hasModelSpecificConfig: true confirms per-model options found
Case 2: Default Model (No Custom Config)
DEBUG_CODEX_PLUGIN=1 opencode run "test" --model=openai/gpt-5-codex
[openai-codex-plugin] Debug logging ENABLED
[openai-codex-plugin] Model config lookup: "gpt-5-codex" โ normalized to "gpt-5-codex" for API {
hasModelSpecificConfig: false,
resolvedConfig: {
reasoningEffort: 'medium',
textVerbosity: 'medium',
reasoningSummary: 'auto',
include: ['reasoning.encrypted_content']
}
}
[openai-codex-plugin] Filtering 0 message IDs from input: []
โ
Verify: hasModelSpecificConfig: false confirms using global options
Case 3: Multi-Turn with ID Filtering
[openai-codex-plugin] Filtering 3 message IDs from input: ['msg_abc123', 'rs_xyz789', 'msg_def456']
[openai-codex-plugin] Successfully removed all 3 message IDs
โ Verify: All IDs removed, no warnings
Case 4: Warning if IDs Leak (Should Never Happen)
[openai-codex-plugin] WARNING: 1 IDs still present after filtering: ['msg_abc123']
โ This would indicate a bug - should never appear
Integration Test Plan
Manual Testing Procedure
Step 1: Fresh Install Test
# 1. Clear cache
(cd ~ && rm -rf .cache/opencode/node_modules/opencode-openai-codex-auth)
# 2. Use minimal config
cat > ~/.config/opencode/opencode.json <<'EOF'
{
"plugin": ["opencode-openai-codex-auth"],
"model": "openai/gpt-5-codex"
}
EOF
# 3. Test default model
DEBUG_CODEX_PLUGIN=1 opencode run "write hello world to test.txt"
Verify:
- โ Plugin installs automatically
- โ Auth works
- โ
Debug log shows:
hasModelSpecificConfig: false - โ
Model normalizes to
"gpt-5-codex" - โ No errors
Step 2: Custom Config Test
# Update config with custom models
cat > ~/.config/opencode/opencode.json <<'EOF'
{
"plugin": ["opencode-openai-codex-auth"],
"provider": {
"openai": {
"models": {
"gpt-5-codex-low": {
"name": "GPT 5 Codex Low (OAuth)",
"options": { "reasoningEffort": "low" }
},
"gpt-5-codex-high": {
"name": "GPT 5 Codex High (OAuth)",
"options": { "reasoningEffort": "high" }
}
}
}
}
}
EOF
# Test per-model options
DEBUG_CODEX_PLUGIN=1 opencode run "test low" --model=openai/gpt-5-codex-low
DEBUG_CODEX_PLUGIN=1 opencode run "test high" --model=openai/gpt-5-codex-high
Verify:
- โ
Debug log shows:
hasModelSpecificConfig: truefor both - โ
Different
reasoningEffortvalues in logs - โ TUI shows friendly names
Step 3: Multi-Turn Test (Critical for store:false)
DEBUG_CODEX_PLUGIN=1 opencode --model=openai/gpt-5-codex-medium
> write "test content" to file1.txt
> read file1.txt
> what did you just write?
> create file2.txt with different content
> compare the two files
Verify:
- โ No "item not found" errors on ANY turn
- โ Debug shows IDs removed on turns 2+
- โ Context is maintained across turns
- โ All tool calls work correctly
Step 4: Model Switching Test
DEBUG_CODEX_PLUGIN=1 opencode
> /model openai/gpt-5-codex-low
> write hello to test.txt
> /model openai/gpt-5-codex-high
> write goodbye to test2.txt
Verify:
- โ Different reasoning efforts logged for each model
- โ Per-model options applied correctly
- โ No errors when switching
Step 5: TUI Persistence Test
# 1. Start opencode
opencode --model=openai/gpt-5-codex-high
# 2. Run a command
> write test
# 3. Exit (ctrl+c)
# 4. Restart
opencode
# 5. Check which model is selected
> /model
Verify:
- โ
Last used model is
gpt-5-codex-high - โ Model is auto-selected on restart
- โ TUI shows correct model highlighted
Normalization Edge Cases
Test: normalizeModel() Coverage
normalizeModel("gpt-5-codex") // โ "gpt-5-codex" โ
normalizeModel("gpt-5-codex-low") // โ "gpt-5-codex" โ
normalizeModel("GPT-5-CODEX-HIGH") // โ "gpt-5-codex" โ
normalizeModel("my-codex-model") // โ "gpt-5-codex" โ
normalizeModel("gpt-5") // โ "gpt-5" โ
normalizeModel("gpt-5-mini") // โ "gpt-5" โ
normalizeModel("gpt-5-nano") // โ "gpt-5" โ
normalizeModel("GPT 5 High") // โ "gpt-5" โ
normalizeModel(undefined) // โ "gpt-5" โ
normalizeModel("random-model") // โ "gpt-5" โ
(fallback)
Implementation:
export function normalizeModel(model: string | undefined): string {
if (!model) return "gpt-5";
if (model.includes("codex")) return "gpt-5-codex"; // Check codex first
if (model.includes("gpt-5")) return "gpt-5"; // Then gpt-5
return "gpt-5"; // Safe fallback
}
Why this works:
- โ
Case-insensitive (
.includes()works with any case) - โ Pattern-based (works with any naming)
- โ
Safe fallback (unknown models โ
gpt-5) - โ Codex priority (checks "codex" before "gpt-5")
Expected Failures (These Should Error)
Invalid Model Selection
opencode run "test" --model=openai/claude-3.5
Expected: โ Error before plugin (OpenCode rejects unknown model)
Missing Authentication
# Without running: opencode auth login
opencode run "test" --model=openai/gpt-5-codex
Expected: โ 401 Unauthorized error
Success Criteria
All Tests Must Pass
- Default models work without custom config
- Custom config variants use per-model options
- Old config format still works (backwards compat)
- Mixed default + custom models work
- Multi-turn conversations have no ID errors
- Model switching works correctly
- TUI persistence remembers last used model
- Debug logging shows correct information
- All normalization edge cases work
No Errors
- No "item not found" errors
- No TypeScript errors
- No authentication errors (after login)
- No config validation errors
Automated Test Suggestions
Unit Tests (Future)
describe('normalizeModel', () => {
test('handles all default models', () => {
expect(normalizeModel('gpt-5')).toBe('gpt-5')
expect(normalizeModel('gpt-5-codex')).toBe('gpt-5-codex')
expect(normalizeModel('gpt-5-mini')).toBe('gpt-5')
expect(normalizeModel('gpt-5-nano')).toBe('gpt-5')
})
test('handles custom preset names', () => {
expect(normalizeModel('gpt-5-codex-low')).toBe('gpt-5-codex')
expect(normalizeModel('gpt-5-high')).toBe('gpt-5')
})
test('handles legacy names', () => {
expect(normalizeModel('GPT 5 Codex Low (ChatGPT Subscription)')).toBe('gpt-5-codex')
})
test('handles edge cases', () => {
expect(normalizeModel(undefined)).toBe('gpt-5')
expect(normalizeModel('random')).toBe('gpt-5')
})
})
describe('getModelConfig', () => {
test('returns per-model options when found', () => {
const config = getModelConfig('gpt-5-codex-low', {
global: { reasoningEffort: 'medium' },
models: {
'gpt-5-codex-low': {
options: { reasoningEffort: 'low' }
}
}
})
expect(config.reasoningEffort).toBe('low')
})
test('returns global options when model not in config', () => {
const config = getModelConfig('gpt-5-codex', {
global: { reasoningEffort: 'medium' },
models: {}
})
expect(config.reasoningEffort).toBe('medium')
})
})
describe('filterInput', () => {
test('removes all message IDs', () => {
const input = [
{ id: 'msg_123', role: 'user', content: [] },
{ id: 'rs_456', role: 'assistant', content: [] },
{ role: 'user', content: [] } // No ID
]
const result = filterInput(input)
expect(result.every(item => !item.id)).toBe(true)
})
})
See Also
- IMPLEMENTATION_SUMMARY.md - Complete summary
- CONFIG_FIELDS.md - Field usage guide
- BUGS_FIXED.md - Bug analysis