mirror of
https://github.com/hanzoai/opencode-openai-codex-auth.git
synced 2026-09-24 17:01:02 +00:00
This PR standardizes all documentation and configuration on GPT 5.1 model identifiers, removes deprecated GPT 5.0 models, and enforces full-opencode.json as the only officially supported configuration. The changes address GPT 5 model temperamental behavior and ensure users have the proper setup for reliable operation with OpenCode. Co-authored-by: Hanzo Dev <[email protected]>
751 lines
18 KiB
Markdown
751 lines
18 KiB
Markdown
# 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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):**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```typescript
|
|
normalizeModel("gpt-5-codex") // → "gpt-5-codex" ✅
|
|
normalizeModel("gpt-5-codex-low") // → "gpt-5-codex" ✅
|
|
normalizeModel("GPT-5-CODEX-HIGH") // → "gpt-5-codex" ✅
|
|
normalizeModel("gpt-5-codex-mini-high")// → "codex-mini-latest" ✅
|
|
normalizeModel("codex-mini-latest") // → "codex-mini-latest" ✅
|
|
normalizeModel("gpt-5.1-codex-mini") // → "gpt-5.1-codex-mini" ✅
|
|
normalizeModel("gpt-5.1-codex") // → "gpt-5.1-codex" ✅
|
|
normalizeModel("gpt-5.1") // → "gpt-5.1" ✅
|
|
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:**
|
|
```typescript
|
|
export function normalizeModel(model: string | undefined): string {
|
|
if (!model) return "gpt-5";
|
|
const normalized = model.toLowerCase();
|
|
|
|
if (normalized.includes("gpt-5.1-codex-mini") || normalized.includes("gpt 5.1 codex mini")) {
|
|
return "gpt-5.1-codex-mini";
|
|
}
|
|
if (
|
|
normalized.includes("codex-mini-latest") ||
|
|
normalized.includes("gpt-5-codex-mini") ||
|
|
normalized.includes("gpt 5 codex mini")
|
|
) {
|
|
return "codex-mini-latest";
|
|
}
|
|
if (normalized.includes("gpt-5.1-codex") || normalized.includes("gpt 5.1 codex")) {
|
|
return "gpt-5.1-codex";
|
|
}
|
|
if (normalized.includes("gpt-5.1") || normalized.includes("gpt 5.1")) {
|
|
return "gpt-5.1";
|
|
}
|
|
if (normalized.includes("codex")) {
|
|
return "gpt-5-codex";
|
|
}
|
|
if (normalized.includes("gpt-5") || normalized.includes("gpt 5")) {
|
|
return "gpt-5";
|
|
}
|
|
return "gpt-5";
|
|
}
|
|
```
|
|
|
|
**Why this works:**
|
|
- ✅ Case-insensitive (`.toLowerCase()` + `.includes()`)
|
|
- ✅ Pattern-based (works with any naming)
|
|
- ✅ Safe fallback (unknown models → `gpt-5`)
|
|
- ✅ Codex priority with explicit Codex Mini support (`codex-mini*` → `codex-mini-latest`)
|
|
|
|
---
|
|
|
|
## Expected Failures (These Should Error)
|
|
|
|
### Invalid Model Selection
|
|
|
|
```bash
|
|
opencode run "test" --model=openai/claude-3.5
|
|
```
|
|
|
|
**Expected:** ❌ Error before plugin (OpenCode rejects unknown model)
|
|
|
|
### Missing Authentication
|
|
|
|
```bash
|
|
# 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)
|
|
|
|
```typescript
|
|
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-codex-mini')).toBe('codex-mini-latest')
|
|
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('openai/gpt-5-codex-mini-high')).toBe('codex-mini-latest')
|
|
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('codex-mini-latest')).toBe('codex-mini-latest')
|
|
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](./IMPLEMENTATION_SUMMARY.md) - Complete summary
|
|
- [CONFIG_FIELDS.md](./CONFIG_FIELDS.md) - Field usage guide
|
|
- [BUGS_FIXED.md](./BUGS_FIXED.md) - Bug analysis
|