chore: update GPT-5.2 references to GPT-5.4
Align runtime defaults, tests, docs, and generated artifacts with the newer GPT-5.4 baseline. Keep think-mode and prompt-routing expectations consistent after the model version bump.
This commit is contained in:
@@ -65,7 +65,7 @@ These agents have Claude-optimized prompts — long, detailed, mechanics-driven.
|
||||
| Agent | Role | Fallback Chain | Notes |
|
||||
| ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| **Sisyphus** | Main orchestrator | Claude Opus → GLM 5 → Big Pickle | Claude-family first. GPT-5.4 has dedicated support, but Claude/Kimi/GLM remain the preferred fit. |
|
||||
| **Metis** | Plan gap analyzer | Claude Opus → GPT-5.2 → Gemini 3.1 Pro | Claude preferred, GPT acceptable fallback. |
|
||||
| **Metis** | Plan gap analyzer | Claude Opus → GPT-5.4 → Gemini 3.1 Pro | Claude preferred, GPT acceptable fallback. |
|
||||
|
||||
### Dual-Prompt Agents → Claude preferred, GPT supported
|
||||
|
||||
@@ -83,7 +83,7 @@ These agents are built for GPT's principle-driven style. Their prompts assume au
|
||||
| Agent | Role | Fallback Chain | Notes |
|
||||
| -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ |
|
||||
| **Hephaestus** | Autonomous deep worker | GPT-5.3 Codex only | No fallback. Requires GPT access. The craftsman. |
|
||||
| **Oracle** | Architecture consultant | GPT-5.2 → Gemini 3.1 Pro → Claude Opus | Read-only high-IQ consultation. |
|
||||
| **Oracle** | Architecture consultant | GPT-5.4 → Gemini 3.1 Pro → Claude Opus | Read-only high-IQ consultation. |
|
||||
| **Momus** | Ruthless reviewer | GPT-5.4 → Claude Opus → Gemini 3.1 Pro | Verification and plan review. |
|
||||
|
||||
### Utility Runners → Speed over Intelligence
|
||||
@@ -119,7 +119,7 @@ Principle-driven, explicit reasoning, deep technical capability. Best for agents
|
||||
| Model | Strengths |
|
||||
| ----------------- | ----------------------------------------------------------------------------------------------- |
|
||||
| **GPT-5.3 Codex** | Deep coding powerhouse. Autonomous exploration. Required for Hephaestus. |
|
||||
| **GPT-5.2** | High intelligence, strategic reasoning. Default for Oracle. |
|
||||
| **GPT-5.4** | High intelligence, strategic reasoning. Default for Oracle. |
|
||||
| **GPT-5.4** | Strong principle-driven reasoning. Default for Momus and a key fallback for Prometheus / Atlas. |
|
||||
| **GPT-5-Nano** | Ultra-cheap, fast. Good for simple utility tasks. |
|
||||
|
||||
@@ -149,7 +149,7 @@ When agents delegate work, they don't pick a model name — they pick a **catego
|
||||
| `visual-engineering` | Frontend, UI, CSS, design | Gemini 3.1 Pro → GLM 5 → Claude Opus |
|
||||
| `ultrabrain` | Maximum reasoning needed | GPT-5.3 Codex → Gemini 3.1 Pro → Claude Opus |
|
||||
| `deep` | Deep coding, complex logic | GPT-5.3 Codex → Claude Opus → Gemini 3.1 Pro |
|
||||
| `artistry` | Creative, novel approaches | Gemini 3.1 Pro → Claude Opus → GPT-5.2 |
|
||||
| `artistry` | Creative, novel approaches | Gemini 3.1 Pro → Claude Opus → GPT-5.4 |
|
||||
| `quick` | Simple, fast tasks | Claude Haiku → Gemini Flash → GPT-5-Nano |
|
||||
| `unspecified-high` | General complex work | GPT-5.4 → Claude Opus → GLM 5 → K2P5 |
|
||||
| `unspecified-low` | General standard work | Claude Sonnet → GPT-5.3 Codex → Gemini Flash |
|
||||
@@ -179,7 +179,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch
|
||||
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||
|
||||
// Architecture consultation: GPT or Claude Opus
|
||||
"oracle": { "model": "openai/gpt-5.2", "variant": "high" },
|
||||
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
|
||||
|
||||
// Prometheus inherits sisyphus model; just add prompt guidance
|
||||
"prometheus": {
|
||||
|
||||
@@ -49,7 +49,7 @@ Ask the user these questions to determine CLI options:
|
||||
- If **no** → `--claude=no`
|
||||
|
||||
2. **Do you have an OpenAI/ChatGPT Plus Subscription?**
|
||||
- If **yes** → `--openai=yes` (GPT-5.2 for Oracle agent)
|
||||
- If **yes** → `--openai=yes` (GPT-5.4 for Oracle agent)
|
||||
- If **no** → `--openai=no` (default)
|
||||
|
||||
3. **Will you integrate Gemini models?**
|
||||
@@ -200,7 +200,7 @@ When GitHub Copilot is the best available provider, oh-my-opencode uses these mo
|
||||
| Agent | Model |
|
||||
| ------------- | --------------------------------- |
|
||||
| **Sisyphus** | `github-copilot/claude-opus-4-6` |
|
||||
| **Oracle** | `github-copilot/gpt-5.2` |
|
||||
| **Oracle** | `github-copilot/gpt-5.4` |
|
||||
| **Explore** | `github-copilot/grok-code-fast-1` |
|
||||
| **Librarian** | `github-copilot/gemini-3-flash` |
|
||||
|
||||
@@ -228,7 +228,7 @@ When OpenCode Zen is the best available provider (no native or Copilot), these m
|
||||
| Agent | Model |
|
||||
| ------------- | ---------------------------------------------------- |
|
||||
| **Sisyphus** | `opencode/claude-opus-4-6` |
|
||||
| **Oracle** | `opencode/gpt-5.2` |
|
||||
| **Oracle** | `opencode/gpt-5.4` |
|
||||
| **Explore** | `opencode/gpt-5-nano` |
|
||||
| **Librarian** | `opencode/minimax-m2.5-free` / `opencode/big-pickle` |
|
||||
|
||||
@@ -280,7 +280,7 @@ Not all models behave the same way. Understanding which models are "similar" hel
|
||||
| Model | Provider(s) | Notes |
|
||||
| ----------------- | -------------------------------- | ------------------------------------------------- |
|
||||
| **GPT-5.3-codex** | openai, github-copilot, opencode | Deep coding powerhouse. Required for Hephaestus. |
|
||||
| **GPT-5.2** | openai, github-copilot, opencode | High intelligence. Default for Oracle. |
|
||||
| **GPT-5.4** | openai, github-copilot, opencode | High intelligence. Default for Oracle. |
|
||||
| **GPT-5-Nano** | opencode | Ultra-cheap, fast. Good for simple utility tasks. |
|
||||
|
||||
**Different-Behavior Models**:
|
||||
@@ -310,7 +310,7 @@ Based on your subscriptions, here's how the agents were configured:
|
||||
| Agent | Role | Default Chain | What It Does |
|
||||
| ------------ | ---------------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
| **Sisyphus** | Main ultraworker | Opus (max) → Kimi K2.5 → GLM 5 → Big Pickle | Primary coding agent. Orchestrates everything. **Never use GPT — no GPT prompt exists.** |
|
||||
| **Metis** | Plan review | Opus (max) → Kimi K2.5 → GPT-5.2 → Gemini 3 Pro | Reviews Prometheus plans for gaps. |
|
||||
| **Metis** | Plan review | Opus (max) → Kimi K2.5 → GPT-5.4 → Gemini 3 Pro | Reviews Prometheus plans for gaps. |
|
||||
|
||||
**Dual-Prompt Agents** (auto-switch between Claude and GPT prompts):
|
||||
|
||||
@@ -320,16 +320,16 @@ Priority: **Claude > GPT > Claude-like models**
|
||||
|
||||
| Agent | Role | Default Chain | GPT Prompt? |
|
||||
| -------------- | ----------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| **Prometheus** | Strategic planner | Opus (max) → **GPT-5.2 (high)** → Kimi K2.5 → Gemini 3 Pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
|
||||
| **Atlas** | Todo orchestrator | **Kimi K2.5** → Sonnet → GPT-5.2 | Yes — GPT-optimized todo management |
|
||||
| **Prometheus** | Strategic planner | Opus (max) → **GPT-5.4 (high)** → Kimi K2.5 → Gemini 3 Pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
|
||||
| **Atlas** | Todo orchestrator | **Kimi K2.5** → Sonnet → GPT-5.4 | Yes — GPT-optimized todo management |
|
||||
|
||||
**GPT-Native Agents** (built for GPT, don't override to Claude):
|
||||
|
||||
| Agent | Role | Default Chain | Notes |
|
||||
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
|
||||
| **Hephaestus** | Deep autonomous worker | GPT-5.3-codex (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
|
||||
| **Oracle** | Architecture/debugging | GPT-5.2 (high) → Gemini 3 Pro → Opus | High-IQ strategic backup. GPT preferred. |
|
||||
| **Momus** | High-accuracy reviewer | GPT-5.2 (medium) → Opus → Gemini 3 Pro | Verification agent. GPT preferred. |
|
||||
| **Oracle** | Architecture/debugging | GPT-5.4 (high) → Gemini 3 Pro → Opus | High-IQ strategic backup. GPT preferred. |
|
||||
| **Momus** | High-accuracy reviewer | GPT-5.4 (medium) → Opus → Gemini 3 Pro | Verification agent. GPT preferred. |
|
||||
|
||||
**Utility Agents** (speed over intelligence):
|
||||
|
||||
@@ -339,7 +339,7 @@ These agents do search, grep, and retrieval. They intentionally use fast, cheap
|
||||
| --------------------- | ------------------ | ---------------------------------------------------------------------- | -------------------------------------------------------------- |
|
||||
| **Explore** | Fast codebase grep | MiniMax M2.5 Free → Grok Code Fast → MiniMax M2.5 → Haiku → GPT-5-Nano | Speed is everything. Grok is blazing fast for grep. |
|
||||
| **Librarian** | Docs/code search | MiniMax M2.5 Free → Gemini Flash → Big Pickle | Entirely free-tier. Doc retrieval doesn't need deep reasoning. |
|
||||
| **Multimodal Looker** | Vision/screenshots | Kimi K2.5 → Kimi Free → Gemini Flash → GPT-5.2 → GLM-4.6v | Kimi excels at multimodal understanding. |
|
||||
| **Multimodal Looker** | Vision/screenshots | Kimi K2.5 → Kimi Free → Gemini Flash → GPT-5.4 → GLM-4.6v | Kimi excels at multimodal understanding. |
|
||||
|
||||
#### Why Different Models Need Different Prompts
|
||||
|
||||
@@ -388,8 +388,8 @@ GPT (5.3-codex, 5.2) > Claude Opus (decent fallback) > Gemini (acceptable)
|
||||
**Safe** (same family):
|
||||
|
||||
- Sisyphus: Opus → Sonnet, Kimi K2.5, GLM 5
|
||||
- Prometheus: Opus → GPT-5.2 (auto-switches prompt)
|
||||
- Atlas: Kimi K2.5 → Sonnet, GPT-5.2 (auto-switches)
|
||||
- Prometheus: Opus → GPT-5.4 (auto-switches prompt)
|
||||
- Atlas: Kimi K2.5 → Sonnet, GPT-5.4 (auto-switches)
|
||||
|
||||
**Dangerous** (no prompt support):
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ flowchart TB
|
||||
|
||||
subgraph Workers["Worker Layer (Specialized Agents)"]
|
||||
Junior[" Sisyphus-Junior<br/>(Task Executor)<br/>Claude Sonnet 4.6"]
|
||||
Oracle[" Oracle<br/>(Architecture)<br/>GPT-5.2"]
|
||||
Oracle[" Oracle<br/>(Architecture)<br/>GPT-5.4"]
|
||||
Explore[" Explore<br/>(Codebase Grep)<br/>Grok Code"]
|
||||
Librarian[" Librarian<br/>(Docs/OSS)<br/>Gemini 3 Flash"]
|
||||
Frontend[" Frontend<br/>(UI/UX)<br/>Gemini 3.1 Pro"]
|
||||
|
||||
@@ -182,7 +182,7 @@ You can override specific agents or categories in your config:
|
||||
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||
|
||||
// Architecture consultation: GPT or Claude Opus
|
||||
"oracle": { "model": "openai/gpt-5.2", "variant": "high" },
|
||||
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
|
||||
},
|
||||
|
||||
"categories": {
|
||||
@@ -215,7 +215,7 @@ You can override specific agents or categories in your config:
|
||||
**GPT models** (explicit reasoning, principle-driven):
|
||||
|
||||
- GPT-5.3-codex — deep coding powerhouse, required for Hephaestus
|
||||
- GPT-5.2 — high intelligence, default for Oracle
|
||||
- GPT-5.4 — high intelligence, default for Oracle
|
||||
- GPT-5-Nano — ultra-cheap, fast utility tasks
|
||||
|
||||
**Different-behavior models**:
|
||||
|
||||
@@ -83,8 +83,8 @@ Here's a practical starting configuration:
|
||||
"librarian": { "model": "google/gemini-3-flash" },
|
||||
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||
|
||||
// Architecture consultation: GPT-5.2 or Claude Opus
|
||||
"oracle": { "model": "openai/gpt-5.2", "variant": "high" },
|
||||
// Architecture consultation: GPT-5.4 or Claude Opus
|
||||
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
|
||||
|
||||
// Prometheus inherits sisyphus model; just add prompt guidance
|
||||
"prometheus": {
|
||||
@@ -268,13 +268,13 @@ Disable categories: `{ "disabled_categories": ["ultrabrain"] }`
|
||||
| Agent | Default Model | Provider Priority |
|
||||
| --------------------- | ------------------- | ---------------------------------------------------------------------------- |
|
||||
| **Sisyphus** | `claude-opus-4-6` | `claude-opus-4-6` → `glm-5` → `big-pickle` |
|
||||
| **Hephaestus** | `gpt-5.3-codex` | `gpt-5.3-codex` → `gpt-5.2` (GitHub Copilot fallback) |
|
||||
| **oracle** | `gpt-5.2` | `gpt-5.2` → `gemini-3.1-pro` → `claude-opus-4-6` |
|
||||
| **Hephaestus** | `gpt-5.3-codex` | `gpt-5.3-codex` → `gpt-5.4` (GitHub Copilot fallback) |
|
||||
| **oracle** | `gpt-5.4` | `gpt-5.4` → `gemini-3.1-pro` → `claude-opus-4-6` |
|
||||
| **librarian** | `gemini-3-flash` | `gemini-3-flash` → `minimax-m2.5-free` → `big-pickle` |
|
||||
| **explore** | `grok-code-fast-1` | `grok-code-fast-1` → `minimax-m2.5-free` → `claude-haiku-4-5` → `gpt-5-nano` |
|
||||
| **multimodal-looker** | `gpt-5.3-codex` | `gpt-5.3-codex` → `k2p5` → `gemini-3-flash` → `glm-4.6v` → `gpt-5-nano` |
|
||||
| **Prometheus** | `claude-opus-4-6` | `claude-opus-4-6` → `gpt-5.4` → `gemini-3.1-pro` |
|
||||
| **Metis** | `claude-opus-4-6` | `claude-opus-4-6` → `gpt-5.2` → `gemini-3.1-pro` |
|
||||
| **Metis** | `claude-opus-4-6` | `claude-opus-4-6` → `gpt-5.4` → `gemini-3.1-pro` |
|
||||
| **Momus** | `gpt-5.4` | `gpt-5.4` → `claude-opus-4-6` → `gemini-3.1-pro` |
|
||||
| **Atlas** | `claude-sonnet-4-6` | `claude-sonnet-4-6` → `gpt-5.4` |
|
||||
|
||||
@@ -285,7 +285,7 @@ Disable categories: `{ "disabled_categories": ["ultrabrain"] }`
|
||||
| **visual-engineering** | `gemini-3.1-pro` | `gemini-3.1-pro` → `glm-5` → `claude-opus-4-6` |
|
||||
| **ultrabrain** | `gpt-5.3-codex` | `gpt-5.3-codex` → `gemini-3.1-pro` → `claude-opus-4-6` |
|
||||
| **deep** | `gpt-5.3-codex` | `gpt-5.3-codex` → `claude-opus-4-6` → `gemini-3.1-pro` |
|
||||
| **artistry** | `gemini-3.1-pro` | `gemini-3.1-pro` → `claude-opus-4-6` → `gpt-5.2` |
|
||||
| **artistry** | `gemini-3.1-pro` | `gemini-3.1-pro` → `claude-opus-4-6` → `gpt-5.4` |
|
||||
| **quick** | `claude-haiku-4-5` | `claude-haiku-4-5` → `gemini-3-flash` → `gpt-5-nano` |
|
||||
| **unspecified-low** | `claude-sonnet-4-6` | `claude-sonnet-4-6` → `gpt-5.3-codex` → `gemini-3-flash` |
|
||||
| **unspecified-high** | `gpt-5.4` | `gpt-5.4` → `claude-opus-4-6` → `glm-5` → `k2p5` → `kimi-k2.5` |
|
||||
|
||||
@@ -9,8 +9,8 @@ Oh-My-OpenCode provides 11 specialized AI agents. Each has distinct expertise, o
|
||||
| Agent | Model | Purpose |
|
||||
| --------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Sisyphus** | `claude-opus-4-6` | The default orchestrator. Plans, delegates, and executes complex tasks using specialized subagents with aggressive parallel execution. Todo-driven workflow with extended thinking (32k budget). Fallback: `glm-5` → `big-pickle`. |
|
||||
| **Hephaestus** | `gpt-5.3-codex` | The Legitimate Craftsman. Autonomous deep worker inspired by AmpCode's deep mode. Goal-oriented execution with thorough research before action. Explores codebase patterns, completes tasks end-to-end without premature stopping. Named after the Greek god of forge and craftsmanship. Fallback: `gpt-5.2` on GitHub Copilot. Requires a GPT-capable provider. |
|
||||
| **Oracle** | `gpt-5.2` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `gemini-3.1-pro` → `claude-opus-4-6`. |
|
||||
| **Hephaestus** | `gpt-5.3-codex` | The Legitimate Craftsman. Autonomous deep worker inspired by AmpCode's deep mode. Goal-oriented execution with thorough research before action. Explores codebase patterns, completes tasks end-to-end without premature stopping. Named after the Greek god of forge and craftsmanship. Fallback: `gpt-5.4` on GitHub Copilot. Requires a GPT-capable provider. |
|
||||
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `gemini-3.1-pro` → `claude-opus-4-6`. |
|
||||
| **Librarian** | `gemini-3-flash` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `minimax-m2.5-free` → `big-pickle`. |
|
||||
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `minimax-m2.5-free` → `claude-haiku-4-5` → `gpt-5-nano`. |
|
||||
| **Multimodal-Looker** | `gpt-5.3-codex` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `k2p5` → `gemini-3-flash` → `glm-4.6v` → `gpt-5-nano`. |
|
||||
@@ -20,7 +20,7 @@ Oh-My-OpenCode provides 11 specialized AI agents. Each has distinct expertise, o
|
||||
| Agent | Model | Purpose |
|
||||
| -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `gpt-5.4` → `gemini-3.1-pro`. |
|
||||
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `gpt-5.2` → `gemini-3.1-pro`. |
|
||||
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `gpt-5.4` → `gemini-3.1-pro`. |
|
||||
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `claude-opus-4-6` → `gemini-3.1-pro`. |
|
||||
|
||||
### Orchestration Agents
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,12 +11,12 @@ Agent factories following `createXXXAgent(model) → AgentConfig` pattern. Each
|
||||
| Agent | Model | Temp | Mode | Fallback Chain | Purpose |
|
||||
|-------|-------|------|------|----------------|---------|
|
||||
| **Sisyphus** | claude-opus-4-6 max | 0.1 | all | glm-5 → big-pickle | Main orchestrator, plans + delegates |
|
||||
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.2 medium (copilot) | Autonomous deep worker |
|
||||
| **Oracle** | gpt-5.2 high | 0.1 | subagent | gemini-3.1-pro high → claude-opus-4-6 max | Read-only consultation |
|
||||
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.4 medium (copilot) | Autonomous deep worker |
|
||||
| **Oracle** | gpt-5.4 high | 0.1 | subagent | gemini-3.1-pro high → claude-opus-4-6 max | Read-only consultation |
|
||||
| **Librarian** | gemini-3-flash | 0.1 | subagent | minimax-m2.5-free → big-pickle | External docs/code search |
|
||||
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.5-free → claude-haiku-4-5 → gpt-5-nano | Contextual grep |
|
||||
| **Multimodal-Looker** | gpt-5.3-codex medium | 0.1 | subagent | k2p5 → gemini-3-flash → glm-4.6v → gpt-5-nano | PDF/image analysis |
|
||||
| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.2 high → gemini-3.1-pro high | Pre-planning consultant |
|
||||
| **Metis** | claude-opus-4-6 max | **0.3** | subagent | gpt-5.4 high → gemini-3.1-pro high | Pre-planning consultant |
|
||||
| **Momus** | gpt-5.4 xhigh | 0.1 | subagent | claude-opus-4-6 max → gemini-3.1-pro high | Plan reviewer |
|
||||
| **Atlas** | claude-sonnet-4-6 | 0.1 | primary | gpt-5.4 medium | Todo-list orchestrator |
|
||||
| **Prometheus** | claude-opus-4-6 max | 0.1 | — | gpt-5.4 high → gemini-3.1-pro | Strategic planner (internal) |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* You are the conductor of a symphony of specialized agents.
|
||||
*
|
||||
* Routing:
|
||||
* 1. GPT models (openai/*, github-copilot/gpt-*) → gpt.ts (GPT-5.2 optimized)
|
||||
* 1. GPT models (openai/*, github-copilot/gpt-*) → gpt.ts (GPT-5.4 optimized)
|
||||
* 2. Gemini models (google/*, google-vertex/*) → gemini.ts (Gemini-optimized)
|
||||
* 3. Default (Claude, etc.) → default.ts (Claude-optimized)
|
||||
*/
|
||||
|
||||
@@ -197,7 +197,7 @@ describe("buildParallelDelegationSection", () => {
|
||||
|
||||
it("#given non-Claude model with unspecified-high category #when building #then returns aggressive delegation section", () => {
|
||||
//#given
|
||||
const model = "openai/gpt-5.2"
|
||||
const model = "openai/gpt-5.4"
|
||||
const categories = [unspecifiedHighCategory, otherCategory]
|
||||
|
||||
//#when
|
||||
@@ -223,7 +223,7 @@ describe("buildParallelDelegationSection", () => {
|
||||
|
||||
it("#given non-Claude model without deep or unspecified-high category #when building #then returns empty", () => {
|
||||
//#given
|
||||
const model = "openai/gpt-5.2"
|
||||
const model = "openai/gpt-5.4"
|
||||
const categories = [otherCategory]
|
||||
|
||||
//#when
|
||||
@@ -261,7 +261,7 @@ describe("buildNonClaudePlannerSection", () => {
|
||||
|
||||
it("#given GPT model #when building #then returns plan agent section", () => {
|
||||
//#given
|
||||
const model = "openai/gpt-5.2"
|
||||
const model = "openai/gpt-5.4"
|
||||
|
||||
//#when
|
||||
const result = buildNonClaudePlannerSection(model)
|
||||
|
||||
@@ -39,8 +39,8 @@ describe("getHephaestusPromptSource", () => {
|
||||
|
||||
test("returns 'gpt' for generic GPT models", () => {
|
||||
// given
|
||||
const model1 = "openai/gpt-5.2";
|
||||
const model2 = "github-copilot/gpt-5.2";
|
||||
const model1 = "openai/gpt-4o";
|
||||
const model2 = "github-copilot/gpt-4o";
|
||||
const model3 = "openai/gpt-4o";
|
||||
|
||||
// when
|
||||
@@ -111,7 +111,7 @@ describe("getHephaestusPrompt", () => {
|
||||
|
||||
test("generic GPT model returns generic GPT prompt", () => {
|
||||
// given
|
||||
const model = "openai/gpt-5.2";
|
||||
const model = "openai/gpt-4o";
|
||||
|
||||
// when
|
||||
const prompt = getHephaestusPrompt(model);
|
||||
|
||||
@@ -522,7 +522,7 @@ export function createHephaestusAgent(
|
||||
|
||||
return {
|
||||
description:
|
||||
"Autonomous Deep Worker - goal-oriented execution with GPT 5.2 Codex. Explores thoroughly before acting, uses explore/librarian agents for comprehensive context, completes tasks end-to-end. Inspired by AmpCode deep mode. (Hephaestus - OhMyOpenCode)",
|
||||
"Autonomous Deep Worker - goal-oriented execution with GPT 5.4 Codex. Explores thoroughly before acting, uses explore/librarian agents for comprehensive context, completes tasks end-to-end. Inspired by AmpCode deep mode. (Hephaestus - OhMyOpenCode)",
|
||||
mode: MODE,
|
||||
model,
|
||||
maxTokens: 32000,
|
||||
|
||||
@@ -48,7 +48,7 @@ export function getPrometheusPromptSource(model?: string): PrometheusPromptSourc
|
||||
|
||||
/**
|
||||
* Gets the appropriate Prometheus prompt based on model.
|
||||
* GPT models → GPT-5.2 optimized prompt (XML-tagged, principle-driven)
|
||||
* GPT models → GPT-5.4 optimized prompt (XML-tagged, principle-driven)
|
||||
* Gemini models → Gemini-optimized prompt (aggressive tool-call enforcement, thinking checkpoints)
|
||||
* Default (Claude, etc.) → Claude-optimized prompt (modular sections)
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* Category-spawned executor with domain-specific configurations.
|
||||
*
|
||||
* Routing:
|
||||
* 1. GPT models (openai/*, github-copilot/gpt-*) -> gpt.ts (GPT-5.2 optimized)
|
||||
* 1. GPT models (openai/*, github-copilot/gpt-*) -> gpt.ts (GPT-5.4 optimized)
|
||||
* 2. Gemini models (google/*, google-vertex/*) -> gemini.ts (Gemini-optimized)
|
||||
* 3. Default (Claude, etc.) -> default.ts (Claude-optimized)
|
||||
*/
|
||||
|
||||
@@ -10,13 +10,13 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
||||
describe("honored fields", () => {
|
||||
test("applies model override", () => {
|
||||
// given
|
||||
const override = { model: "openai/gpt-5.2" }
|
||||
const override = { model: "openai/gpt-5.4" }
|
||||
|
||||
// when
|
||||
const result = createSisyphusJuniorAgentWithOverrides(override)
|
||||
|
||||
// then
|
||||
expect(result.model).toBe("openai/gpt-5.2")
|
||||
expect(result.model).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("applies temperature override", () => {
|
||||
@@ -105,7 +105,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
||||
// given
|
||||
const override = {
|
||||
disable: true,
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.9,
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
||||
|
||||
test("useTaskSystem=true produces Task Discipline prompt for GPT", () => {
|
||||
//#given
|
||||
const override = { model: "openai/gpt-5.2" }
|
||||
const override = { model: "openai/gpt-5.4" }
|
||||
|
||||
//#when
|
||||
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
||||
@@ -253,7 +253,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
||||
|
||||
test("useTaskSystem=true includes task_create/task_update in GPT prompt", () => {
|
||||
//#given
|
||||
const override = { model: "openai/gpt-5.2" }
|
||||
const override = { model: "openai/gpt-5.4" }
|
||||
|
||||
//#when
|
||||
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
||||
@@ -303,7 +303,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
||||
|
||||
test("GPT model uses GPT-optimized prompt with Hephaestus-style sections", () => {
|
||||
// given
|
||||
const override = { model: "openai/gpt-5.2" }
|
||||
const override = { model: "openai/gpt-5.4" }
|
||||
|
||||
// when
|
||||
const result = createSisyphusJuniorAgentWithOverrides(override)
|
||||
@@ -401,7 +401,7 @@ describe("getSisyphusJuniorPromptSource", () => {
|
||||
|
||||
test("returns 'gpt' for generic GPT models", () => {
|
||||
// given
|
||||
const model = "openai/gpt-5.2"
|
||||
const model = "openai/gpt-4o"
|
||||
|
||||
// when
|
||||
const source = getSisyphusJuniorPromptSource(model)
|
||||
@@ -473,7 +473,7 @@ describe("buildSisyphusJuniorPrompt", () => {
|
||||
|
||||
test("generic GPT model uses generic GPT prompt", () => {
|
||||
// given
|
||||
const model = "openai/gpt-5.2"
|
||||
const model = "openai/gpt-5.4"
|
||||
|
||||
// when
|
||||
const prompt = buildSisyphusJuniorPrompt(model, false)
|
||||
|
||||
@@ -12,9 +12,9 @@ describe("isGpt5_4Model", () => {
|
||||
|
||||
test("does not match other GPT models", () => {
|
||||
expect(isGpt5_4Model("openai/gpt-5.3-codex")).toBe(false);
|
||||
expect(isGpt5_4Model("openai/gpt-5.2")).toBe(false);
|
||||
expect(isGpt5_4Model("openai/gpt-5.1")).toBe(false);
|
||||
expect(isGpt5_4Model("openai/gpt-4o")).toBe(false);
|
||||
expect(isGpt5_4Model("github-copilot/gpt-5.2")).toBe(false);
|
||||
expect(isGpt5_4Model("github-copilot/gpt-4o")).toBe(false);
|
||||
});
|
||||
|
||||
test("does not match non-GPT models", () => {
|
||||
@@ -26,7 +26,7 @@ describe("isGpt5_4Model", () => {
|
||||
|
||||
describe("isGptModel", () => {
|
||||
test("standard openai provider gpt models", () => {
|
||||
expect(isGptModel("openai/gpt-5.2")).toBe(true);
|
||||
expect(isGptModel("openai/gpt-5.4")).toBe(true);
|
||||
expect(isGptModel("openai/gpt-4o")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -39,22 +39,22 @@ describe("isGptModel", () => {
|
||||
});
|
||||
|
||||
test("github copilot gpt models", () => {
|
||||
expect(isGptModel("github-copilot/gpt-5.2")).toBe(true);
|
||||
expect(isGptModel("github-copilot/gpt-5.4")).toBe(true);
|
||||
expect(isGptModel("github-copilot/gpt-4o")).toBe(true);
|
||||
});
|
||||
|
||||
test("litellm proxied gpt models", () => {
|
||||
expect(isGptModel("litellm/gpt-5.2")).toBe(true);
|
||||
expect(isGptModel("litellm/gpt-5.4")).toBe(true);
|
||||
expect(isGptModel("litellm/gpt-4o")).toBe(true);
|
||||
});
|
||||
|
||||
test("other proxied gpt models", () => {
|
||||
expect(isGptModel("ollama/gpt-4o")).toBe(true);
|
||||
expect(isGptModel("custom-provider/gpt-5.2")).toBe(true);
|
||||
expect(isGptModel("custom-provider/gpt-5.4")).toBe(true);
|
||||
});
|
||||
|
||||
test("venice provider gpt models", () => {
|
||||
expect(isGptModel("venice/gpt-5.2")).toBe(true);
|
||||
expect(isGptModel("venice/gpt-5.4")).toBe(true);
|
||||
expect(isGptModel("venice/gpt-4o")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -108,7 +108,7 @@ describe("isGeminiModel", () => {
|
||||
});
|
||||
|
||||
test("#given gpt models #then returns false", () => {
|
||||
expect(isGeminiModel("openai/gpt-5.2")).toBe(false);
|
||||
expect(isGeminiModel("openai/gpt-5.4")).toBe(false);
|
||||
expect(isGeminiModel("openai/o3-mini")).toBe(false);
|
||||
expect(isGeminiModel("litellm/gpt-4o")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -39,14 +39,14 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
|
||||
// #given
|
||||
const overrides = {
|
||||
sisyphus: { model: "github-copilot/gpt-5.2" },
|
||||
sisyphus: { model: "github-copilot/gpt-5.4" },
|
||||
}
|
||||
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
|
||||
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
|
||||
expect(agents.sisyphus.reasoningEffort).toBe("medium")
|
||||
expect(agents.sisyphus.thinking).toBeUndefined()
|
||||
})
|
||||
@@ -54,9 +54,9 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("Atlas uses uiSelectedModel", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
|
||||
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
|
||||
)
|
||||
const uiSelectedModel = "openai/gpt-5.2"
|
||||
const uiSelectedModel = "openai/gpt-5.4"
|
||||
|
||||
try {
|
||||
// #when
|
||||
@@ -75,7 +75,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
|
||||
// #then
|
||||
expect(agents.atlas).toBeDefined()
|
||||
expect(agents.atlas.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.atlas.model).toBe("openai/gpt-5.4")
|
||||
} finally {
|
||||
fetchSpy.mockRestore()
|
||||
}
|
||||
@@ -84,9 +84,9 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("user config model takes priority over uiSelectedModel for sisyphus", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
|
||||
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
|
||||
)
|
||||
const uiSelectedModel = "openai/gpt-5.2"
|
||||
const uiSelectedModel = "openai/gpt-5.4"
|
||||
const overrides = {
|
||||
sisyphus: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
||||
}
|
||||
@@ -117,9 +117,9 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("user config model takes priority over uiSelectedModel for atlas", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.2", "anthropic/claude-sonnet-4-6"])
|
||||
new Set(["openai/gpt-5.4", "anthropic/claude-sonnet-4-6"])
|
||||
)
|
||||
const uiSelectedModel = "openai/gpt-5.2"
|
||||
const uiSelectedModel = "openai/gpt-5.4"
|
||||
const overrides = {
|
||||
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
||||
}
|
||||
@@ -173,8 +173,8 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||
|
||||
// #then - oracle resolves via connected cache fallback to openai/gpt-5.2 (not system default)
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
// #then - oracle resolves via connected cache fallback to openai/gpt-5.4 (not system default)
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.4")
|
||||
expect(agents.oracle.reasoningEffort).toBe("medium")
|
||||
expect(agents.oracle.thinking).toBeUndefined()
|
||||
cacheSpy.mockRestore?.()
|
||||
@@ -196,14 +196,14 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
|
||||
// #given
|
||||
const overrides = {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
}
|
||||
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||
|
||||
// #then
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.4")
|
||||
expect(agents.oracle.reasoningEffort).toBe("medium")
|
||||
expect(agents.oracle.textVerbosity).toBe("high")
|
||||
expect(agents.oracle.thinking).toBeUndefined()
|
||||
@@ -228,14 +228,14 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("non-model overrides are still applied after factory rebuild", async () => {
|
||||
// #given
|
||||
const overrides = {
|
||||
sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
|
||||
sisyphus: { model: "github-copilot/gpt-5.4", temperature: 0.5 },
|
||||
}
|
||||
|
||||
// #when
|
||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||
|
||||
// #then
|
||||
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.2")
|
||||
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
|
||||
expect(agents.sisyphus.temperature).toBe(0.5)
|
||||
})
|
||||
|
||||
@@ -261,7 +261,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
"opencode/kimi-k2.5-free",
|
||||
"zai-coding-plan/glm-5",
|
||||
"opencode/big-pickle",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
])
|
||||
)
|
||||
|
||||
@@ -298,7 +298,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("excludes hidden custom agents from orchestrator prompts", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -334,7 +334,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("excludes disabled custom agents from orchestrator prompts", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -370,7 +370,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("excludes custom agents when disabledAgents contains their name (case-insensitive)", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const disabledAgents = ["ReSeArChEr"]
|
||||
@@ -406,7 +406,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("deduplicates custom agents case-insensitively", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -438,7 +438,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
||||
test("sanitizes custom agent strings for markdown tables", async () => {
|
||||
// #given
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
|
||||
const customAgentSummaries = [
|
||||
@@ -479,7 +479,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
|
||||
|
||||
// #then - connected cache enables model resolution despite no systemDefaultModel
|
||||
expect(agents.oracle).toBeDefined()
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.4")
|
||||
cacheSpy.mockRestore?.()
|
||||
})
|
||||
|
||||
@@ -787,7 +787,7 @@ describe("Atlas is unaffected by environment context toggle", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.2"])
|
||||
new Set(["anthropic/claude-opus-4-6", "openai/gpt-5.4"])
|
||||
)
|
||||
})
|
||||
|
||||
@@ -891,9 +891,9 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
})
|
||||
|
||||
test("sisyphus is not created when no fallback model is available and provider not connected", async () => {
|
||||
// #given - only openai/gpt-5.2 available, not in sisyphus fallback chain
|
||||
// #given - only openai/gpt-5.4 available, not in sisyphus fallback chain
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.2"])
|
||||
new Set(["openai/gpt-5.4"])
|
||||
)
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue([])
|
||||
|
||||
@@ -913,7 +913,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
|
||||
// #given - user configures a model from a plugin provider (like antigravity)
|
||||
// that is NOT in the availableModels cache and NOT in the fallback chain
|
||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||
new Set(["openai/gpt-5.2"])
|
||||
new Set(["openai/gpt-5.4"])
|
||||
)
|
||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(
|
||||
["openai"]
|
||||
@@ -1021,7 +1021,7 @@ describe("buildAgent with category and skills", () => {
|
||||
|
||||
const categories = {
|
||||
"custom-category": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
variant: "xhigh",
|
||||
},
|
||||
}
|
||||
@@ -1030,7 +1030,7 @@ describe("buildAgent with category and skills", () => {
|
||||
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
|
||||
|
||||
// #then
|
||||
expect(agent.model).toBe("openai/gpt-5.2")
|
||||
expect(agent.model).toBe("openai/gpt-5.4")
|
||||
expect(agent.variant).toBe("xhigh")
|
||||
})
|
||||
|
||||
@@ -1247,7 +1247,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
|
||||
// #given - custom category has reasoningEffort=xhigh, direct override says "low"
|
||||
const categories = {
|
||||
"test-cat": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
reasoningEffort: "xhigh" as const,
|
||||
},
|
||||
}
|
||||
@@ -1267,7 +1267,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
|
||||
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
|
||||
const categories = {
|
||||
"reasoning-cat": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
reasoningEffort: "high" as const,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"metis": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"momus": {
|
||||
@@ -217,7 +217,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -274,7 +274,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
|
||||
"model": "opencode/glm-4.7-free",
|
||||
},
|
||||
"metis": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"momus": {
|
||||
@@ -286,7 +286,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -476,7 +476,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -551,7 +551,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -627,7 +627,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "opencode/gpt-5.2",
|
||||
"model": "opencode/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -702,7 +702,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "opencode/gpt-5.2",
|
||||
"model": "opencode/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -773,7 +773,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "github-copilot/gpt-5.2",
|
||||
"model": "github-copilot/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -839,7 +839,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
|
||||
"model": "github-copilot/gemini-3-flash-preview",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "github-copilot/gpt-5.2",
|
||||
"model": "github-copilot/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -1021,7 +1021,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "opencode/gpt-5.2",
|
||||
"model": "opencode/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -1096,7 +1096,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -1298,7 +1298,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "github-copilot/gpt-5.2",
|
||||
"model": "github-copilot/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -1373,7 +1373,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
@@ -1448,7 +1448,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
|
||||
"variant": "medium",
|
||||
},
|
||||
"oracle": {
|
||||
"model": "openai/gpt-5.2",
|
||||
"model": "openai/gpt-5.4",
|
||||
"variant": "high",
|
||||
},
|
||||
"prometheus": {
|
||||
|
||||
@@ -40,7 +40,7 @@ Examples:
|
||||
|
||||
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
|
||||
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
||||
OpenAI Native openai/ models (GPT-5.2 for Oracle)
|
||||
OpenAI Native openai/ models (GPT-5.4 for Oracle)
|
||||
Gemini Native google/ models (Gemini 3 Pro, Flash)
|
||||
Copilot github-copilot/ models (fallback)
|
||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
|
||||
|
||||
@@ -252,7 +252,7 @@ describe("generateOmoConfig - model fallback system", () => {
|
||||
// #then Sisyphus is omitted (requires all fallback providers)
|
||||
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
|
||||
// #then Oracle should use native OpenAI (first fallback entry)
|
||||
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.2")
|
||||
expect((result.agents as Record<string, { model: string }>).oracle.model).toBe("openai/gpt-5.4")
|
||||
// #then multimodal-looker should use native OpenAI (first fallback entry is gpt-5.3-codex)
|
||||
expect((result.agents as Record<string, { model: string }>)["multimodal-looker"].model).toBe("openai/gpt-5.3-codex")
|
||||
})
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("model-resolution check", () => {
|
||||
// given: User has override for visual-engineering category
|
||||
const mockConfig = {
|
||||
categories: {
|
||||
"visual-engineering": { model: "openai/gpt-5.2" },
|
||||
"visual-engineering": { model: "openai/gpt-5.4" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -70,8 +70,8 @@ describe("model-resolution check", () => {
|
||||
// then: visual-engineering should show the override
|
||||
const visual = info.categories.find((c) => c.name === "visual-engineering")
|
||||
expect(visual).toBeDefined()
|
||||
expect(visual!.userOverride).toBe("openai/gpt-5.2")
|
||||
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.2")
|
||||
expect(visual!.userOverride).toBe("openai/gpt-5.4")
|
||||
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.4")
|
||||
})
|
||||
|
||||
it("shows provider fallback when no override exists", async () => {
|
||||
@@ -96,7 +96,7 @@ describe("model-resolution check", () => {
|
||||
//#given User has model with variant override for oracle agent
|
||||
const mockConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2", variant: "xhigh" },
|
||||
oracle: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ describe("model-resolution check", () => {
|
||||
//#then Oracle should have userVariant set
|
||||
const oracle = info.agents.find((a) => a.name === "oracle")
|
||||
expect(oracle).toBeDefined()
|
||||
expect(oracle!.userOverride).toBe("openai/gpt-5.2")
|
||||
expect(oracle!.userOverride).toBe("openai/gpt-5.4")
|
||||
expect(oracle!.userVariant).toBe("xhigh")
|
||||
})
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export function formatConfigSummary(config: InstallConfig): string {
|
||||
|
||||
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
||||
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
||||
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.2 for Oracle"))
|
||||
lines.push(formatProvider("OpenAI/ChatGPT", config.hasOpenAI, "GPT-5.4 for Oracle"))
|
||||
lines.push(formatProvider("Gemini", config.hasGemini))
|
||||
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
||||
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
||||
|
||||
@@ -31,7 +31,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["openai", "github-copilot", "opencode"],
|
||||
model: "gpt-5.2",
|
||||
model: "gpt-5.4",
|
||||
variant: "high",
|
||||
},
|
||||
{
|
||||
@@ -108,7 +108,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||
{
|
||||
providers: ["openai", "github-copilot", "opencode"],
|
||||
model: "gpt-5.2",
|
||||
model: "gpt-5.4",
|
||||
variant: "high",
|
||||
},
|
||||
{
|
||||
@@ -224,7 +224,7 @@ export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> =
|
||||
},
|
||||
{
|
||||
providers: ["openai", "github-copilot", "opencode"],
|
||||
model: "gpt-5.2",
|
||||
model: "gpt-5.4",
|
||||
},
|
||||
],
|
||||
requiresModel: "gemini-3.1-pro",
|
||||
|
||||
@@ -44,7 +44,7 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
|
||||
message: "Do you have an OpenAI/ChatGPT Plus subscription?",
|
||||
options: [
|
||||
{ value: "no", label: "No", hint: "Oracle will use fallback models" },
|
||||
{ value: "yes", label: "Yes", hint: "GPT-5.2 for Oracle (high-IQ debugging)" },
|
||||
{ value: "yes", label: "Yes", hint: "GPT-5.4 for Oracle (high-IQ debugging)" },
|
||||
],
|
||||
initialValue: initial.openai,
|
||||
})
|
||||
@@ -74,7 +74,7 @@ export async function promptInstallConfig(detected: DetectedConfig): Promise<Ins
|
||||
message: "Do you have access to OpenCode Zen (opencode/ models)?",
|
||||
options: [
|
||||
{ value: "no", label: "No", hint: "Will use other configured providers" },
|
||||
{ value: "yes", label: "Yes", hint: "opencode/claude-opus-4-6, opencode/gpt-5.2, etc." },
|
||||
{ value: "yes", label: "Yes", hint: "opencode/claude-opus-4-6, opencode/gpt-5.4, etc." },
|
||||
],
|
||||
initialValue: initial.opencodeZen,
|
||||
})
|
||||
|
||||
@@ -266,7 +266,7 @@ describe("AgentOverrideConfigSchema", () => {
|
||||
describe("backward compatibility", () => {
|
||||
test("still accepts model field (deprecated)", () => {
|
||||
// given
|
||||
const config = { model: "openai/gpt-5.2" }
|
||||
const config = { model: "openai/gpt-5.4" }
|
||||
|
||||
// when
|
||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||
@@ -274,14 +274,14 @@ describe("AgentOverrideConfigSchema", () => {
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.model).toBe("openai/gpt-5.2")
|
||||
expect(result.data.model).toBe("openai/gpt-5.4")
|
||||
}
|
||||
})
|
||||
|
||||
test("accepts both model and category (deprecated usage)", () => {
|
||||
// given - category should take precedence at runtime, but both should validate
|
||||
const config = {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
category: "ultrabrain"
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ describe("AgentOverrideConfigSchema", () => {
|
||||
// then
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.model).toBe("openai/gpt-5.2")
|
||||
expect(result.data.model).toBe("openai/gpt-5.4")
|
||||
expect(result.data.category).toBe("ultrabrain")
|
||||
}
|
||||
})
|
||||
@@ -343,7 +343,7 @@ describe("AgentOverrideConfigSchema", () => {
|
||||
describe("CategoryConfigSchema", () => {
|
||||
test("accepts variant as optional string", () => {
|
||||
// given
|
||||
const config = { model: "openai/gpt-5.2", variant: "xhigh" }
|
||||
const config = { model: "openai/gpt-5.4", variant: "xhigh" }
|
||||
|
||||
// when
|
||||
const result = CategoryConfigSchema.safeParse(config)
|
||||
@@ -371,7 +371,7 @@ describe("CategoryConfigSchema", () => {
|
||||
|
||||
test("rejects non-string variant", () => {
|
||||
// given
|
||||
const config = { model: "openai/gpt-5.2", variant: 123 }
|
||||
const config = { model: "openai/gpt-5.4", variant: 123 }
|
||||
|
||||
// when
|
||||
const result = CategoryConfigSchema.safeParse(config)
|
||||
@@ -413,7 +413,7 @@ describe("Sisyphus-Junior agent override", () => {
|
||||
const config = {
|
||||
agents: {
|
||||
"sisyphus-junior": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.2,
|
||||
},
|
||||
},
|
||||
@@ -426,7 +426,7 @@ describe("Sisyphus-Junior agent override", () => {
|
||||
expect(result.success).toBe(true)
|
||||
if (result.success) {
|
||||
expect(result.data.agents?.["sisyphus-junior"]).toBeDefined()
|
||||
expect(result.data.agents?.["sisyphus-junior"]?.model).toBe("openai/gpt-5.2")
|
||||
expect(result.data.agents?.["sisyphus-junior"]?.model).toBe("openai/gpt-5.4")
|
||||
expect(result.data.agents?.["sisyphus-junior"]?.temperature).toBe(0.2)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2078,7 +2078,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
||||
description: "Task 2",
|
||||
prompt: "Do something else",
|
||||
agent: "test-agent",
|
||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
parentSessionID: "parent-session",
|
||||
parentMessageID: "parent-message",
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe("createAnthropicEffortHook", () => {
|
||||
const hook = createAnthropicEffortHook()
|
||||
const { input, output } = createMockParams({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.2",
|
||||
modelID: "gpt-5.4",
|
||||
})
|
||||
|
||||
//#when chat.params hook is called
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Routing logic:
|
||||
* 1. Planner agents (prometheus, plan) → planner.ts
|
||||
* 2. GPT 5.2 models → gpt5.2.ts
|
||||
* 2. GPT 5.4 models → gpt5.4.ts
|
||||
* 3. Gemini models → gemini.ts
|
||||
* 4. Everything else (Claude, etc.) → default.ts
|
||||
*/
|
||||
|
||||
@@ -104,7 +104,7 @@ describe("no-sisyphus-gpt hook", () => {
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_3",
|
||||
agent: HEPHAESTUS_DISPLAY,
|
||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
||||
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||
}, output)
|
||||
|
||||
// then - no toast
|
||||
@@ -126,7 +126,7 @@ describe("no-sisyphus-gpt hook", () => {
|
||||
// when - chat.message runs without input.agent
|
||||
await hook["chat.message"]?.({
|
||||
sessionID: "ses_4",
|
||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
||||
model: { providerID: "openai", modelID: "gpt-4o" },
|
||||
}, output)
|
||||
|
||||
// then - toast shown via session-agent fallback
|
||||
|
||||
@@ -103,7 +103,7 @@ describe("runtime-fallback", () => {
|
||||
await hook.event({
|
||||
event: {
|
||||
type: "session.created",
|
||||
properties: { info: { id: sessionID, model: "openai/gpt-5.2" } },
|
||||
properties: { info: { id: sessionID, model: "openai/gpt-5.4" } },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -202,7 +202,7 @@ describe("runtime-fallback", () => {
|
||||
test("should trigger fallback for missing API key errors when fallback models are configured", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
})
|
||||
const sessionID = "test-session-missing-api-key-fallback"
|
||||
SessionCategoryRegistry.register(sessionID, "test")
|
||||
@@ -230,7 +230,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should detect retryable error from message pattern 'rate limit'", async () => {
|
||||
@@ -260,7 +260,7 @@ describe("runtime-fallback", () => {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"anthropic/claude-opus-4.6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
})
|
||||
const sessionID = "test-session-model-not-found"
|
||||
@@ -302,7 +302,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLogs = logCalls.filter((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLogs.length).toBeGreaterThanOrEqual(2)
|
||||
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.6", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.6", to: "openai/gpt-5.4" })
|
||||
|
||||
const nonRetryLog = logCalls.find(
|
||||
(c) => c.msg.includes("Error not retryable") && (c.data as { sessionID?: string } | undefined)?.sessionID === sessionID
|
||||
@@ -313,7 +313,7 @@ describe("runtime-fallback", () => {
|
||||
test("should trigger fallback on Copilot auto-retry signal in message.updated", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
})
|
||||
|
||||
const sessionID = "test-session-copilot-auto-retry"
|
||||
@@ -346,7 +346,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.6", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.6", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should trigger fallback on OpenAI auto-retry signal in message.updated", async () => {
|
||||
@@ -658,7 +658,7 @@ describe("runtime-fallback", () => {
|
||||
test("should trigger fallback when message.updated has missing API key error without model", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
})
|
||||
const sessionID = "test-message-updated-missing-model"
|
||||
SessionCategoryRegistry.register(sessionID, "test")
|
||||
@@ -689,7 +689,7 @@ describe("runtime-fallback", () => {
|
||||
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "google/gemini-2.5-pro", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should not advance fallback state from message.updated while retry is already in flight", async () => {
|
||||
@@ -709,7 +709,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
}
|
||||
)
|
||||
@@ -799,7 +799,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
}
|
||||
)
|
||||
@@ -883,7 +883,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -949,7 +949,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1034,7 +1034,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1099,7 +1099,7 @@ describe("runtime-fallback", () => {
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"github-copilot/claude-opus-4.6",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
]),
|
||||
session_timeout_ms: 20,
|
||||
}
|
||||
@@ -1637,7 +1637,7 @@ describe("runtime-fallback", () => {
|
||||
}),
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1665,7 +1665,7 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
expect(retriedModels).toContain("openai/gpt-5.2")
|
||||
expect(retriedModels).toContain("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("triggers fallback when message has mixed text and error parts", async () => {
|
||||
@@ -1745,7 +1745,7 @@ describe("runtime-fallback", () => {
|
||||
}),
|
||||
{
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1841,7 +1841,7 @@ describe("runtime-fallback", () => {
|
||||
test("should apply fallback model on next chat.message after error", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2", "google/gemini-3.1-pro"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4", "google/gemini-3.1-pro"]),
|
||||
})
|
||||
const sessionID = "test-session-switch"
|
||||
SessionCategoryRegistry.register(sessionID, "test")
|
||||
@@ -1871,13 +1871,13 @@ describe("runtime-fallback", () => {
|
||||
output
|
||||
)
|
||||
|
||||
expect(output.message.model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
||||
expect(output.message.model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should notify when fallback occurs", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: true }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||
})
|
||||
const sessionID = "test-session-notify"
|
||||
SessionCategoryRegistry.register(sessionID, "test")
|
||||
@@ -1897,7 +1897,7 @@ describe("runtime-fallback", () => {
|
||||
})
|
||||
|
||||
expect(toastCalls.length).toBe(1)
|
||||
expect(toastCalls[0]?.message.includes("gpt-5.2")).toBe(true)
|
||||
expect(toastCalls[0]?.message.includes("gpt-5.4")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1916,7 +1916,7 @@ describe("runtime-fallback", () => {
|
||||
const input = createMockPluginInput()
|
||||
const hook = createRuntimeFallbackHook(input, {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("oracle", ["openai/gpt-5.2", "google/gemini-3.1-pro"]),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("oracle", ["openai/gpt-5.4", "google/gemini-3.1-pro"]),
|
||||
})
|
||||
const sessionID = "test-agent-fallback"
|
||||
|
||||
@@ -1936,16 +1936,16 @@ describe("runtime-fallback", () => {
|
||||
},
|
||||
})
|
||||
|
||||
//#then - should prepare fallback to openai/gpt-5.2
|
||||
//#then - should prepare fallback to openai/gpt-5.4
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "anthropic/claude-opus-4-5", to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ from: "anthropic/claude-opus-4-5", to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should detect agent from sessionID pattern", async () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("sisyphus", ["openai/gpt-5.2"]),
|
||||
pluginConfig: createMockPluginConfigWithAgentFallback("sisyphus", ["openai/gpt-5.4"]),
|
||||
})
|
||||
const sessionID = "sisyphus-session-123"
|
||||
|
||||
@@ -1966,7 +1966,7 @@ describe("runtime-fallback", () => {
|
||||
//#then - should detect sisyphus from sessionID and use its fallback
|
||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||
expect(fallbackLog).toBeDefined()
|
||||
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.2" })
|
||||
expect(fallbackLog?.data).toMatchObject({ to: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("should preserve resolved agent during auto-retry", async () => {
|
||||
@@ -2019,7 +2019,7 @@ describe("runtime-fallback", () => {
|
||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||
config: createMockConfig({ cooldown_seconds: 60, notify_on_fallback: false }),
|
||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-5",
|
||||
]),
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ describe("createThinkModeHook", () => {
|
||||
const input = createHookInput({
|
||||
sessionID,
|
||||
providerID: "github-copilot",
|
||||
modelID: "gpt-5.2",
|
||||
modelID: "gpt-5.4",
|
||||
})
|
||||
const output = createHookOutput("ultrathink about this")
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("createThinkModeHook", () => {
|
||||
expect(output.message.variant).toBe("high")
|
||||
expect(output.message.model).toEqual({
|
||||
providerID: "github-copilot",
|
||||
modelID: "gpt-5-2-high",
|
||||
modelID: "gpt-5-4-high",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -32,11 +32,11 @@ describe("think-mode switcher", () => {
|
||||
})
|
||||
|
||||
it("should handle dots in GPT version numbers", () => {
|
||||
// given a GPT model ID with dot format (gpt-5.2)
|
||||
const variant = getHighVariant("gpt-5.2")
|
||||
// given a GPT model ID with dot format (gpt-5.4)
|
||||
const variant = getHighVariant("gpt-5.4")
|
||||
|
||||
// then should return high variant
|
||||
expect(variant).toBe("gpt-5-2-high")
|
||||
expect(variant).toBe("gpt-5-4-high")
|
||||
})
|
||||
|
||||
it("should handle dots in GPT-5.1 codex variants", () => {
|
||||
@@ -60,7 +60,7 @@ describe("think-mode switcher", () => {
|
||||
it("should return null for already-high variants", () => {
|
||||
// given model IDs that are already high variants
|
||||
expect(getHighVariant("claude-opus-4-6-high")).toBeNull()
|
||||
expect(getHighVariant("gpt-5-2-high")).toBeNull()
|
||||
expect(getHighVariant("gpt-5-4-high")).toBeNull()
|
||||
expect(getHighVariant("gemini-3-1-pro-high")).toBeNull()
|
||||
})
|
||||
|
||||
@@ -76,20 +76,20 @@ describe("think-mode switcher", () => {
|
||||
it("should detect -high suffix", () => {
|
||||
// given model IDs with -high suffix
|
||||
expect(isAlreadyHighVariant("claude-opus-4-6-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gpt-5-2-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gpt-5-4-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gemini-3.1-pro-high")).toBe(true)
|
||||
})
|
||||
|
||||
it("should detect -high suffix after normalization", () => {
|
||||
// given model IDs with dots that end in -high
|
||||
expect(isAlreadyHighVariant("gpt-5.2-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("gpt-5.4-high")).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for base models", () => {
|
||||
// given base model IDs without -high suffix
|
||||
expect(isAlreadyHighVariant("claude-opus-4-6")).toBe(false)
|
||||
expect(isAlreadyHighVariant("claude-opus-4.6")).toBe(false)
|
||||
expect(isAlreadyHighVariant("gpt-5.2")).toBe(false)
|
||||
expect(isAlreadyHighVariant("gpt-5.4")).toBe(false)
|
||||
expect(isAlreadyHighVariant("gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -111,10 +111,10 @@ describe("think-mode switcher", () => {
|
||||
|
||||
it("should preserve openai/ prefix when getting high variant", () => {
|
||||
// given a model ID with openai/ prefix
|
||||
const variant = getHighVariant("openai/gpt-5-2")
|
||||
const variant = getHighVariant("openai/gpt-5-4")
|
||||
|
||||
// then should return high variant with prefix preserved
|
||||
expect(variant).toBe("openai/gpt-5-2-high")
|
||||
expect(variant).toBe("openai/gpt-5-4-high")
|
||||
})
|
||||
|
||||
it("should handle prefixes with dots in version numbers", () => {
|
||||
@@ -141,7 +141,7 @@ describe("think-mode switcher", () => {
|
||||
it("should return null for already-high prefixed models", () => {
|
||||
// given prefixed model IDs that are already high
|
||||
expect(getHighVariant("vertex_ai/claude-opus-4-6-high")).toBeNull()
|
||||
expect(getHighVariant("openai/gpt-5-2-high")).toBeNull()
|
||||
expect(getHighVariant("openai/gpt-5-4-high")).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -149,20 +149,20 @@ describe("think-mode switcher", () => {
|
||||
it("should detect -high suffix in prefixed models", () => {
|
||||
// given prefixed model IDs with -high suffix
|
||||
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("openai/gpt-5-2-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("openai/gpt-5-4-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("custom/gemini-3.1-pro-high")).toBe(true)
|
||||
})
|
||||
|
||||
it("should return false for prefixed base models", () => {
|
||||
// given prefixed base model IDs without -high suffix
|
||||
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6")).toBe(false)
|
||||
expect(isAlreadyHighVariant("openai/gpt-5-2")).toBe(false)
|
||||
expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false)
|
||||
})
|
||||
|
||||
it("should handle prefixed models with dots", () => {
|
||||
// given prefixed model IDs with dots
|
||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2")).toBe(false)
|
||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2-high")).toBe(true)
|
||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4")).toBe(false)
|
||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4-high")).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +25,7 @@ import { normalizeModelID } from "../../shared"
|
||||
* @example
|
||||
* extractModelPrefix("vertex_ai/claude-sonnet-4-6") // { prefix: "vertex_ai/", base: "claude-sonnet-4-6" }
|
||||
* extractModelPrefix("claude-sonnet-4-6") // { prefix: "", base: "claude-sonnet-4-6" }
|
||||
* extractModelPrefix("openai/gpt-5.2") // { prefix: "openai/", base: "gpt-5.2" }
|
||||
* extractModelPrefix("openai/gpt-5.4") // { prefix: "openai/", base: "gpt-5.4" }
|
||||
*/
|
||||
function extractModelPrefix(modelID: string): { prefix: string; base: string } {
|
||||
const slashIndex = modelID.indexOf("/")
|
||||
@@ -61,10 +61,10 @@ const HIGH_VARIANT_MAP: Record<string, string> = {
|
||||
"gpt-5-1-codex": "gpt-5-1-codex-high",
|
||||
"gpt-5-1-codex-mini": "gpt-5-1-codex-mini-high",
|
||||
"gpt-5-1-codex-max": "gpt-5-1-codex-max-high",
|
||||
// GPT-5.2
|
||||
"gpt-5-2": "gpt-5-2-high",
|
||||
"gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
|
||||
"gpt-5-2-pro": "gpt-5-2-pro-high",
|
||||
// GPT-5.4
|
||||
"gpt-5-4": "gpt-5-4-high",
|
||||
"gpt-5-4-chat-latest": "gpt-5-4-chat-latest-high",
|
||||
"gpt-5-4-pro": "gpt-5-4-pro-high",
|
||||
// Antigravity (Google)
|
||||
"antigravity-gemini-3-1-pro": "antigravity-gemini-3-1-pro-high",
|
||||
"antigravity-gemini-3-flash": "antigravity-gemini-3-flash-high",
|
||||
@@ -97,4 +97,3 @@ export function isAlreadyHighVariant(modelID: string): boolean {
|
||||
const { base } = extractModelPrefix(normalized)
|
||||
return ALREADY_HIGH.has(base) || base.endsWith("-high")
|
||||
}
|
||||
|
||||
|
||||
@@ -1345,8 +1345,8 @@ describe("todo-continuation-enforcer", () => {
|
||||
|
||||
// OpenCode returns assistant messages with flat modelID/providerID, not nested model object
|
||||
const mockMessagesWithAssistant = [
|
||||
{ info: { id: "msg-1", role: "user", agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.2" } } },
|
||||
{ info: { id: "msg-2", role: "assistant", agent: "sisyphus", modelID: "gpt-5.2", providerID: "openai" } },
|
||||
{ info: { id: "msg-1", role: "user", agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.4" } } },
|
||||
{ info: { id: "msg-2", role: "assistant", agent: "sisyphus", modelID: "gpt-5.4", providerID: "openai" } },
|
||||
]
|
||||
|
||||
const mockInput = {
|
||||
@@ -1390,7 +1390,7 @@ describe("todo-continuation-enforcer", () => {
|
||||
|
||||
// then - model should be extracted from assistant message's flat modelID/providerID
|
||||
expect(promptCalls.length).toBe(1)
|
||||
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.2" })
|
||||
expect(promptCalls[0].model).toEqual({ providerID: "openai", modelID: "gpt-5.4" })
|
||||
})
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -12,7 +12,7 @@ describe("mergeConfigs", () => {
|
||||
const base = {
|
||||
categories: {
|
||||
general: {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.5,
|
||||
},
|
||||
quick: {
|
||||
@@ -35,7 +35,7 @@ describe("mergeConfigs", () => {
|
||||
const result = mergeConfigs(base, override);
|
||||
|
||||
// then general.model should be preserved from base
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
|
||||
// then general.temperature should be overridden
|
||||
expect(result.categories?.general?.temperature).toBe(0.3);
|
||||
// then quick should be preserved from base
|
||||
@@ -48,7 +48,7 @@ describe("mergeConfigs", () => {
|
||||
const base: OhMyOpenCodeConfig = {
|
||||
categories: {
|
||||
general: {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -57,7 +57,7 @@ describe("mergeConfigs", () => {
|
||||
|
||||
const result = mergeConfigs(base, override);
|
||||
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
|
||||
});
|
||||
|
||||
it("should use override categories when base has no categories", () => {
|
||||
@@ -66,14 +66,14 @@ describe("mergeConfigs", () => {
|
||||
const override: OhMyOpenCodeConfig = {
|
||||
categories: {
|
||||
general: {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const result = mergeConfigs(base, override);
|
||||
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.2");
|
||||
expect(result.categories?.general?.model).toBe("openai/gpt-5.4");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ describe("mergeConfigs", () => {
|
||||
it("should deep merge agents", () => {
|
||||
const base: OhMyOpenCodeConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
},
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("mergeConfigs", () => {
|
||||
|
||||
const result = mergeConfigs(base, override);
|
||||
|
||||
expect(result.agents?.oracle?.model).toBe("openai/gpt-5.2");
|
||||
expect(result.agents?.oracle?.model).toBe("openai/gpt-5.4");
|
||||
expect(result.agents?.oracle?.temperature).toBe(0.5);
|
||||
expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-5");
|
||||
});
|
||||
@@ -127,8 +127,8 @@ describe("parseConfigPartially", () => {
|
||||
it("should return the full config when everything is valid", () => {
|
||||
const rawConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
momus: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
momus: { model: "openai/gpt-5.4" },
|
||||
},
|
||||
disabled_hooks: ["comment-checker"],
|
||||
};
|
||||
@@ -136,8 +136,8 @@ describe("parseConfigPartially", () => {
|
||||
const result = parseConfigPartially(rawConfig);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
|
||||
expect(result!.agents?.momus?.model).toBe("openai/gpt-5.2");
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
|
||||
expect(result!.agents?.momus?.model).toBe("openai/gpt-5.4");
|
||||
expect(result!.disabled_hooks).toEqual(["comment-checker"]);
|
||||
});
|
||||
});
|
||||
@@ -150,8 +150,8 @@ describe("parseConfigPartially", () => {
|
||||
it("should preserve valid agent overrides when another section is invalid", () => {
|
||||
const rawConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
momus: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
momus: { model: "openai/gpt-5.4" },
|
||||
prometheus: {
|
||||
permission: {
|
||||
edit: { "*": "ask", ".sisyphus/**": "allow" },
|
||||
@@ -171,7 +171,7 @@ describe("parseConfigPartially", () => {
|
||||
it("should preserve valid agents when a non-agent section is invalid", () => {
|
||||
const rawConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
},
|
||||
disabled_hooks: ["not-a-real-hook"],
|
||||
};
|
||||
@@ -179,7 +179,7 @@ describe("parseConfigPartially", () => {
|
||||
const result = parseConfigPartially(rawConfig);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
|
||||
expect(result!.disabled_hooks).toEqual(["not-a-real-hook"]);
|
||||
});
|
||||
});
|
||||
@@ -224,7 +224,7 @@ describe("parseConfigPartially", () => {
|
||||
it("should ignore unknown keys and return valid sections", () => {
|
||||
const rawConfig = {
|
||||
agents: {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
},
|
||||
some_future_key: { foo: "bar" },
|
||||
};
|
||||
@@ -232,7 +232,7 @@ describe("parseConfigPartially", () => {
|
||||
const result = parseConfigPartially(rawConfig);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.2");
|
||||
expect(result!.agents?.oracle?.model).toBe("openai/gpt-5.4");
|
||||
expect((result as Record<string, unknown>)["some_future_key"]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -656,7 +656,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
categories: {
|
||||
"test-planning": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
reasoningEffort: "xhigh",
|
||||
},
|
||||
},
|
||||
@@ -698,7 +698,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
categories: {
|
||||
"reasoning-cat": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
reasoningEffort: "high",
|
||||
},
|
||||
},
|
||||
@@ -739,7 +739,7 @@ describe("Prometheus direct override priority over category", () => {
|
||||
},
|
||||
categories: {
|
||||
"temp-cat": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.8,
|
||||
},
|
||||
},
|
||||
@@ -860,7 +860,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
test("plan agent inherits temperature, reasoningEffort, and other model settings from prometheus", async () => {
|
||||
//#given - prometheus configured with category that has temperature and reasoningEffort
|
||||
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
provenance: "override",
|
||||
variant: "high",
|
||||
})
|
||||
@@ -871,7 +871,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
agents: {
|
||||
prometheus: {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
variant: "high",
|
||||
temperature: 0.3,
|
||||
top_p: 0.9,
|
||||
@@ -902,7 +902,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
const agents = config.agent as Record<string, Record<string, unknown>>
|
||||
expect(agents.plan).toBeDefined()
|
||||
expect(agents.plan.mode).toBe("subagent")
|
||||
expect(agents.plan.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.plan.model).toBe("openai/gpt-5.4")
|
||||
expect(agents.plan.variant).toBe("high")
|
||||
expect(agents.plan.temperature).toBe(0.3)
|
||||
expect(agents.plan.top_p).toBe(0.9)
|
||||
@@ -913,7 +913,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
})
|
||||
|
||||
test("plan agent user override takes priority over prometheus inherited settings", async () => {
|
||||
//#given - prometheus resolves to opus, but user has plan override for gpt-5.2
|
||||
//#given - prometheus resolves to opus, but user has plan override for gpt-5.4
|
||||
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||
model: "anthropic/claude-opus-4-6",
|
||||
provenance: "provider-fallback",
|
||||
@@ -926,7 +926,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
},
|
||||
agents: {
|
||||
plan: {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
variant: "high",
|
||||
temperature: 0.5,
|
||||
},
|
||||
@@ -950,7 +950,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
||||
|
||||
//#then - plan uses its own override, not prometheus settings
|
||||
const agents = config.agent as Record<string, Record<string, unknown>>
|
||||
expect(agents.plan.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.plan.model).toBe("openai/gpt-5.4")
|
||||
expect(agents.plan.variant).toBe("high")
|
||||
expect(agents.plan.temperature).toBe(0.5)
|
||||
})
|
||||
|
||||
@@ -64,7 +64,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
reasoningEffort: "high",
|
||||
}
|
||||
const planOverride = {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
variant: "high",
|
||||
temperature: 0.5,
|
||||
reasoningEffort: "low",
|
||||
@@ -74,7 +74,7 @@ describe("buildPlanDemoteConfig", () => {
|
||||
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
||||
|
||||
//#then
|
||||
expect(result.model).toBe("openai/gpt-5.2")
|
||||
expect(result.model).toBe("openai/gpt-5.4")
|
||||
expect(result.variant).toBe("high")
|
||||
expect(result.temperature).toBe(0.5)
|
||||
expect(result.reasoningEffort).toBe("low")
|
||||
@@ -89,14 +89,14 @@ describe("buildPlanDemoteConfig", () => {
|
||||
reasoningEffort: "high",
|
||||
}
|
||||
const planOverride = {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
}
|
||||
|
||||
//#when
|
||||
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
||||
|
||||
//#then - plan model wins, rest inherits from prometheus
|
||||
expect(result.model).toBe("openai/gpt-5.2")
|
||||
expect(result.model).toBe("openai/gpt-5.4")
|
||||
expect(result.variant).toBe("max")
|
||||
expect(result.temperature).toBe(0.1)
|
||||
expect(result.reasoningEffort).toBe("high")
|
||||
|
||||
@@ -45,7 +45,7 @@ describe("Agent Config Integration", () => {
|
||||
// given - config with lowercase keys
|
||||
const config = {
|
||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
librarian: { model: "opencode/big-pickle" },
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ describe("Agent Config Integration", () => {
|
||||
// given - config with mixed old and new format
|
||||
const mixedConfig = {
|
||||
Sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
"Prometheus (Planner)": { model: "anthropic/claude-opus-4-6" },
|
||||
librarian: { model: "opencode/big-pickle" },
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ describe("resolveAgentVariant", () => {
|
||||
sisyphus: { category: "ultrabrain" },
|
||||
},
|
||||
categories: {
|
||||
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
||||
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||
},
|
||||
} as OhMyOpenCodeConfig
|
||||
|
||||
@@ -127,7 +127,7 @@ describe("resolveVariantForModel", () => {
|
||||
test("returns undefined for provider not in sisyphus chain", () => {
|
||||
// #given openai is not in sisyphus fallback chain anymore
|
||||
const config = {} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "openai", modelID: "gpt-5.2" }
|
||||
const model = { providerID: "openai", modelID: "gpt-5.4" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "sisyphus", model)
|
||||
@@ -191,7 +191,7 @@ describe("resolveVariantForModel", () => {
|
||||
test("returns correct variant for oracle agent with openai", () => {
|
||||
// given
|
||||
const config = {} as OhMyOpenCodeConfig
|
||||
const model = { providerID: "openai", modelID: "gpt-5.2" }
|
||||
const model = { providerID: "openai", modelID: "gpt-5.4" }
|
||||
|
||||
// when
|
||||
const variant = resolveVariantForModel(config, "oracle", model)
|
||||
|
||||
@@ -38,7 +38,7 @@ describe("updateConnectedProvidersCache", () => {
|
||||
env: [],
|
||||
models: {
|
||||
"gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
||||
"gpt-5.2": { id: "gpt-5.2", name: "GPT-5.2" },
|
||||
"gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" },
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -64,7 +64,7 @@ describe("updateConnectedProvidersCache", () => {
|
||||
expect(cache).not.toBeNull()
|
||||
expect(cache!.connected).toEqual(["openai", "anthropic"])
|
||||
expect(cache!.models).toEqual({
|
||||
openai: ["gpt-5.3-codex", "gpt-5.2"],
|
||||
openai: ["gpt-5.3-codex", "gpt-5.4"],
|
||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -105,7 +105,7 @@ describe("parseJsonc", () => {
|
||||
const jsonc = `{
|
||||
// This is an example config
|
||||
"agents": {
|
||||
"oracle": { "model": "openai/gpt-5.2" }, // GPT for strategic reasoning
|
||||
"oracle": { "model": "openai/gpt-5.4" }, // GPT for strategic reasoning
|
||||
},
|
||||
/* Agent overrides */
|
||||
"disabled_agents": [],
|
||||
@@ -118,7 +118,7 @@ describe("parseJsonc", () => {
|
||||
}>(jsonc)
|
||||
|
||||
// then
|
||||
expect(result.agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(result.agents.oracle.model).toBe("openai/gpt-5.4")
|
||||
expect(result.disabled_agents).toEqual([])
|
||||
})
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ describe("mergeCategories", () => {
|
||||
it("allows user to add custom categories", () => {
|
||||
//#given
|
||||
const userCategories = {
|
||||
"my-custom": { model: "openai/gpt-5.2", description: "Custom category" },
|
||||
"my-custom": { model: "openai/gpt-5.4", description: "Custom category" },
|
||||
}
|
||||
|
||||
//#when
|
||||
@@ -52,13 +52,13 @@ describe("mergeCategories", () => {
|
||||
|
||||
//#then
|
||||
expect(result["my-custom"]).toBeDefined()
|
||||
expect(result["my-custom"].model).toBe("openai/gpt-5.2")
|
||||
expect(result["my-custom"].model).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
it("allows user to disable custom categories", () => {
|
||||
//#given
|
||||
const userCategories = {
|
||||
"my-custom": { model: "openai/gpt-5.2", disable: true },
|
||||
"my-custom": { model: "openai/gpt-5.4", disable: true },
|
||||
}
|
||||
|
||||
//#when
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("migrateAgentNames", () => {
|
||||
test("preserves current agent names unchanged", () => {
|
||||
// given: Config with current agent names
|
||||
const agents = {
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
librarian: { model: "google/gemini-3-flash" },
|
||||
explore: { model: "opencode/gpt-5-nano" },
|
||||
}
|
||||
@@ -47,7 +47,7 @@ describe("migrateAgentNames", () => {
|
||||
|
||||
// then: Current names should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(migrated["oracle"]).toEqual({ model: "openai/gpt-5.2" })
|
||||
expect(migrated["oracle"]).toEqual({ model: "openai/gpt-5.4" })
|
||||
expect(migrated["librarian"]).toEqual({ model: "google/gemini-3-flash" })
|
||||
expect(migrated["explore"]).toEqual({ model: "opencode/gpt-5-nano" })
|
||||
})
|
||||
@@ -57,7 +57,7 @@ describe("migrateAgentNames", () => {
|
||||
const agents = {
|
||||
SISYPHUS: { model: "test" },
|
||||
"planner-sisyphus": { prompt: "test" },
|
||||
"Orchestrator-Sisyphus": { model: "openai/gpt-5.2" },
|
||||
"Orchestrator-Sisyphus": { model: "openai/gpt-5.4" },
|
||||
}
|
||||
|
||||
// when: Migrate agent names
|
||||
@@ -66,7 +66,7 @@ describe("migrateAgentNames", () => {
|
||||
// then: Case-insensitive lookup should migrate correctly
|
||||
expect(migrated["sisyphus"]).toEqual({ model: "test" })
|
||||
expect(migrated["prometheus"]).toEqual({ prompt: "test" })
|
||||
expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.2" })
|
||||
expect(migrated["atlas"]).toEqual({ model: "openai/gpt-5.4" })
|
||||
})
|
||||
|
||||
test("passes through unknown agent names unchanged", () => {
|
||||
@@ -441,11 +441,11 @@ describe("migrateConfigFile", () => {
|
||||
expect(rawConfig.disabled_hooks).toContain("anthropic-context-window-limit-recovery")
|
||||
})
|
||||
|
||||
test("does not migrate gpt-5.2-codex model versions in agents", () => {
|
||||
test("does not migrate gpt-5.4-codex model versions in agents", () => {
|
||||
// given: Config with old model version in agents
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
|
||||
},
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ describe("migrateConfigFile", () => {
|
||||
// then: Model version should remain unchanged
|
||||
expect(needsWrite).toBe(false)
|
||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||
expect(agents["sisyphus"].model).toBe("openai/gpt-5.2-codex")
|
||||
expect(agents["sisyphus"].model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("migrates model versions in categories", () => {
|
||||
@@ -479,7 +479,7 @@ describe("migrateConfigFile", () => {
|
||||
// given: Config with current model versions
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
},
|
||||
categories: {
|
||||
"my-category": { model: "anthropic/claude-opus-4-6" },
|
||||
@@ -514,10 +514,10 @@ describe("migration maps", () => {
|
||||
})
|
||||
|
||||
describe("MODEL_VERSION_MAP", () => {
|
||||
test("does not include openai/gpt-5.2-codex migration", () => {
|
||||
test("does not include openai/gpt-5.4-codex migration", () => {
|
||||
// given/when: Check MODEL_VERSION_MAP
|
||||
// then: openai/gpt-5.2-codex should not be migrated
|
||||
expect(MODEL_VERSION_MAP["openai/gpt-5.2-codex"]).toBeUndefined()
|
||||
// then: openai/gpt-5.4-codex should not be migrated
|
||||
expect(MODEL_VERSION_MAP["openai/gpt-5.4-codex"]).toBeUndefined()
|
||||
})
|
||||
|
||||
test("maps anthropic/claude-opus-4-5 to anthropic/claude-opus-4-6", () => {
|
||||
@@ -528,10 +528,10 @@ describe("MODEL_VERSION_MAP", () => {
|
||||
})
|
||||
|
||||
describe("migrateModelVersions", () => {
|
||||
test("#given a config with gpt-5.2-codex model #when migrating model versions #then does not overwrite with non-existent gpt-5.3-codex", () => {
|
||||
// given: Agent config with gpt-5.2-codex model
|
||||
test("#given a config with gpt-5.4-codex model #when migrating model versions #then does not overwrite with non-existent gpt-5.3-codex", () => {
|
||||
// given: Agent config with gpt-5.4-codex model
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
|
||||
}
|
||||
|
||||
// when: Migrate model versions
|
||||
@@ -540,7 +540,7 @@ describe("migrateModelVersions", () => {
|
||||
// then: Model should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
expect(sisyphus.temperature).toBe(0.1)
|
||||
})
|
||||
|
||||
@@ -562,7 +562,7 @@ describe("migrateModelVersions", () => {
|
||||
test("leaves unknown model strings untouched", () => {
|
||||
// given: Agent config with unknown model
|
||||
const agents = {
|
||||
oracle: { model: "openai/gpt-5.2", temperature: 0.5 },
|
||||
oracle: { model: "openai/gpt-5.4", temperature: 0.5 },
|
||||
}
|
||||
|
||||
// when: Migrate model versions
|
||||
@@ -571,7 +571,7 @@ describe("migrateModelVersions", () => {
|
||||
// then: Config should remain unchanged
|
||||
expect(changed).toBe(false)
|
||||
const oracle = migrated["oracle"] as Record<string, unknown>
|
||||
expect(oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(oracle.model).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("handles agent config with no model field", () => {
|
||||
@@ -605,9 +605,9 @@ describe("migrateModelVersions", () => {
|
||||
test("migrates multiple agents in one pass", () => {
|
||||
// given: Multiple agents with old models
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
}
|
||||
|
||||
// when: Migrate model versions
|
||||
@@ -615,9 +615,9 @@ describe("migrateModelVersions", () => {
|
||||
|
||||
// then: Only mapped models should be updated
|
||||
expect(changed).toBe(true)
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
expect((migrated["prometheus"] as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
|
||||
expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.2")
|
||||
expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("handles empty object", () => {
|
||||
@@ -635,9 +635,9 @@ describe("migrateModelVersions", () => {
|
||||
test("skips already-applied migrations", () => {
|
||||
// given: Agent config with old model, but migration already applied
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex", temperature: 0.1 },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex", temperature: 0.1 },
|
||||
}
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
|
||||
|
||||
// when: Migrate with applied migrations
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
||||
@@ -646,32 +646,32 @@ describe("migrateModelVersions", () => {
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toHaveLength(0)
|
||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("applies new migrations and records them", () => {
|
||||
// given: Agent config with old model, no prior migrations
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
|
||||
// when: Migrate without applied migrations
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
|
||||
|
||||
// then: No migration should be applied for gpt-5.2-codex
|
||||
// then: No migration should be applied for gpt-5.4-codex
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toEqual([])
|
||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("handles mixed: some applied, some new", () => {
|
||||
// given: Multiple agents, one migration already applied
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
||||
}
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
|
||||
|
||||
// when: Migrate with partial history
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
||||
@@ -679,23 +679,23 @@ describe("migrateModelVersions", () => {
|
||||
// then: Only prometheus should be migrated
|
||||
expect(changed).toBe(true)
|
||||
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
expect((migrated["prometheus"] as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
|
||||
})
|
||||
|
||||
test("backward compatible without appliedMigrations param", () => {
|
||||
// given: Agent config with old model, no appliedMigrations param
|
||||
const agents = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
|
||||
// when: Migrate without the param (backward compat)
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
|
||||
|
||||
// then: Should keep gpt-5.2-codex unchanged
|
||||
// then: Should keep gpt-5.4-codex unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toHaveLength(0)
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -706,14 +706,14 @@ describe("migrateConfigFile _migrations tracking", () => {
|
||||
const configPath = `${tmpDir}/oh-my-opencode.json`
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
},
|
||||
}
|
||||
|
||||
// when: Migrate config file
|
||||
const result = migrateConfigFile(configPath, rawConfig)
|
||||
|
||||
// then: gpt-5.2-codex should not produce migrations
|
||||
// then: gpt-5.4-codex should not produce migrations
|
||||
expect(result).toBe(false)
|
||||
expect(rawConfig._migrations).toBeUndefined()
|
||||
|
||||
@@ -727,9 +727,9 @@ describe("migrateConfigFile _migrations tracking", () => {
|
||||
const configPath = `${tmpDir}/oh-my-opencode.json`
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
},
|
||||
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
|
||||
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
|
||||
}
|
||||
|
||||
// when: Migrate config file
|
||||
@@ -738,7 +738,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
||||
// then: Should NOT rewrite (model stays as user set it)
|
||||
// Note: result may be true due to other migrations, but model should NOT change
|
||||
const sisyphus = (rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect(sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
|
||||
// cleanup
|
||||
fs.rmSync(tmpDir, { recursive: true })
|
||||
@@ -752,7 +752,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
||||
agents: {
|
||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
||||
},
|
||||
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
|
||||
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
|
||||
}
|
||||
|
||||
// when: Migrate config file
|
||||
@@ -761,7 +761,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
||||
// then: New migration appended, old one preserved
|
||||
expect(result).toBe(true)
|
||||
expect(rawConfig._migrations).toEqual([
|
||||
"model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex",
|
||||
"model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex",
|
||||
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6",
|
||||
])
|
||||
|
||||
@@ -825,7 +825,7 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
const configs = [
|
||||
{ model: "google/gemini-3.1-pro" },
|
||||
{ model: "google/gemini-3-flash" },
|
||||
{ model: "openai/gpt-5.2" },
|
||||
{ model: "openai/gpt-5.4" },
|
||||
{ model: "anthropic/claude-haiku-4-5" },
|
||||
{ model: "anthropic/claude-opus-4-6" },
|
||||
{ model: "anthropic/claude-sonnet-4-6" },
|
||||
@@ -847,7 +847,7 @@ describe("migrateAgentConfigToCategory", () => {
|
||||
test("preserves non-model fields during migration", () => {
|
||||
// given: Config with multiple fields
|
||||
const config = {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.1,
|
||||
top_p: 0.95,
|
||||
maxTokens: 4096,
|
||||
@@ -1020,7 +1020,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
"multimodal-looker": { model: "anthropic/claude-haiku-4-5" },
|
||||
oracle: { model: "openai/gpt-5.2" },
|
||||
oracle: { model: "openai/gpt-5.4" },
|
||||
"my-custom-agent": { model: "google/gemini-3.1-pro" },
|
||||
},
|
||||
}
|
||||
@@ -1036,7 +1036,7 @@ describe("migrateConfigFile with backup", () => {
|
||||
|
||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||
expect(agents["multimodal-looker"].model).toBe("anthropic/claude-haiku-4-5")
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
||||
expect(agents.oracle.model).toBe("openai/gpt-5.4")
|
||||
expect(agents["my-custom-agent"].model).toBe("google/gemini-3.1-pro")
|
||||
})
|
||||
|
||||
@@ -1140,9 +1140,9 @@ describe("migrateModelVersions with applied migrations", () => {
|
||||
test("skips already-applied migrations", () => {
|
||||
// given: Config with old model and migration already applied
|
||||
const configs = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
|
||||
|
||||
// when: Migrate model versions
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
||||
@@ -1150,32 +1150,32 @@ describe("migrateModelVersions with applied migrations", () => {
|
||||
// then: Migration should be skipped (user reverted)
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toEqual([])
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("applies new migrations not in history", () => {
|
||||
// given: Config with old model, no migration history
|
||||
const configs = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
const appliedMigrations = new Set<string>()
|
||||
|
||||
// when: Migrate model versions
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
||||
|
||||
// then: gpt-5.2-codex should not be migrated
|
||||
// then: gpt-5.4-codex should not be migrated
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toEqual([])
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("handles mixed: skip applied, apply new", () => {
|
||||
// given: Config with 2 old models, 1 already migrated
|
||||
const configs = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
oracle: { model: "anthropic/claude-opus-4-5" },
|
||||
}
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
|
||||
const appliedMigrations = new Set(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
|
||||
|
||||
// when: Migrate model versions
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
||||
@@ -1183,29 +1183,29 @@ describe("migrateModelVersions with applied migrations", () => {
|
||||
// then: Skip sisyphus (already applied), apply oracle
|
||||
expect(changed).toBe(true)
|
||||
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
expect((migrated.oracle as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
|
||||
})
|
||||
|
||||
test("backward compatible: no appliedMigrations param", () => {
|
||||
// given: Config with old model, no appliedMigrations param (legacy call)
|
||||
const configs = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
|
||||
// when: Migrate model versions (without appliedMigrations)
|
||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs)
|
||||
|
||||
// then: gpt-5.2-codex remains unchanged
|
||||
// then: gpt-5.4-codex remains unchanged
|
||||
expect(changed).toBe(false)
|
||||
expect(newMigrations).toEqual([])
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.2-codex")
|
||||
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("returns empty newMigrations when no migrations applied", () => {
|
||||
// given: Config with no old models
|
||||
const configs = {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
}
|
||||
|
||||
// when: Migrate model versions
|
||||
@@ -1235,7 +1235,7 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
const testConfigPath = "/tmp/test-config-migrations-1.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
},
|
||||
}
|
||||
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||
@@ -1244,10 +1244,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
// when: Migrate config file
|
||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
||||
|
||||
// then: gpt-5.2-codex should not create migration history
|
||||
// then: gpt-5.4-codex should not create migration history
|
||||
expect(needsWrite).toBe(false)
|
||||
expect(rawConfig._migrations).toBeUndefined()
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
})
|
||||
|
||||
test("skips re-applying already-recorded migrations", () => {
|
||||
@@ -1255,9 +1255,9 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
const testConfigPath = "/tmp/test-config-migrations-2.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
},
|
||||
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
|
||||
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
|
||||
}
|
||||
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||
cleanupPaths.push(testConfigPath)
|
||||
@@ -1267,8 +1267,8 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
|
||||
// then: Should not migrate (user reverted)
|
||||
expect(needsWrite).toBe(false)
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect(rawConfig._migrations).toEqual(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"])
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
expect(rawConfig._migrations).toEqual(["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"])
|
||||
})
|
||||
|
||||
test("preserves existing _migrations and appends new ones", () => {
|
||||
@@ -1276,10 +1276,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
const testConfigPath = "/tmp/test-config-migrations-3.json"
|
||||
const rawConfig: Record<string, unknown> = {
|
||||
agents: {
|
||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
||||
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||
oracle: { model: "anthropic/claude-opus-4-5" },
|
||||
},
|
||||
_migrations: ["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex"],
|
||||
_migrations: ["model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex"],
|
||||
}
|
||||
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||
cleanupPaths.push(testConfigPath)
|
||||
@@ -1289,10 +1289,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
||||
|
||||
// then: Should skip sisyphus, migrate oracle, append to _migrations
|
||||
expect(needsWrite).toBe(true)
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.2-codex")
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).sisyphus.model).toBe("openai/gpt-5.4-codex")
|
||||
expect((rawConfig.agents as Record<string, Record<string, unknown>>).oracle.model).toBe("anthropic/claude-opus-4-6")
|
||||
expect(rawConfig._migrations).toEqual([
|
||||
"model-version:openai/gpt-5.2-codex->openai/gpt-5.3-codex",
|
||||
"model-version:openai/gpt-5.4-codex->openai/gpt-5.3-codex",
|
||||
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6",
|
||||
])
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
export const MODEL_TO_CATEGORY_MAP: Record<string, string> = {
|
||||
"google/gemini-3.1-pro": "visual-engineering",
|
||||
"google/gemini-3-flash": "writing",
|
||||
"openai/gpt-5.2": "ultrabrain",
|
||||
"openai/gpt-5.4": "ultrabrain",
|
||||
"anthropic/claude-haiku-4-5": "quick",
|
||||
"anthropic/claude-opus-4-6": "unspecified-high",
|
||||
"anthropic/claude-sonnet-4-6": "unspecified-low",
|
||||
|
||||
@@ -61,7 +61,7 @@ describe("fetchAvailableModels", () => {
|
||||
|
||||
it("#given cache file with models #when fetchAvailableModels called with connectedProviders #then returns Set of model IDs", async () => {
|
||||
writeModelsCache({
|
||||
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { id: "google", models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
@@ -72,14 +72,14 @@ describe("fetchAvailableModels", () => {
|
||||
|
||||
expect(result).toBeInstanceOf(Set)
|
||||
expect(result.size).toBe(3)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
})
|
||||
|
||||
it("#given connectedProviders unknown #when fetchAvailableModels called without options #then returns empty Set", async () => {
|
||||
writeModelsCache({
|
||||
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels()
|
||||
@@ -141,7 +141,7 @@ describe("fetchAvailableModels", () => {
|
||||
|
||||
it("#given cache read twice #when second call made with same providers #then reads fresh each time", async () => {
|
||||
writeModelsCache({
|
||||
openai: { id: "openai", models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { id: "openai", models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { id: "anthropic", models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
})
|
||||
|
||||
@@ -149,7 +149,7 @@ describe("fetchAvailableModels", () => {
|
||||
const result2 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] })
|
||||
|
||||
expect(result1.size).toBe(result2.size)
|
||||
expect(result1.has("openai/gpt-5.2")).toBe(true)
|
||||
expect(result1.has("openai/gpt-5.4")).toBe(true)
|
||||
})
|
||||
|
||||
it("#given empty providers in cache #when fetchAvailableModels called with connectedProviders #then returns empty Set", async () => {
|
||||
@@ -187,12 +187,12 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return the matching model
|
||||
it("should match substring in model name", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.3-codex",
|
||||
"anthropic/claude-opus-4-6",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available model with preview suffix
|
||||
@@ -213,12 +213,12 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return exact match if it exists
|
||||
it("should prefer exact match over substring match", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.3-codex",
|
||||
"openai/gpt-5.2-ultra",
|
||||
"openai/gpt-5.4-ultra",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available models with multiple substring matches
|
||||
@@ -226,11 +226,11 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return the shorter model name (more specific)
|
||||
it("should prefer shorter model name when multiple matches exist", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2-ultra",
|
||||
"openai/gpt-5.2-ultra-mega",
|
||||
"openai/gpt-5.4-ultra",
|
||||
"openai/gpt-5.4-ultra-mega",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
||||
expect(result).toBe("openai/gpt-5.2-ultra")
|
||||
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4-ultra")
|
||||
})
|
||||
|
||||
// given available models with claude variants
|
||||
@@ -271,12 +271,12 @@ describe("fuzzyMatchModel", () => {
|
||||
// then only search models from specified providers
|
||||
it("should filter by provider when providers array is given", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"google/gemini-3",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available models from multiple providers
|
||||
@@ -284,7 +284,7 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return null
|
||||
it("should return null when provider filter excludes all matches", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
])
|
||||
const result = fuzzyMatchModel("claude", available, ["openai"])
|
||||
@@ -296,7 +296,7 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return null
|
||||
it("should return null when no match found", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
])
|
||||
const result = fuzzyMatchModel("gemini", available)
|
||||
@@ -308,11 +308,11 @@ describe("fuzzyMatchModel", () => {
|
||||
// then match case-insensitively
|
||||
it("should match case-insensitively", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
])
|
||||
const result = fuzzyMatchModel("GPT-5.2", available)
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
const result = fuzzyMatchModel("GPT-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available models with exact match and longer variants
|
||||
@@ -356,11 +356,11 @@ describe("fuzzyMatchModel", () => {
|
||||
// then return shortest full string (preserves tie-break behavior)
|
||||
it("should use shortest tie-break when multiple providers have same model ID", () => {
|
||||
const available = new Set([
|
||||
"opencode/gpt-5.2",
|
||||
"openai/gpt-5.2",
|
||||
"opencode/gpt-5.4",
|
||||
"openai/gpt-5.4",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available models with multiple providers
|
||||
@@ -368,12 +368,12 @@ describe("fuzzyMatchModel", () => {
|
||||
// then search all specified providers
|
||||
it("should search all specified providers", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
"google/gemini-3",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt", available, ["openai", "google"])
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given available models with provider prefix
|
||||
@@ -381,11 +381,11 @@ describe("fuzzyMatchModel", () => {
|
||||
// then only match models with correct provider prefix
|
||||
it("should only match models with correct provider prefix", () => {
|
||||
const available = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/gpt-something",
|
||||
])
|
||||
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
// given empty available set
|
||||
@@ -513,7 +513,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then only returns models from that provider
|
||||
it("should filter models by connected providers", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
@@ -524,7 +524,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
|
||||
expect(result.size).toBe(1)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(false)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(false)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -533,7 +533,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then returns models from all specified providers
|
||||
it("should filter models by multiple connected providers", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
||||
})
|
||||
@@ -545,7 +545,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
expect(result.size).toBe(2)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("google/gemini-3.1-pro")).toBe(true)
|
||||
expect(result.has("openai/gpt-5.2")).toBe(false)
|
||||
expect(result.has("openai/gpt-5.4")).toBe(false)
|
||||
})
|
||||
|
||||
// given cache with models
|
||||
@@ -553,7 +553,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then returns empty set
|
||||
it("should return empty set when connectedProviders is empty", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
})
|
||||
|
||||
@@ -569,7 +569,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then returns empty set (triggers fallback in resolver)
|
||||
it("should return empty set when connectedProviders not specified", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
})
|
||||
|
||||
@@ -583,7 +583,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then returns empty set for that provider
|
||||
it("should handle provider not in cache gracefully", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -598,7 +598,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then returns models only from matching providers
|
||||
it("should return models from providers that exist in both cache and connected list", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
})
|
||||
|
||||
@@ -615,7 +615,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then does NOT use cache (dynamic per-session)
|
||||
it("should not cache filtered results", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||
})
|
||||
|
||||
@@ -630,7 +630,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
connectedProviders: ["openai"]
|
||||
})
|
||||
expect(result2.size).toBe(1)
|
||||
expect(result2.has("openai/gpt-5.2")).toBe(true)
|
||||
expect(result2.has("openai/gpt-5.4")).toBe(true)
|
||||
})
|
||||
|
||||
// given connectedProviders unknown
|
||||
@@ -638,7 +638,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
||||
// then always returns empty set (triggers fallback)
|
||||
it("should return empty set when connectedProviders unknown", async () => {
|
||||
writeModelsCache({
|
||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
||||
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||
})
|
||||
|
||||
const result1 = await fetchAvailableModels()
|
||||
@@ -696,7 +696,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
connected: ["opencode", "anthropic"]
|
||||
})
|
||||
writeModelsCache({
|
||||
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.2": {} } },
|
||||
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } },
|
||||
anthropic: { models: { "claude-opus-4-6": {}, "claude-sonnet-4-6": {} } }
|
||||
})
|
||||
|
||||
@@ -708,7 +708,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
expect(result.has("opencode/big-pickle")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5-nano")).toBe(true)
|
||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5.2")).toBe(false)
|
||||
expect(result.has("opencode/gpt-5.4")).toBe(false)
|
||||
expect(result.has("anthropic/claude-sonnet-4-6")).toBe(false)
|
||||
})
|
||||
|
||||
@@ -738,7 +738,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
// then falls back to models.json (no whitelist filtering)
|
||||
it("should fallback to models.json when provider-models cache not found", async () => {
|
||||
writeModelsCache({
|
||||
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.2": {} } },
|
||||
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } },
|
||||
})
|
||||
|
||||
const result = await fetchAvailableModels(undefined, {
|
||||
@@ -748,7 +748,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
||||
expect(result.size).toBe(3)
|
||||
expect(result.has("opencode/big-pickle")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5-nano")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5.2")).toBe(true)
|
||||
expect(result.has("opencode/gpt-5.4")).toBe(true)
|
||||
})
|
||||
|
||||
// given provider-models cache with whitelist
|
||||
@@ -907,7 +907,7 @@ describe("fallback model availability", () => {
|
||||
|
||||
it("returns null for completely unknown model", () => {
|
||||
// given
|
||||
const available = new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6"])
|
||||
const available = new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"])
|
||||
|
||||
// when
|
||||
const result = fuzzyMatchModel("non-existent-model-family", available)
|
||||
@@ -918,7 +918,7 @@ describe("fallback model availability", () => {
|
||||
|
||||
it("returns true when models do not match but provider is connected", () => {
|
||||
// given
|
||||
const fallbackChain = [{ providers: ["openai"], model: "gpt-5.2" }]
|
||||
const fallbackChain = [{ providers: ["openai"], model: "gpt-5.4" }]
|
||||
const availableModels = new Set(["anthropic/claude-opus-4-6"])
|
||||
writeConnectedProvidersCache(["openai"])
|
||||
|
||||
@@ -932,25 +932,25 @@ describe("fallback model availability", () => {
|
||||
it("returns first resolved fallback model from chain", () => {
|
||||
// given
|
||||
const fallbackChain = [
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
]
|
||||
const availableModels = new Set([
|
||||
"anthropic/claude-opus-4-6",
|
||||
"openai/gpt-5.2-preview",
|
||||
"openai/gpt-5.4-preview",
|
||||
])
|
||||
|
||||
// when
|
||||
const result = resolveFirstAvailableFallback(fallbackChain, availableModels)
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ provider: "openai", model: "openai/gpt-5.2-preview" })
|
||||
expect(result).toEqual({ provider: "openai", model: "openai/gpt-5.4-preview" })
|
||||
})
|
||||
|
||||
it("returns null when no fallback model resolves", () => {
|
||||
// given
|
||||
const fallbackChain = [
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
]
|
||||
const availableModels = new Set(["google/gemini-3.1-pro"])
|
||||
|
||||
@@ -8,7 +8,7 @@ import { normalizeSDKResponse } from "./normalize-sdk-response"
|
||||
/**
|
||||
* Fuzzy match a target model name against available models
|
||||
*
|
||||
* @param target - The model name or substring to search for (e.g., "gpt-5.2", "claude-opus")
|
||||
* @param target - The model name or substring to search for (e.g., "gpt-5.4", "claude-opus")
|
||||
* @param available - Set of available model names in format "provider/model-name"
|
||||
* @param providers - Optional array of provider names to filter by (e.g., ["openai", "anthropic"])
|
||||
* @returns The matched model name or null if no match found
|
||||
@@ -21,8 +21,8 @@ import { normalizeSDKResponse } from "./normalize-sdk-response"
|
||||
* If providers array is given, only models starting with "provider/" are considered.
|
||||
*
|
||||
* @example
|
||||
* const available = new Set(["openai/gpt-5.2", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
||||
* fuzzyMatchModel("gpt-5.2", available) // → "openai/gpt-5.2"
|
||||
* const available = new Set(["openai/gpt-5.4", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
||||
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
|
||||
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
||||
*/
|
||||
function normalizeModelName(name: string): string {
|
||||
@@ -82,7 +82,7 @@ export function fuzzyMatchModel(
|
||||
|
||||
// Priority 2: Exact model ID match (part after provider/)
|
||||
// This ensures "big-pickle" matches "zai-coding-plan/big-pickle" over "zai-coding-plan/glm-5"
|
||||
// Use filter + shortest to handle multi-provider cases (e.g., openai/gpt-5.2 + opencode/gpt-5.2)
|
||||
// Use filter + shortest to handle multi-provider cases (e.g., openai/gpt-5.4 + opencode/gpt-5.4)
|
||||
const exactModelIdMatches = matches.filter((model) => {
|
||||
const modelId = model.split("/").slice(1).join("/")
|
||||
return normalizeModelName(modelId) === targetNormalized
|
||||
|
||||
@@ -7,19 +7,19 @@ import {
|
||||
} from "./model-requirements"
|
||||
|
||||
describe("AGENT_MODEL_REQUIREMENTS", () => {
|
||||
test("oracle has valid fallbackChain with gpt-5.2 as primary", () => {
|
||||
test("oracle has valid fallbackChain with gpt-5.4 as primary", () => {
|
||||
// given - oracle agent requirement
|
||||
const oracle = AGENT_MODEL_REQUIREMENTS["oracle"]
|
||||
|
||||
// when - accessing oracle requirement
|
||||
// then - fallbackChain exists with gpt-5.2 as first entry
|
||||
// then - fallbackChain exists with gpt-5.4 as first entry
|
||||
expect(oracle).toBeDefined()
|
||||
expect(oracle.fallbackChain).toBeArray()
|
||||
expect(oracle.fallbackChain.length).toBeGreaterThan(0)
|
||||
|
||||
const primary = oracle.fallbackChain[0]
|
||||
expect(primary.providers).toContain("openai")
|
||||
expect(primary.model).toBe("gpt-5.2")
|
||||
expect(primary.model).toBe("gpt-5.4")
|
||||
expect(primary.variant).toBe("high")
|
||||
})
|
||||
|
||||
@@ -435,7 +435,7 @@ describe("ModelRequirement type", () => {
|
||||
const requirement: ModelRequirement = {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6", variant: "max" },
|
||||
{ providers: ["openai", "github-copilot"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["openai", "github-copilot"], model: "gpt-5.4", variant: "high" },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ describe("ModelRequirement type", () => {
|
||||
expect(requirement.fallbackChain).toBeArray()
|
||||
expect(requirement.fallbackChain).toHaveLength(2)
|
||||
expect(requirement.fallbackChain[0].model).toBe("claude-opus-4-6")
|
||||
expect(requirement.fallbackChain[1].model).toBe("gpt-5.2")
|
||||
expect(requirement.fallbackChain[1].model).toBe("gpt-5.4")
|
||||
})
|
||||
|
||||
test("ModelRequirement variant is optional", () => {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
model: "gpt-5.3-codex",
|
||||
variant: "medium",
|
||||
},
|
||||
{ providers: ["github-copilot"], model: "gpt-5.2", variant: "medium" },
|
||||
{ providers: ["github-copilot"], model: "gpt-5.4", variant: "medium" },
|
||||
],
|
||||
requiresProvider: ["openai", "github-copilot", "venice", "opencode"],
|
||||
},
|
||||
@@ -40,7 +40,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
fallbackChain: [
|
||||
{
|
||||
providers: ["openai", "github-copilot", "opencode"],
|
||||
model: "gpt-5.2",
|
||||
model: "gpt-5.4",
|
||||
variant: "high",
|
||||
},
|
||||
{
|
||||
@@ -119,7 +119,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
},
|
||||
{
|
||||
providers: ["openai", "github-copilot", "opencode"],
|
||||
model: "gpt-5.2",
|
||||
model: "gpt-5.4",
|
||||
variant: "high",
|
||||
},
|
||||
{
|
||||
@@ -226,7 +226,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
||||
model: "claude-opus-4-6",
|
||||
variant: "max",
|
||||
},
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4" },
|
||||
],
|
||||
requiresModel: "gemini-3.1-pro",
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ describe("resolveModel", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("resolveModel", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: undefined,
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ describe("resolveModel", () => {
|
||||
const result = resolveModel(input)
|
||||
|
||||
// then
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("returns systemDefault when both userModel and inheritedModel are undefined", () => {
|
||||
@@ -56,7 +56,7 @@ describe("resolveModel", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ describe("resolveModel", () => {
|
||||
const result = resolveModel(input)
|
||||
|
||||
// then
|
||||
expect(result).toBe("openai/gpt-5.2")
|
||||
expect(result).toBe("openai/gpt-5.4")
|
||||
})
|
||||
|
||||
test("treats whitespace-only string as unset, uses fallback", () => {
|
||||
@@ -88,7 +88,7 @@ describe("resolveModel", () => {
|
||||
// given
|
||||
const input: ModelResolutionInput = {
|
||||
userModel: "anthropic/claude-opus-4-6",
|
||||
inheritedModel: "openai/gpt-5.2",
|
||||
inheritedModel: "openai/gpt-5.4",
|
||||
systemDefault: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -292,9 +292,9 @@ describe("resolveModelWithFallback", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.2" },
|
||||
{ providers: ["openai", "anthropic", "google"], model: "gpt-5.4" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6", "google/gemini-3.1-pro"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -302,7 +302,7 @@ describe("resolveModelWithFallback", () => {
|
||||
const result = resolveModelWithFallback(input)
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("openai/gpt-5.2")
|
||||
expect(result!.model).toBe("openai/gpt-5.4")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
@@ -476,7 +476,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2", "anthropic/claude-opus-4-6"]),
|
||||
availableModels: new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-6"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -592,7 +592,7 @@ describe("resolveModelWithFallback", () => {
|
||||
test("returns system default when fallbackChain is not provided", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
availableModels: new Set(["openai/gpt-5.4"]),
|
||||
systemDefaultModel: "google/gemini-3.1-pro",
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.2", variant: "high" },
|
||||
{ providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4", variant: "high" },
|
||||
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||
],
|
||||
availableModels,
|
||||
@@ -632,7 +632,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai", "anthropic"], model: "gpt-5.2" },
|
||||
{ providers: ["openai", "anthropic"], model: "gpt-5.4" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
availableModels,
|
||||
@@ -647,14 +647,14 @@ describe("resolveModelWithFallback", () => {
|
||||
test("returns first matching entry even if later entries have better matches", () => {
|
||||
// given
|
||||
const availableModels = new Set([
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"anthropic/claude-opus-4-6",
|
||||
])
|
||||
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
],
|
||||
availableModels,
|
||||
@@ -662,7 +662,7 @@ describe("resolveModelWithFallback", () => {
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result!.model).toBe("openai/gpt-5.2")
|
||||
expect(result!.model).toBe("openai/gpt-5.4")
|
||||
expect(result!.source).toBe("provider-fallback")
|
||||
})
|
||||
|
||||
@@ -673,7 +673,7 @@ describe("resolveModelWithFallback", () => {
|
||||
// when
|
||||
const result = resolveModelWithFallback({
|
||||
fallbackChain: [
|
||||
{ providers: ["openai"], model: "gpt-5.2" },
|
||||
{ providers: ["openai"], model: "gpt-5.4" },
|
||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||
],
|
||||
@@ -884,7 +884,7 @@ describe("resolveModelWithFallback", () => {
|
||||
fallbackChain: [
|
||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||
],
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
availableModels: new Set(["openai/gpt-5.4"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
@@ -898,7 +898,7 @@ describe("resolveModelWithFallback", () => {
|
||||
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
|
||||
// given
|
||||
const input: ExtendedModelResolutionInput = {
|
||||
availableModels: new Set(["openai/gpt-5.2"]),
|
||||
availableModels: new Set(["openai/gpt-5.4"]),
|
||||
systemDefaultModel: undefined,
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ describe("parseModelSuggestion", () => {
|
||||
data: {
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5",
|
||||
suggestions: ["gpt-5.2"],
|
||||
suggestions: ["gpt-5.4"],
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -82,7 +82,7 @@ describe("parseModelSuggestion", () => {
|
||||
expect(result).toEqual({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5",
|
||||
suggestion: "gpt-5.2",
|
||||
suggestion: "gpt-5.4",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
|
||||
data: [
|
||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 }, agent: "oracle" } },
|
||||
{
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn", agent: "oracle", providerID: "openai", modelID: "gpt-5.2" },
|
||||
info: { id: "msg_002", role: "assistant", time: { created: 2000 }, finish: "end_turn", agent: "oracle", providerID: "openai", modelID: "gpt-5.4" },
|
||||
parts: [{ type: "text", text: "Response" }],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ const TEST_AVAILABLE_MODELS = new Set([
|
||||
"anthropic/claude-haiku-4-5",
|
||||
"google/gemini-3.1-pro",
|
||||
"google/gemini-3-flash",
|
||||
"openai/gpt-5.2",
|
||||
"openai/gpt-5.4",
|
||||
"openai/gpt-5.3-codex",
|
||||
])
|
||||
|
||||
@@ -53,7 +53,7 @@ describe("sisyphus-task", () => {
|
||||
models: {
|
||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
||||
openai: ["gpt-5.2", "gpt-5.3-codex"],
|
||||
openai: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
},
|
||||
connected: ["anthropic", "google", "openai"],
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
@@ -824,7 +824,7 @@ describe("sisyphus-task", () => {
|
||||
const categoryName = "my-custom"
|
||||
const userCategories = {
|
||||
"my-custom": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
temperature: 0.5,
|
||||
prompt_append: "You are a custom agent",
|
||||
},
|
||||
@@ -835,7 +835,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then
|
||||
expect(result).not.toBeNull()
|
||||
expect(result!.config.model).toBe("openai/gpt-5.2")
|
||||
expect(result!.config.model).toBe("openai/gpt-5.4")
|
||||
expect(result!.config.temperature).toBe(0.5)
|
||||
expect(result!.promptAppend).toBe("You are a custom agent")
|
||||
})
|
||||
@@ -948,7 +948,7 @@ describe("sisyphus-task", () => {
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
userCategories: {
|
||||
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
||||
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||
},
|
||||
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||
availableModelsOverride: createTestAvailableModels(),
|
||||
@@ -976,7 +976,7 @@ describe("sisyphus-task", () => {
|
||||
// then
|
||||
expect(launchInput.model).toEqual({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.2",
|
||||
modelID: "gpt-5.4",
|
||||
variant: "xhigh",
|
||||
})
|
||||
})
|
||||
@@ -1040,9 +1040,9 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - variant MUST be "max" from DEFAULT_CATEGORIES
|
||||
expect(launchInput.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
variant: "max",
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
variant: "high",
|
||||
})
|
||||
}, { timeout: 20000 })
|
||||
|
||||
@@ -1101,10 +1101,10 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - variant MUST be "max" from DEFAULT_CATEGORIES (passed as separate field)
|
||||
expect(promptBody.model).toEqual({
|
||||
providerID: "anthropic",
|
||||
modelID: "claude-opus-4-6",
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
})
|
||||
expect(promptBody.variant).toBe("max")
|
||||
expect(promptBody.variant).toBe("high")
|
||||
}, { timeout: 20000 })
|
||||
})
|
||||
|
||||
@@ -1976,7 +1976,7 @@ describe("sisyphus-task", () => {
|
||||
},
|
||||
}
|
||||
|
||||
// Use ultrabrain which uses gpt-5.2 (non-gemini)
|
||||
// Use ultrabrain which uses gpt-5.4 (non-gemini)
|
||||
const tool = createDelegateTask({
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
@@ -2185,7 +2185,7 @@ describe("sisyphus-task", () => {
|
||||
client: mockClient,
|
||||
userCategories: {
|
||||
"my-unstable-cat": {
|
||||
model: "openai/gpt-5.2",
|
||||
model: "openai/gpt-5.4",
|
||||
is_unstable_agent: true,
|
||||
},
|
||||
},
|
||||
@@ -2568,7 +2568,7 @@ describe("sisyphus-task", () => {
|
||||
const tool = createDelegateTask({
|
||||
manager: mockManager,
|
||||
client: mockClient,
|
||||
sisyphusJuniorModel: "openai/gpt-5.2",
|
||||
sisyphusJuniorModel: "openai/gpt-5.4",
|
||||
userCategories: {
|
||||
"my-custom": { temperature: 0.5 },
|
||||
},
|
||||
@@ -2595,7 +2595,7 @@ describe("sisyphus-task", () => {
|
||||
|
||||
// then - sisyphus-junior override model should be used as fallback
|
||||
expect(launchInput.model.providerID).toBe("openai")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.2")
|
||||
expect(launchInput.model.modelID).toBe("gpt-5.4")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3406,7 +3406,7 @@ describe("sisyphus-task", () => {
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -3473,7 +3473,7 @@ describe("sisyphus-task", () => {
|
||||
app: {
|
||||
agents: async () => ({
|
||||
data: [
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.2" } },
|
||||
{ name: "oracle", mode: "subagent", model: { providerID: "openai", modelID: "gpt-5.4" } },
|
||||
],
|
||||
}),
|
||||
},
|
||||
@@ -3582,11 +3582,11 @@ describe("sisyphus-task", () => {
|
||||
)
|
||||
|
||||
// then - should resolve via AGENT_MODEL_REQUIREMENTS fallback chain for oracle
|
||||
// oracle fallback chain: gpt-5.2 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic)
|
||||
// Since openai is in connectedProviders, should resolve to openai/gpt-5.2
|
||||
// oracle fallback chain: gpt-5.4 (openai) > gemini-3.1-pro (google) > claude-opus-4-6 (anthropic)
|
||||
// Since openai is in connectedProviders, should resolve to openai/gpt-5.4
|
||||
expect(promptBody.model).toBeDefined()
|
||||
expect(promptBody.model.providerID).toBe("openai")
|
||||
expect(promptBody.model.modelID).toContain("gpt-5.2")
|
||||
expect(promptBody.model.modelID).toContain("gpt-5.4")
|
||||
}, { timeout: 20000 })
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user