Files
opencode-openai-codex-auth/docs/development/TESTING.md
T
Numman AliandHanzo Dev fe29b2110d feat: Add comprehensive compliance documentation and fix critical bugs (v2.1.2)
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]>
2025-10-12 21:25:43 +01:00

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-5
  • gpt-5-codex
  • gpt-5-mini
  • gpt-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-codex uses 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


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: true for both
  • โœ… Different reasoningEffort values 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