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 |
|
| 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. |
|
| **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
|
### 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 |
|
| Agent | Role | Fallback Chain | Notes |
|
||||||
| -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ |
|
| -------------- | ----------------------- | -------------------------------------- | ------------------------------------------------ |
|
||||||
| **Hephaestus** | Autonomous deep worker | GPT-5.3 Codex only | No fallback. Requires GPT access. The craftsman. |
|
| **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. |
|
| **Momus** | Ruthless reviewer | GPT-5.4 → Claude Opus → Gemini 3.1 Pro | Verification and plan review. |
|
||||||
|
|
||||||
### Utility Runners → Speed over Intelligence
|
### Utility Runners → Speed over Intelligence
|
||||||
@@ -119,7 +119,7 @@ Principle-driven, explicit reasoning, deep technical capability. Best for agents
|
|||||||
| Model | Strengths |
|
| Model | Strengths |
|
||||||
| ----------------- | ----------------------------------------------------------------------------------------------- |
|
| ----------------- | ----------------------------------------------------------------------------------------------- |
|
||||||
| **GPT-5.3 Codex** | Deep coding powerhouse. Autonomous exploration. Required for Hephaestus. |
|
| **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.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. |
|
| **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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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-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 |
|
| `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" },
|
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||||
|
|
||||||
// Architecture consultation: GPT or Claude Opus
|
// 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 inherits sisyphus model; just add prompt guidance
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
|
|||||||
+12
-12
@@ -49,7 +49,7 @@ Ask the user these questions to determine CLI options:
|
|||||||
- If **no** → `--claude=no`
|
- If **no** → `--claude=no`
|
||||||
|
|
||||||
2. **Do you have an OpenAI/ChatGPT Plus Subscription?**
|
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)
|
- If **no** → `--openai=no` (default)
|
||||||
|
|
||||||
3. **Will you integrate Gemini models?**
|
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 |
|
| Agent | Model |
|
||||||
| ------------- | --------------------------------- |
|
| ------------- | --------------------------------- |
|
||||||
| **Sisyphus** | `github-copilot/claude-opus-4-6` |
|
| **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` |
|
| **Explore** | `github-copilot/grok-code-fast-1` |
|
||||||
| **Librarian** | `github-copilot/gemini-3-flash` |
|
| **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 |
|
| Agent | Model |
|
||||||
| ------------- | ---------------------------------------------------- |
|
| ------------- | ---------------------------------------------------- |
|
||||||
| **Sisyphus** | `opencode/claude-opus-4-6` |
|
| **Sisyphus** | `opencode/claude-opus-4-6` |
|
||||||
| **Oracle** | `opencode/gpt-5.2` |
|
| **Oracle** | `opencode/gpt-5.4` |
|
||||||
| **Explore** | `opencode/gpt-5-nano` |
|
| **Explore** | `opencode/gpt-5-nano` |
|
||||||
| **Librarian** | `opencode/minimax-m2.5-free` / `opencode/big-pickle` |
|
| **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 |
|
| Model | Provider(s) | Notes |
|
||||||
| ----------------- | -------------------------------- | ------------------------------------------------- |
|
| ----------------- | -------------------------------- | ------------------------------------------------- |
|
||||||
| **GPT-5.3-codex** | openai, github-copilot, opencode | Deep coding powerhouse. Required for Hephaestus. |
|
| **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. |
|
| **GPT-5-Nano** | opencode | Ultra-cheap, fast. Good for simple utility tasks. |
|
||||||
|
|
||||||
**Different-Behavior Models**:
|
**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 |
|
| 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.** |
|
| **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):
|
**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? |
|
| 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) |
|
| **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.2 | Yes — GPT-optimized todo management |
|
| **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):
|
**GPT-Native Agents** (built for GPT, don't override to Claude):
|
||||||
|
|
||||||
| Agent | Role | Default Chain | Notes |
|
| Agent | Role | Default Chain | Notes |
|
||||||
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
|
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
|
||||||
| **Hephaestus** | Deep autonomous worker | GPT-5.3-codex (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
|
| **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. |
|
| **Oracle** | Architecture/debugging | GPT-5.4 (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. |
|
| **Momus** | High-accuracy reviewer | GPT-5.4 (medium) → Opus → Gemini 3 Pro | Verification agent. GPT preferred. |
|
||||||
|
|
||||||
**Utility Agents** (speed over intelligence):
|
**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. |
|
| **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. |
|
| **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
|
#### 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):
|
**Safe** (same family):
|
||||||
|
|
||||||
- Sisyphus: Opus → Sonnet, Kimi K2.5, GLM 5
|
- Sisyphus: Opus → Sonnet, Kimi K2.5, GLM 5
|
||||||
- Prometheus: Opus → GPT-5.2 (auto-switches prompt)
|
- Prometheus: Opus → GPT-5.4 (auto-switches prompt)
|
||||||
- Atlas: Kimi K2.5 → Sonnet, GPT-5.2 (auto-switches)
|
- Atlas: Kimi K2.5 → Sonnet, GPT-5.4 (auto-switches)
|
||||||
|
|
||||||
**Dangerous** (no prompt support):
|
**Dangerous** (no prompt support):
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ flowchart TB
|
|||||||
|
|
||||||
subgraph Workers["Worker Layer (Specialized Agents)"]
|
subgraph Workers["Worker Layer (Specialized Agents)"]
|
||||||
Junior[" Sisyphus-Junior<br/>(Task Executor)<br/>Claude Sonnet 4.6"]
|
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"]
|
Explore[" Explore<br/>(Codebase Grep)<br/>Grok Code"]
|
||||||
Librarian[" Librarian<br/>(Docs/OSS)<br/>Gemini 3 Flash"]
|
Librarian[" Librarian<br/>(Docs/OSS)<br/>Gemini 3 Flash"]
|
||||||
Frontend[" Frontend<br/>(UI/UX)<br/>Gemini 3.1 Pro"]
|
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" },
|
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||||
|
|
||||||
// Architecture consultation: GPT or Claude Opus
|
// Architecture consultation: GPT or Claude Opus
|
||||||
"oracle": { "model": "openai/gpt-5.2", "variant": "high" },
|
"oracle": { "model": "openai/gpt-5.4", "variant": "high" },
|
||||||
},
|
},
|
||||||
|
|
||||||
"categories": {
|
"categories": {
|
||||||
@@ -215,7 +215,7 @@ You can override specific agents or categories in your config:
|
|||||||
**GPT models** (explicit reasoning, principle-driven):
|
**GPT models** (explicit reasoning, principle-driven):
|
||||||
|
|
||||||
- GPT-5.3-codex — deep coding powerhouse, required for Hephaestus
|
- 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
|
- GPT-5-Nano — ultra-cheap, fast utility tasks
|
||||||
|
|
||||||
**Different-behavior models**:
|
**Different-behavior models**:
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ Here's a practical starting configuration:
|
|||||||
"librarian": { "model": "google/gemini-3-flash" },
|
"librarian": { "model": "google/gemini-3-flash" },
|
||||||
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
"explore": { "model": "github-copilot/grok-code-fast-1" },
|
||||||
|
|
||||||
// Architecture consultation: GPT-5.2 or Claude Opus
|
// Architecture consultation: GPT-5.4 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 inherits sisyphus model; just add prompt guidance
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -268,13 +268,13 @@ Disable categories: `{ "disabled_categories": ["ultrabrain"] }`
|
|||||||
| Agent | Default Model | Provider Priority |
|
| Agent | Default Model | Provider Priority |
|
||||||
| --------------------- | ------------------- | ---------------------------------------------------------------------------- |
|
| --------------------- | ------------------- | ---------------------------------------------------------------------------- |
|
||||||
| **Sisyphus** | `claude-opus-4-6` | `claude-opus-4-6` → `glm-5` → `big-pickle` |
|
| **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) |
|
| **Hephaestus** | `gpt-5.3-codex` | `gpt-5.3-codex` → `gpt-5.4` (GitHub Copilot fallback) |
|
||||||
| **oracle** | `gpt-5.2` | `gpt-5.2` → `gemini-3.1-pro` → `claude-opus-4-6` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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` |
|
| **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-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` |
|
| **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 |
|
| 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`. |
|
| **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. |
|
| **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.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`. |
|
| **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`. |
|
| **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`. |
|
| **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`. |
|
| **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 |
|
| 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`. |
|
| **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`. |
|
| **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
|
### 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 |
|
| Agent | Model | Temp | Mode | Fallback Chain | Purpose |
|
||||||
|-------|-------|------|------|----------------|---------|
|
|-------|-------|------|------|----------------|---------|
|
||||||
| **Sisyphus** | claude-opus-4-6 max | 0.1 | all | glm-5 → big-pickle | Main orchestrator, plans + delegates |
|
| **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 |
|
| **Hephaestus** | gpt-5.3-codex medium | 0.1 | all | gpt-5.4 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 |
|
| **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 |
|
| **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 |
|
| **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 |
|
| **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 |
|
| **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 |
|
| **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) |
|
| **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.
|
* You are the conductor of a symphony of specialized agents.
|
||||||
*
|
*
|
||||||
* Routing:
|
* 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)
|
* 2. Gemini models (google/*, google-vertex/*) → gemini.ts (Gemini-optimized)
|
||||||
* 3. Default (Claude, etc.) → default.ts (Claude-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", () => {
|
it("#given non-Claude model with unspecified-high category #when building #then returns aggressive delegation section", () => {
|
||||||
//#given
|
//#given
|
||||||
const model = "openai/gpt-5.2"
|
const model = "openai/gpt-5.4"
|
||||||
const categories = [unspecifiedHighCategory, otherCategory]
|
const categories = [unspecifiedHighCategory, otherCategory]
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -223,7 +223,7 @@ describe("buildParallelDelegationSection", () => {
|
|||||||
|
|
||||||
it("#given non-Claude model without deep or unspecified-high category #when building #then returns empty", () => {
|
it("#given non-Claude model without deep or unspecified-high category #when building #then returns empty", () => {
|
||||||
//#given
|
//#given
|
||||||
const model = "openai/gpt-5.2"
|
const model = "openai/gpt-5.4"
|
||||||
const categories = [otherCategory]
|
const categories = [otherCategory]
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -261,7 +261,7 @@ describe("buildNonClaudePlannerSection", () => {
|
|||||||
|
|
||||||
it("#given GPT model #when building #then returns plan agent section", () => {
|
it("#given GPT model #when building #then returns plan agent section", () => {
|
||||||
//#given
|
//#given
|
||||||
const model = "openai/gpt-5.2"
|
const model = "openai/gpt-5.4"
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = buildNonClaudePlannerSection(model)
|
const result = buildNonClaudePlannerSection(model)
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ describe("getHephaestusPromptSource", () => {
|
|||||||
|
|
||||||
test("returns 'gpt' for generic GPT models", () => {
|
test("returns 'gpt' for generic GPT models", () => {
|
||||||
// given
|
// given
|
||||||
const model1 = "openai/gpt-5.2";
|
const model1 = "openai/gpt-4o";
|
||||||
const model2 = "github-copilot/gpt-5.2";
|
const model2 = "github-copilot/gpt-4o";
|
||||||
const model3 = "openai/gpt-4o";
|
const model3 = "openai/gpt-4o";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@@ -111,7 +111,7 @@ describe("getHephaestusPrompt", () => {
|
|||||||
|
|
||||||
test("generic GPT model returns generic GPT prompt", () => {
|
test("generic GPT model returns generic GPT prompt", () => {
|
||||||
// given
|
// given
|
||||||
const model = "openai/gpt-5.2";
|
const model = "openai/gpt-4o";
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const prompt = getHephaestusPrompt(model);
|
const prompt = getHephaestusPrompt(model);
|
||||||
|
|||||||
@@ -522,7 +522,7 @@ export function createHephaestusAgent(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
description:
|
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,
|
mode: MODE,
|
||||||
model,
|
model,
|
||||||
maxTokens: 32000,
|
maxTokens: 32000,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export function getPrometheusPromptSource(model?: string): PrometheusPromptSourc
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the appropriate Prometheus prompt based on model.
|
* 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)
|
* Gemini models → Gemini-optimized prompt (aggressive tool-call enforcement, thinking checkpoints)
|
||||||
* Default (Claude, etc.) → Claude-optimized prompt (modular sections)
|
* Default (Claude, etc.) → Claude-optimized prompt (modular sections)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* Category-spawned executor with domain-specific configurations.
|
* Category-spawned executor with domain-specific configurations.
|
||||||
*
|
*
|
||||||
* Routing:
|
* 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)
|
* 2. Gemini models (google/*, google-vertex/*) -> gemini.ts (Gemini-optimized)
|
||||||
* 3. Default (Claude, etc.) -> default.ts (Claude-optimized)
|
* 3. Default (Claude, etc.) -> default.ts (Claude-optimized)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
|||||||
describe("honored fields", () => {
|
describe("honored fields", () => {
|
||||||
test("applies model override", () => {
|
test("applies model override", () => {
|
||||||
// given
|
// given
|
||||||
const override = { model: "openai/gpt-5.2" }
|
const override = { model: "openai/gpt-5.4" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = createSisyphusJuniorAgentWithOverrides(override)
|
const result = createSisyphusJuniorAgentWithOverrides(override)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result.model).toBe("openai/gpt-5.2")
|
expect(result.model).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("applies temperature override", () => {
|
test("applies temperature override", () => {
|
||||||
@@ -105,7 +105,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
|||||||
// given
|
// given
|
||||||
const override = {
|
const override = {
|
||||||
disable: true,
|
disable: true,
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.9,
|
temperature: 0.9,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
|||||||
|
|
||||||
test("useTaskSystem=true produces Task Discipline prompt for GPT", () => {
|
test("useTaskSystem=true produces Task Discipline prompt for GPT", () => {
|
||||||
//#given
|
//#given
|
||||||
const override = { model: "openai/gpt-5.2" }
|
const override = { model: "openai/gpt-5.4" }
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
||||||
@@ -253,7 +253,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
|||||||
|
|
||||||
test("useTaskSystem=true includes task_create/task_update in GPT prompt", () => {
|
test("useTaskSystem=true includes task_create/task_update in GPT prompt", () => {
|
||||||
//#given
|
//#given
|
||||||
const override = { model: "openai/gpt-5.2" }
|
const override = { model: "openai/gpt-5.4" }
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
const result = createSisyphusJuniorAgentWithOverrides(override, undefined, true)
|
||||||
@@ -303,7 +303,7 @@ describe("createSisyphusJuniorAgentWithOverrides", () => {
|
|||||||
|
|
||||||
test("GPT model uses GPT-optimized prompt with Hephaestus-style sections", () => {
|
test("GPT model uses GPT-optimized prompt with Hephaestus-style sections", () => {
|
||||||
// given
|
// given
|
||||||
const override = { model: "openai/gpt-5.2" }
|
const override = { model: "openai/gpt-5.4" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = createSisyphusJuniorAgentWithOverrides(override)
|
const result = createSisyphusJuniorAgentWithOverrides(override)
|
||||||
@@ -401,7 +401,7 @@ describe("getSisyphusJuniorPromptSource", () => {
|
|||||||
|
|
||||||
test("returns 'gpt' for generic GPT models", () => {
|
test("returns 'gpt' for generic GPT models", () => {
|
||||||
// given
|
// given
|
||||||
const model = "openai/gpt-5.2"
|
const model = "openai/gpt-4o"
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const source = getSisyphusJuniorPromptSource(model)
|
const source = getSisyphusJuniorPromptSource(model)
|
||||||
@@ -473,7 +473,7 @@ describe("buildSisyphusJuniorPrompt", () => {
|
|||||||
|
|
||||||
test("generic GPT model uses generic GPT prompt", () => {
|
test("generic GPT model uses generic GPT prompt", () => {
|
||||||
// given
|
// given
|
||||||
const model = "openai/gpt-5.2"
|
const model = "openai/gpt-5.4"
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const prompt = buildSisyphusJuniorPrompt(model, false)
|
const prompt = buildSisyphusJuniorPrompt(model, false)
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ describe("isGpt5_4Model", () => {
|
|||||||
|
|
||||||
test("does not match other GPT models", () => {
|
test("does not match other GPT models", () => {
|
||||||
expect(isGpt5_4Model("openai/gpt-5.3-codex")).toBe(false);
|
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("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", () => {
|
test("does not match non-GPT models", () => {
|
||||||
@@ -26,7 +26,7 @@ describe("isGpt5_4Model", () => {
|
|||||||
|
|
||||||
describe("isGptModel", () => {
|
describe("isGptModel", () => {
|
||||||
test("standard openai provider gpt models", () => {
|
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);
|
expect(isGptModel("openai/gpt-4o")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,22 +39,22 @@ describe("isGptModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("github copilot gpt models", () => {
|
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);
|
expect(isGptModel("github-copilot/gpt-4o")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("litellm proxied gpt models", () => {
|
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);
|
expect(isGptModel("litellm/gpt-4o")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("other proxied gpt models", () => {
|
test("other proxied gpt models", () => {
|
||||||
expect(isGptModel("ollama/gpt-4o")).toBe(true);
|
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", () => {
|
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);
|
expect(isGptModel("venice/gpt-4o")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ describe("isGeminiModel", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("#given gpt models #then returns false", () => {
|
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("openai/o3-mini")).toBe(false);
|
||||||
expect(isGeminiModel("litellm/gpt-4o")).toBe(false);
|
expect(isGeminiModel("litellm/gpt-4o")).toBe(false);
|
||||||
});
|
});
|
||||||
|
|||||||
+30
-30
@@ -39,14 +39,14 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
|
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
|
||||||
// #given
|
// #given
|
||||||
const overrides = {
|
const overrides = {
|
||||||
sisyphus: { model: "github-copilot/gpt-5.2" },
|
sisyphus: { model: "github-copilot/gpt-5.4" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||||
|
|
||||||
// #then
|
// #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.reasoningEffort).toBe("medium")
|
||||||
expect(agents.sisyphus.thinking).toBeUndefined()
|
expect(agents.sisyphus.thinking).toBeUndefined()
|
||||||
})
|
})
|
||||||
@@ -54,9 +54,9 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("Atlas uses uiSelectedModel", async () => {
|
test("Atlas uses uiSelectedModel", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 {
|
try {
|
||||||
// #when
|
// #when
|
||||||
@@ -75,7 +75,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(agents.atlas).toBeDefined()
|
expect(agents.atlas).toBeDefined()
|
||||||
expect(agents.atlas.model).toBe("openai/gpt-5.2")
|
expect(agents.atlas.model).toBe("openai/gpt-5.4")
|
||||||
} finally {
|
} finally {
|
||||||
fetchSpy.mockRestore()
|
fetchSpy.mockRestore()
|
||||||
}
|
}
|
||||||
@@ -84,9 +84,9 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("user config model takes priority over uiSelectedModel for sisyphus", async () => {
|
test("user config model takes priority over uiSelectedModel for sisyphus", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = {
|
const overrides = {
|
||||||
sisyphus: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
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 () => {
|
test("user config model takes priority over uiSelectedModel for atlas", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = {
|
const overrides = {
|
||||||
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
atlas: { model: "google/antigravity-claude-opus-4-5-thinking" },
|
||||||
}
|
}
|
||||||
@@ -173,8 +173,8 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
// #when
|
// #when
|
||||||
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
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)
|
// #then - oracle resolves via connected cache fallback to openai/gpt-5.4 (not system default)
|
||||||
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.reasoningEffort).toBe("medium")
|
||||||
expect(agents.oracle.thinking).toBeUndefined()
|
expect(agents.oracle.thinking).toBeUndefined()
|
||||||
cacheSpy.mockRestore?.()
|
cacheSpy.mockRestore?.()
|
||||||
@@ -196,14 +196,14 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
|
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
|
||||||
// #given
|
// #given
|
||||||
const overrides = {
|
const overrides = {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||||
|
|
||||||
// #then
|
// #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.reasoningEffort).toBe("medium")
|
||||||
expect(agents.oracle.textVerbosity).toBe("high")
|
expect(agents.oracle.textVerbosity).toBe("high")
|
||||||
expect(agents.oracle.thinking).toBeUndefined()
|
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 () => {
|
test("non-model overrides are still applied after factory rebuild", async () => {
|
||||||
// #given
|
// #given
|
||||||
const overrides = {
|
const overrides = {
|
||||||
sisyphus: { model: "github-copilot/gpt-5.2", temperature: 0.5 },
|
sisyphus: { model: "github-copilot/gpt-5.4", temperature: 0.5 },
|
||||||
}
|
}
|
||||||
|
|
||||||
// #when
|
// #when
|
||||||
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
const agents = await createBuiltinAgents([], overrides, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], undefined, undefined)
|
||||||
|
|
||||||
// #then
|
// #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)
|
expect(agents.sisyphus.temperature).toBe(0.5)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
"opencode/kimi-k2.5-free",
|
"opencode/kimi-k2.5-free",
|
||||||
"zai-coding-plan/glm-5",
|
"zai-coding-plan/glm-5",
|
||||||
"opencode/big-pickle",
|
"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 () => {
|
test("excludes hidden custom agents from orchestrator prompts", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = [
|
const customAgentSummaries = [
|
||||||
@@ -334,7 +334,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("excludes disabled custom agents from orchestrator prompts", async () => {
|
test("excludes disabled custom agents from orchestrator prompts", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = [
|
const customAgentSummaries = [
|
||||||
@@ -370,7 +370,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("excludes custom agents when disabledAgents contains their name (case-insensitive)", async () => {
|
test("excludes custom agents when disabledAgents contains their name (case-insensitive)", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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"]
|
const disabledAgents = ["ReSeArChEr"]
|
||||||
@@ -406,7 +406,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("deduplicates custom agents case-insensitively", async () => {
|
test("deduplicates custom agents case-insensitively", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = [
|
const customAgentSummaries = [
|
||||||
@@ -438,7 +438,7 @@ describe("createBuiltinAgents with model overrides", () => {
|
|||||||
test("sanitizes custom agent strings for markdown tables", async () => {
|
test("sanitizes custom agent strings for markdown tables", async () => {
|
||||||
// #given
|
// #given
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 = [
|
const customAgentSummaries = [
|
||||||
@@ -479,7 +479,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
|
|||||||
|
|
||||||
// #then - connected cache enables model resolution despite no systemDefaultModel
|
// #then - connected cache enables model resolution despite no systemDefaultModel
|
||||||
expect(agents.oracle).toBeDefined()
|
expect(agents.oracle).toBeDefined()
|
||||||
expect(agents.oracle.model).toBe("openai/gpt-5.2")
|
expect(agents.oracle.model).toBe("openai/gpt-5.4")
|
||||||
cacheSpy.mockRestore?.()
|
cacheSpy.mockRestore?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -787,7 +787,7 @@ describe("Atlas is unaffected by environment context toggle", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
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 () => {
|
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(
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||||
new Set(["openai/gpt-5.2"])
|
new Set(["openai/gpt-5.4"])
|
||||||
)
|
)
|
||||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue([])
|
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)
|
// #given - user configures a model from a plugin provider (like antigravity)
|
||||||
// that is NOT in the availableModels cache and NOT in the fallback chain
|
// that is NOT in the availableModels cache and NOT in the fallback chain
|
||||||
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
|
||||||
new Set(["openai/gpt-5.2"])
|
new Set(["openai/gpt-5.4"])
|
||||||
)
|
)
|
||||||
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(
|
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(
|
||||||
["openai"]
|
["openai"]
|
||||||
@@ -1021,7 +1021,7 @@ describe("buildAgent with category and skills", () => {
|
|||||||
|
|
||||||
const categories = {
|
const categories = {
|
||||||
"custom-category": {
|
"custom-category": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
variant: "xhigh",
|
variant: "xhigh",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1030,7 +1030,7 @@ describe("buildAgent with category and skills", () => {
|
|||||||
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
|
const agent = buildAgent(source["test-agent"], TEST_MODEL, categories)
|
||||||
|
|
||||||
// #then
|
// #then
|
||||||
expect(agent.model).toBe("openai/gpt-5.2")
|
expect(agent.model).toBe("openai/gpt-5.4")
|
||||||
expect(agent.variant).toBe("xhigh")
|
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"
|
// #given - custom category has reasoningEffort=xhigh, direct override says "low"
|
||||||
const categories = {
|
const categories = {
|
||||||
"test-cat": {
|
"test-cat": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
reasoningEffort: "xhigh" as const,
|
reasoningEffort: "xhigh" as const,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1267,7 +1267,7 @@ describe("override.category expansion in createBuiltinAgents", () => {
|
|||||||
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
|
// #given - custom category has reasoningEffort, no direct reasoningEffort in override
|
||||||
const categories = {
|
const categories = {
|
||||||
"reasoning-cat": {
|
"reasoning-cat": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
reasoningEffort: "high" as const,
|
reasoningEffort: "high" as const,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
|
|||||||
"model": "opencode/glm-4.7-free",
|
"model": "opencode/glm-4.7-free",
|
||||||
},
|
},
|
||||||
"metis": {
|
"metis": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"momus": {
|
"momus": {
|
||||||
@@ -217,7 +217,7 @@ exports[`generateModelConfig single native provider uses OpenAI models when only
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -274,7 +274,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
|
|||||||
"model": "opencode/glm-4.7-free",
|
"model": "opencode/glm-4.7-free",
|
||||||
},
|
},
|
||||||
"metis": {
|
"metis": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"momus": {
|
"momus": {
|
||||||
@@ -286,7 +286,7 @@ exports[`generateModelConfig single native provider uses OpenAI models with isMa
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -476,7 +476,7 @@ exports[`generateModelConfig all native providers uses preferred models from fal
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -551,7 +551,7 @@ exports[`generateModelConfig all native providers uses preferred models with isM
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -627,7 +627,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models when on
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "opencode/gpt-5.2",
|
"model": "opencode/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -702,7 +702,7 @@ exports[`generateModelConfig fallback providers uses OpenCode Zen models with is
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "opencode/gpt-5.2",
|
"model": "opencode/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -773,7 +773,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models when
|
|||||||
"model": "github-copilot/gemini-3-flash-preview",
|
"model": "github-copilot/gemini-3-flash-preview",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "github-copilot/gpt-5.2",
|
"model": "github-copilot/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -839,7 +839,7 @@ exports[`generateModelConfig fallback providers uses GitHub Copilot models with
|
|||||||
"model": "github-copilot/gemini-3-flash-preview",
|
"model": "github-copilot/gemini-3-flash-preview",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "github-copilot/gpt-5.2",
|
"model": "github-copilot/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -1021,7 +1021,7 @@ exports[`generateModelConfig mixed provider scenarios uses Claude + OpenCode Zen
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "opencode/gpt-5.2",
|
"model": "opencode/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -1096,7 +1096,7 @@ exports[`generateModelConfig mixed provider scenarios uses OpenAI + Copilot comb
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -1298,7 +1298,7 @@ exports[`generateModelConfig mixed provider scenarios uses all fallback provider
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "github-copilot/gpt-5.2",
|
"model": "github-copilot/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -1373,7 +1373,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers togethe
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
@@ -1448,7 +1448,7 @@ exports[`generateModelConfig mixed provider scenarios uses all providers with is
|
|||||||
"variant": "medium",
|
"variant": "medium",
|
||||||
},
|
},
|
||||||
"oracle": {
|
"oracle": {
|
||||||
"model": "openai/gpt-5.2",
|
"model": "openai/gpt-5.4",
|
||||||
"variant": "high",
|
"variant": "high",
|
||||||
},
|
},
|
||||||
"prometheus": {
|
"prometheus": {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Examples:
|
|||||||
|
|
||||||
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
|
Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi):
|
||||||
Claude Native anthropic/ models (Opus, Sonnet, Haiku)
|
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)
|
Gemini Native google/ models (Gemini 3 Pro, Flash)
|
||||||
Copilot github-copilot/ models (fallback)
|
Copilot github-copilot/ models (fallback)
|
||||||
OpenCode Zen opencode/ models (opencode/claude-opus-4-6, etc.)
|
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)
|
// #then Sisyphus is omitted (requires all fallback providers)
|
||||||
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
|
expect((result.agents as Record<string, { model: string }>).sisyphus).toBeUndefined()
|
||||||
// #then Oracle should use native OpenAI (first fallback entry)
|
// #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)
|
// #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")
|
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
|
// given: User has override for visual-engineering category
|
||||||
const mockConfig = {
|
const mockConfig = {
|
||||||
categories: {
|
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
|
// then: visual-engineering should show the override
|
||||||
const visual = info.categories.find((c) => c.name === "visual-engineering")
|
const visual = info.categories.find((c) => c.name === "visual-engineering")
|
||||||
expect(visual).toBeDefined()
|
expect(visual).toBeDefined()
|
||||||
expect(visual!.userOverride).toBe("openai/gpt-5.2")
|
expect(visual!.userOverride).toBe("openai/gpt-5.4")
|
||||||
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.2")
|
expect(visual!.effectiveResolution).toBe("User override: openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("shows provider fallback when no override exists", async () => {
|
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
|
//#given User has model with variant override for oracle agent
|
||||||
const mockConfig = {
|
const mockConfig = {
|
||||||
agents: {
|
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
|
//#then Oracle should have userVariant set
|
||||||
const oracle = info.agents.find((a) => a.name === "oracle")
|
const oracle = info.agents.find((a) => a.name === "oracle")
|
||||||
expect(oracle).toBeDefined()
|
expect(oracle).toBeDefined()
|
||||||
expect(oracle!.userOverride).toBe("openai/gpt-5.2")
|
expect(oracle!.userOverride).toBe("openai/gpt-5.4")
|
||||||
expect(oracle!.userVariant).toBe("xhigh")
|
expect(oracle!.userVariant).toBe("xhigh")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function formatConfigSummary(config: InstallConfig): string {
|
|||||||
|
|
||||||
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
const claudeDetail = config.hasClaude ? (config.isMax20 ? "max20" : "standard") : undefined
|
||||||
lines.push(formatProvider("Claude", config.hasClaude, claudeDetail))
|
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("Gemini", config.hasGemini))
|
||||||
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
lines.push(formatProvider("GitHub Copilot", config.hasCopilot, "fallback"))
|
||||||
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
lines.push(formatProvider("OpenCode Zen", config.hasOpencodeZen, "opencode/ models"))
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{
|
{
|
||||||
providers: ["openai", "github-copilot", "opencode"],
|
providers: ["openai", "github-copilot", "opencode"],
|
||||||
model: "gpt-5.2",
|
model: "gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -108,7 +108,7 @@ export const CLI_AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
{ providers: ["kimi-for-coding"], model: "k2p5" },
|
||||||
{
|
{
|
||||||
providers: ["openai", "github-copilot", "opencode"],
|
providers: ["openai", "github-copilot", "opencode"],
|
||||||
model: "gpt-5.2",
|
model: "gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -224,7 +224,7 @@ export const CLI_CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> =
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
providers: ["openai", "github-copilot", "opencode"],
|
providers: ["openai", "github-copilot", "opencode"],
|
||||||
model: "gpt-5.2",
|
model: "gpt-5.4",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
requiresModel: "gemini-3.1-pro",
|
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?",
|
message: "Do you have an OpenAI/ChatGPT Plus subscription?",
|
||||||
options: [
|
options: [
|
||||||
{ value: "no", label: "No", hint: "Oracle will use fallback models" },
|
{ 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,
|
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)?",
|
message: "Do you have access to OpenCode Zen (opencode/ models)?",
|
||||||
options: [
|
options: [
|
||||||
{ value: "no", label: "No", hint: "Will use other configured providers" },
|
{ 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,
|
initialValue: initial.opencodeZen,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ describe("AgentOverrideConfigSchema", () => {
|
|||||||
describe("backward compatibility", () => {
|
describe("backward compatibility", () => {
|
||||||
test("still accepts model field (deprecated)", () => {
|
test("still accepts model field (deprecated)", () => {
|
||||||
// given
|
// given
|
||||||
const config = { model: "openai/gpt-5.2" }
|
const config = { model: "openai/gpt-5.4" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = AgentOverrideConfigSchema.safeParse(config)
|
const result = AgentOverrideConfigSchema.safeParse(config)
|
||||||
@@ -274,14 +274,14 @@ describe("AgentOverrideConfigSchema", () => {
|
|||||||
// then
|
// then
|
||||||
expect(result.success).toBe(true)
|
expect(result.success).toBe(true)
|
||||||
if (result.success) {
|
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)", () => {
|
test("accepts both model and category (deprecated usage)", () => {
|
||||||
// given - category should take precedence at runtime, but both should validate
|
// given - category should take precedence at runtime, but both should validate
|
||||||
const config = {
|
const config = {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
category: "ultrabrain"
|
category: "ultrabrain"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ describe("AgentOverrideConfigSchema", () => {
|
|||||||
// then
|
// then
|
||||||
expect(result.success).toBe(true)
|
expect(result.success).toBe(true)
|
||||||
if (result.success) {
|
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")
|
expect(result.data.category).toBe("ultrabrain")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -343,7 +343,7 @@ describe("AgentOverrideConfigSchema", () => {
|
|||||||
describe("CategoryConfigSchema", () => {
|
describe("CategoryConfigSchema", () => {
|
||||||
test("accepts variant as optional string", () => {
|
test("accepts variant as optional string", () => {
|
||||||
// given
|
// given
|
||||||
const config = { model: "openai/gpt-5.2", variant: "xhigh" }
|
const config = { model: "openai/gpt-5.4", variant: "xhigh" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = CategoryConfigSchema.safeParse(config)
|
const result = CategoryConfigSchema.safeParse(config)
|
||||||
@@ -371,7 +371,7 @@ describe("CategoryConfigSchema", () => {
|
|||||||
|
|
||||||
test("rejects non-string variant", () => {
|
test("rejects non-string variant", () => {
|
||||||
// given
|
// given
|
||||||
const config = { model: "openai/gpt-5.2", variant: 123 }
|
const config = { model: "openai/gpt-5.4", variant: 123 }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = CategoryConfigSchema.safeParse(config)
|
const result = CategoryConfigSchema.safeParse(config)
|
||||||
@@ -413,7 +413,7 @@ describe("Sisyphus-Junior agent override", () => {
|
|||||||
const config = {
|
const config = {
|
||||||
agents: {
|
agents: {
|
||||||
"sisyphus-junior": {
|
"sisyphus-junior": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -426,7 +426,7 @@ describe("Sisyphus-Junior agent override", () => {
|
|||||||
expect(result.success).toBe(true)
|
expect(result.success).toBe(true)
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.data.agents?.["sisyphus-junior"]).toBeDefined()
|
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)
|
expect(result.data.agents?.["sisyphus-junior"]?.temperature).toBe(0.2)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2078,7 +2078,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
|
|||||||
description: "Task 2",
|
description: "Task 2",
|
||||||
prompt: "Do something else",
|
prompt: "Do something else",
|
||||||
agent: "test-agent",
|
agent: "test-agent",
|
||||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||||
parentSessionID: "parent-session",
|
parentSessionID: "parent-session",
|
||||||
parentMessageID: "parent-message",
|
parentMessageID: "parent-message",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ describe("createAnthropicEffortHook", () => {
|
|||||||
const hook = createAnthropicEffortHook()
|
const hook = createAnthropicEffortHook()
|
||||||
const { input, output } = createMockParams({
|
const { input, output } = createMockParams({
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
modelID: "gpt-5.2",
|
modelID: "gpt-5.4",
|
||||||
})
|
})
|
||||||
|
|
||||||
//#when chat.params hook is called
|
//#when chat.params hook is called
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Routing logic:
|
* Routing logic:
|
||||||
* 1. Planner agents (prometheus, plan) → planner.ts
|
* 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
|
* 3. Gemini models → gemini.ts
|
||||||
* 4. Everything else (Claude, etc.) → default.ts
|
* 4. Everything else (Claude, etc.) → default.ts
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ describe("no-sisyphus-gpt hook", () => {
|
|||||||
await hook["chat.message"]?.({
|
await hook["chat.message"]?.({
|
||||||
sessionID: "ses_3",
|
sessionID: "ses_3",
|
||||||
agent: HEPHAESTUS_DISPLAY,
|
agent: HEPHAESTUS_DISPLAY,
|
||||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
model: { providerID: "openai", modelID: "gpt-5.4" },
|
||||||
}, output)
|
}, output)
|
||||||
|
|
||||||
// then - no toast
|
// then - no toast
|
||||||
@@ -126,7 +126,7 @@ describe("no-sisyphus-gpt hook", () => {
|
|||||||
// when - chat.message runs without input.agent
|
// when - chat.message runs without input.agent
|
||||||
await hook["chat.message"]?.({
|
await hook["chat.message"]?.({
|
||||||
sessionID: "ses_4",
|
sessionID: "ses_4",
|
||||||
model: { providerID: "openai", modelID: "gpt-5.2" },
|
model: { providerID: "openai", modelID: "gpt-4o" },
|
||||||
}, output)
|
}, output)
|
||||||
|
|
||||||
// then - toast shown via session-agent fallback
|
// then - toast shown via session-agent fallback
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ describe("runtime-fallback", () => {
|
|||||||
await hook.event({
|
await hook.event({
|
||||||
event: {
|
event: {
|
||||||
type: "session.created",
|
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 () => {
|
test("should trigger fallback for missing API key errors when fallback models are configured", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
const sessionID = "test-session-missing-api-key-fallback"
|
||||||
SessionCategoryRegistry.register(sessionID, "test")
|
SessionCategoryRegistry.register(sessionID, "test")
|
||||||
@@ -230,7 +230,7 @@ describe("runtime-fallback", () => {
|
|||||||
|
|
||||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLog).toBeDefined()
|
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 () => {
|
test("should detect retryable error from message pattern 'rate limit'", async () => {
|
||||||
@@ -260,7 +260,7 @@ describe("runtime-fallback", () => {
|
|||||||
config: createMockConfig({ notify_on_fallback: false }),
|
config: createMockConfig({ notify_on_fallback: false }),
|
||||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"anthropic/claude-opus-4.6",
|
"anthropic/claude-opus-4.6",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
const sessionID = "test-session-model-not-found"
|
const sessionID = "test-session-model-not-found"
|
||||||
@@ -302,7 +302,7 @@ describe("runtime-fallback", () => {
|
|||||||
|
|
||||||
const fallbackLogs = logCalls.filter((c) => c.msg.includes("Preparing fallback"))
|
const fallbackLogs = logCalls.filter((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLogs.length).toBeGreaterThanOrEqual(2)
|
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(
|
const nonRetryLog = logCalls.find(
|
||||||
(c) => c.msg.includes("Error not retryable") && (c.data as { sessionID?: string } | undefined)?.sessionID === sessionID
|
(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 () => {
|
test("should trigger fallback on Copilot auto-retry signal in message.updated", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
const sessionID = "test-session-copilot-auto-retry"
|
||||||
@@ -346,7 +346,7 @@ describe("runtime-fallback", () => {
|
|||||||
|
|
||||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLog).toBeDefined()
|
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 () => {
|
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 () => {
|
test("should trigger fallback when message.updated has missing API key error without model", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
const sessionID = "test-message-updated-missing-model"
|
||||||
SessionCategoryRegistry.register(sessionID, "test")
|
SessionCategoryRegistry.register(sessionID, "test")
|
||||||
@@ -689,7 +689,7 @@ describe("runtime-fallback", () => {
|
|||||||
|
|
||||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLog).toBeDefined()
|
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 () => {
|
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([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/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([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/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([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
]),
|
]),
|
||||||
session_timeout_ms: 20,
|
session_timeout_ms: 20,
|
||||||
}
|
}
|
||||||
@@ -949,7 +949,7 @@ describe("runtime-fallback", () => {
|
|||||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
]),
|
]),
|
||||||
session_timeout_ms: 20,
|
session_timeout_ms: 20,
|
||||||
}
|
}
|
||||||
@@ -1034,7 +1034,7 @@ describe("runtime-fallback", () => {
|
|||||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
]),
|
]),
|
||||||
session_timeout_ms: 20,
|
session_timeout_ms: 20,
|
||||||
}
|
}
|
||||||
@@ -1099,7 +1099,7 @@ describe("runtime-fallback", () => {
|
|||||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"github-copilot/claude-opus-4.6",
|
"github-copilot/claude-opus-4.6",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
]),
|
]),
|
||||||
session_timeout_ms: 20,
|
session_timeout_ms: 20,
|
||||||
}
|
}
|
||||||
@@ -1637,7 +1637,7 @@ describe("runtime-fallback", () => {
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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 () => {
|
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 }),
|
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 () => {
|
test("should apply fallback model on next chat.message after error", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
const sessionID = "test-session-switch"
|
||||||
SessionCategoryRegistry.register(sessionID, "test")
|
SessionCategoryRegistry.register(sessionID, "test")
|
||||||
@@ -1871,13 +1871,13 @@ describe("runtime-fallback", () => {
|
|||||||
output
|
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 () => {
|
test("should notify when fallback occurs", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: true }),
|
config: createMockConfig({ notify_on_fallback: true }),
|
||||||
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.2"]),
|
pluginConfig: createMockPluginConfigWithCategoryFallback(["openai/gpt-5.4"]),
|
||||||
})
|
})
|
||||||
const sessionID = "test-session-notify"
|
const sessionID = "test-session-notify"
|
||||||
SessionCategoryRegistry.register(sessionID, "test")
|
SessionCategoryRegistry.register(sessionID, "test")
|
||||||
@@ -1897,7 +1897,7 @@ describe("runtime-fallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
expect(toastCalls.length).toBe(1)
|
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 input = createMockPluginInput()
|
||||||
const hook = createRuntimeFallbackHook(input, {
|
const hook = createRuntimeFallbackHook(input, {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
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"))
|
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLog).toBeDefined()
|
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 () => {
|
test("should detect agent from sessionID pattern", async () => {
|
||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ notify_on_fallback: false }),
|
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"
|
const sessionID = "sisyphus-session-123"
|
||||||
|
|
||||||
@@ -1966,7 +1966,7 @@ describe("runtime-fallback", () => {
|
|||||||
//#then - should detect sisyphus from sessionID and use its fallback
|
//#then - should detect sisyphus from sessionID and use its fallback
|
||||||
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
const fallbackLog = logCalls.find((c) => c.msg.includes("Preparing fallback"))
|
||||||
expect(fallbackLog).toBeDefined()
|
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 () => {
|
test("should preserve resolved agent during auto-retry", async () => {
|
||||||
@@ -2019,7 +2019,7 @@ describe("runtime-fallback", () => {
|
|||||||
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
const hook = createRuntimeFallbackHook(createMockPluginInput(), {
|
||||||
config: createMockConfig({ cooldown_seconds: 60, notify_on_fallback: false }),
|
config: createMockConfig({ cooldown_seconds: 60, notify_on_fallback: false }),
|
||||||
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
pluginConfig: createMockPluginConfigWithCategoryFallback([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-5",
|
"anthropic/claude-opus-4-5",
|
||||||
]),
|
]),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ describe("createThinkModeHook", () => {
|
|||||||
const input = createHookInput({
|
const input = createHookInput({
|
||||||
sessionID,
|
sessionID,
|
||||||
providerID: "github-copilot",
|
providerID: "github-copilot",
|
||||||
modelID: "gpt-5.2",
|
modelID: "gpt-5.4",
|
||||||
})
|
})
|
||||||
const output = createHookOutput("ultrathink about this")
|
const output = createHookOutput("ultrathink about this")
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ describe("createThinkModeHook", () => {
|
|||||||
expect(output.message.variant).toBe("high")
|
expect(output.message.variant).toBe("high")
|
||||||
expect(output.message.model).toEqual({
|
expect(output.message.model).toEqual({
|
||||||
providerID: "github-copilot",
|
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", () => {
|
it("should handle dots in GPT version numbers", () => {
|
||||||
// given a GPT model ID with dot format (gpt-5.2)
|
// given a GPT model ID with dot format (gpt-5.4)
|
||||||
const variant = getHighVariant("gpt-5.2")
|
const variant = getHighVariant("gpt-5.4")
|
||||||
|
|
||||||
// then should return high variant
|
// 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", () => {
|
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", () => {
|
it("should return null for already-high variants", () => {
|
||||||
// given model IDs that are already high variants
|
// given model IDs that are already high variants
|
||||||
expect(getHighVariant("claude-opus-4-6-high")).toBeNull()
|
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()
|
expect(getHighVariant("gemini-3-1-pro-high")).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -76,20 +76,20 @@ describe("think-mode switcher", () => {
|
|||||||
it("should detect -high suffix", () => {
|
it("should detect -high suffix", () => {
|
||||||
// given model IDs with -high suffix
|
// given model IDs with -high suffix
|
||||||
expect(isAlreadyHighVariant("claude-opus-4-6-high")).toBe(true)
|
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)
|
expect(isAlreadyHighVariant("gemini-3.1-pro-high")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect -high suffix after normalization", () => {
|
it("should detect -high suffix after normalization", () => {
|
||||||
// given model IDs with dots that end in -high
|
// 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", () => {
|
it("should return false for base models", () => {
|
||||||
// given base model IDs without -high suffix
|
// 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("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)
|
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", () => {
|
it("should preserve openai/ prefix when getting high variant", () => {
|
||||||
// given a model ID with openai/ prefix
|
// 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
|
// 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", () => {
|
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", () => {
|
it("should return null for already-high prefixed models", () => {
|
||||||
// given prefixed model IDs that are already high
|
// given prefixed model IDs that are already high
|
||||||
expect(getHighVariant("vertex_ai/claude-opus-4-6-high")).toBeNull()
|
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", () => {
|
it("should detect -high suffix in prefixed models", () => {
|
||||||
// given prefixed model IDs with -high suffix
|
// given prefixed model IDs with -high suffix
|
||||||
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6-high")).toBe(true)
|
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)
|
expect(isAlreadyHighVariant("custom/gemini-3.1-pro-high")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should return false for prefixed base models", () => {
|
it("should return false for prefixed base models", () => {
|
||||||
// given prefixed base model IDs without -high suffix
|
// given prefixed base model IDs without -high suffix
|
||||||
expect(isAlreadyHighVariant("vertex_ai/claude-opus-4-6")).toBe(false)
|
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", () => {
|
it("should handle prefixed models with dots", () => {
|
||||||
// given prefixed model IDs with dots
|
// given prefixed model IDs with dots
|
||||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2")).toBe(false)
|
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4")).toBe(false)
|
||||||
expect(isAlreadyHighVariant("vertex_ai/gpt-5.2-high")).toBe(true)
|
expect(isAlreadyHighVariant("vertex_ai/gpt-5.4-high")).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { normalizeModelID } from "../../shared"
|
|||||||
* @example
|
* @example
|
||||||
* extractModelPrefix("vertex_ai/claude-sonnet-4-6") // { prefix: "vertex_ai/", base: "claude-sonnet-4-6" }
|
* 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("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 } {
|
function extractModelPrefix(modelID: string): { prefix: string; base: string } {
|
||||||
const slashIndex = modelID.indexOf("/")
|
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": "gpt-5-1-codex-high",
|
||||||
"gpt-5-1-codex-mini": "gpt-5-1-codex-mini-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-1-codex-max": "gpt-5-1-codex-max-high",
|
||||||
// GPT-5.2
|
// GPT-5.4
|
||||||
"gpt-5-2": "gpt-5-2-high",
|
"gpt-5-4": "gpt-5-4-high",
|
||||||
"gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
|
"gpt-5-4-chat-latest": "gpt-5-4-chat-latest-high",
|
||||||
"gpt-5-2-pro": "gpt-5-2-pro-high",
|
"gpt-5-4-pro": "gpt-5-4-pro-high",
|
||||||
// Antigravity (Google)
|
// Antigravity (Google)
|
||||||
"antigravity-gemini-3-1-pro": "antigravity-gemini-3-1-pro-high",
|
"antigravity-gemini-3-1-pro": "antigravity-gemini-3-1-pro-high",
|
||||||
"antigravity-gemini-3-flash": "antigravity-gemini-3-flash-high",
|
"antigravity-gemini-3-flash": "antigravity-gemini-3-flash-high",
|
||||||
@@ -97,4 +97,3 @@ export function isAlreadyHighVariant(modelID: string): boolean {
|
|||||||
const { base } = extractModelPrefix(normalized)
|
const { base } = extractModelPrefix(normalized)
|
||||||
return ALREADY_HIGH.has(base) || base.endsWith("-high")
|
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
|
// OpenCode returns assistant messages with flat modelID/providerID, not nested model object
|
||||||
const mockMessagesWithAssistant = [
|
const mockMessagesWithAssistant = [
|
||||||
{ info: { id: "msg-1", role: "user", agent: "sisyphus", model: { providerID: "openai", modelID: "gpt-5.2" } } },
|
{ 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.2", providerID: "openai" } },
|
{ info: { id: "msg-2", role: "assistant", agent: "sisyphus", modelID: "gpt-5.4", providerID: "openai" } },
|
||||||
]
|
]
|
||||||
|
|
||||||
const mockInput = {
|
const mockInput = {
|
||||||
@@ -1390,7 +1390,7 @@ describe("todo-continuation-enforcer", () => {
|
|||||||
|
|
||||||
// then - model should be extracted from assistant message's flat modelID/providerID
|
// then - model should be extracted from assistant message's flat modelID/providerID
|
||||||
expect(promptCalls.length).toBe(1)
|
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" })
|
||||||
})
|
})
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
+18
-18
@@ -12,7 +12,7 @@ describe("mergeConfigs", () => {
|
|||||||
const base = {
|
const base = {
|
||||||
categories: {
|
categories: {
|
||||||
general: {
|
general: {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
},
|
},
|
||||||
quick: {
|
quick: {
|
||||||
@@ -35,7 +35,7 @@ describe("mergeConfigs", () => {
|
|||||||
const result = mergeConfigs(base, override);
|
const result = mergeConfigs(base, override);
|
||||||
|
|
||||||
// then general.model should be preserved from base
|
// 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
|
// then general.temperature should be overridden
|
||||||
expect(result.categories?.general?.temperature).toBe(0.3);
|
expect(result.categories?.general?.temperature).toBe(0.3);
|
||||||
// then quick should be preserved from base
|
// then quick should be preserved from base
|
||||||
@@ -48,7 +48,7 @@ describe("mergeConfigs", () => {
|
|||||||
const base: OhMyOpenCodeConfig = {
|
const base: OhMyOpenCodeConfig = {
|
||||||
categories: {
|
categories: {
|
||||||
general: {
|
general: {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -57,7 +57,7 @@ describe("mergeConfigs", () => {
|
|||||||
|
|
||||||
const result = mergeConfigs(base, override);
|
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", () => {
|
it("should use override categories when base has no categories", () => {
|
||||||
@@ -66,14 +66,14 @@ describe("mergeConfigs", () => {
|
|||||||
const override: OhMyOpenCodeConfig = {
|
const override: OhMyOpenCodeConfig = {
|
||||||
categories: {
|
categories: {
|
||||||
general: {
|
general: {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = mergeConfigs(base, override);
|
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", () => {
|
it("should deep merge agents", () => {
|
||||||
const base: OhMyOpenCodeConfig = {
|
const base: OhMyOpenCodeConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ describe("mergeConfigs", () => {
|
|||||||
|
|
||||||
const result = mergeConfigs(base, override);
|
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?.oracle?.temperature).toBe(0.5);
|
||||||
expect(result.agents?.explore?.model).toBe("anthropic/claude-haiku-4-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", () => {
|
it("should return the full config when everything is valid", () => {
|
||||||
const rawConfig = {
|
const rawConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
momus: { model: "openai/gpt-5.2" },
|
momus: { model: "openai/gpt-5.4" },
|
||||||
},
|
},
|
||||||
disabled_hooks: ["comment-checker"],
|
disabled_hooks: ["comment-checker"],
|
||||||
};
|
};
|
||||||
@@ -136,8 +136,8 @@ describe("parseConfigPartially", () => {
|
|||||||
const result = parseConfigPartially(rawConfig);
|
const result = parseConfigPartially(rawConfig);
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
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!.agents?.momus?.model).toBe("openai/gpt-5.2");
|
expect(result!.agents?.momus?.model).toBe("openai/gpt-5.4");
|
||||||
expect(result!.disabled_hooks).toEqual(["comment-checker"]);
|
expect(result!.disabled_hooks).toEqual(["comment-checker"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -150,8 +150,8 @@ describe("parseConfigPartially", () => {
|
|||||||
it("should preserve valid agent overrides when another section is invalid", () => {
|
it("should preserve valid agent overrides when another section is invalid", () => {
|
||||||
const rawConfig = {
|
const rawConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
momus: { model: "openai/gpt-5.2" },
|
momus: { model: "openai/gpt-5.4" },
|
||||||
prometheus: {
|
prometheus: {
|
||||||
permission: {
|
permission: {
|
||||||
edit: { "*": "ask", ".sisyphus/**": "allow" },
|
edit: { "*": "ask", ".sisyphus/**": "allow" },
|
||||||
@@ -171,7 +171,7 @@ describe("parseConfigPartially", () => {
|
|||||||
it("should preserve valid agents when a non-agent section is invalid", () => {
|
it("should preserve valid agents when a non-agent section is invalid", () => {
|
||||||
const rawConfig = {
|
const rawConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
},
|
},
|
||||||
disabled_hooks: ["not-a-real-hook"],
|
disabled_hooks: ["not-a-real-hook"],
|
||||||
};
|
};
|
||||||
@@ -179,7 +179,7 @@ describe("parseConfigPartially", () => {
|
|||||||
const result = parseConfigPartially(rawConfig);
|
const result = parseConfigPartially(rawConfig);
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
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"]);
|
expect(result!.disabled_hooks).toEqual(["not-a-real-hook"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -224,7 +224,7 @@ describe("parseConfigPartially", () => {
|
|||||||
it("should ignore unknown keys and return valid sections", () => {
|
it("should ignore unknown keys and return valid sections", () => {
|
||||||
const rawConfig = {
|
const rawConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
},
|
},
|
||||||
some_future_key: { foo: "bar" },
|
some_future_key: { foo: "bar" },
|
||||||
};
|
};
|
||||||
@@ -232,7 +232,7 @@ describe("parseConfigPartially", () => {
|
|||||||
const result = parseConfigPartially(rawConfig);
|
const result = parseConfigPartially(rawConfig);
|
||||||
|
|
||||||
expect(result).not.toBeNull();
|
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();
|
expect((result as Record<string, unknown>)["some_future_key"]).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -656,7 +656,7 @@ describe("Prometheus direct override priority over category", () => {
|
|||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
"test-planning": {
|
"test-planning": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
reasoningEffort: "xhigh",
|
reasoningEffort: "xhigh",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -698,7 +698,7 @@ describe("Prometheus direct override priority over category", () => {
|
|||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
"reasoning-cat": {
|
"reasoning-cat": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -739,7 +739,7 @@ describe("Prometheus direct override priority over category", () => {
|
|||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
"temp-cat": {
|
"temp-cat": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.8,
|
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 () => {
|
test("plan agent inherits temperature, reasoningEffort, and other model settings from prometheus", async () => {
|
||||||
//#given - prometheus configured with category that has temperature and reasoningEffort
|
//#given - prometheus configured with category that has temperature and reasoningEffort
|
||||||
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
provenance: "override",
|
provenance: "override",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
})
|
})
|
||||||
@@ -871,7 +871,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
|||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
prometheus: {
|
prometheus: {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
temperature: 0.3,
|
temperature: 0.3,
|
||||||
top_p: 0.9,
|
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>>
|
const agents = config.agent as Record<string, Record<string, unknown>>
|
||||||
expect(agents.plan).toBeDefined()
|
expect(agents.plan).toBeDefined()
|
||||||
expect(agents.plan.mode).toBe("subagent")
|
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.variant).toBe("high")
|
||||||
expect(agents.plan.temperature).toBe(0.3)
|
expect(agents.plan.temperature).toBe(0.3)
|
||||||
expect(agents.plan.top_p).toBe(0.9)
|
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 () => {
|
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({
|
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
|
||||||
model: "anthropic/claude-opus-4-6",
|
model: "anthropic/claude-opus-4-6",
|
||||||
provenance: "provider-fallback",
|
provenance: "provider-fallback",
|
||||||
@@ -926,7 +926,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
|||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
plan: {
|
plan: {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
},
|
},
|
||||||
@@ -950,7 +950,7 @@ describe("Plan agent model inheritance from prometheus", () => {
|
|||||||
|
|
||||||
//#then - plan uses its own override, not prometheus settings
|
//#then - plan uses its own override, not prometheus settings
|
||||||
const agents = config.agent as Record<string, Record<string, unknown>>
|
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.variant).toBe("high")
|
||||||
expect(agents.plan.temperature).toBe(0.5)
|
expect(agents.plan.temperature).toBe(0.5)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ describe("buildPlanDemoteConfig", () => {
|
|||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
}
|
}
|
||||||
const planOverride = {
|
const planOverride = {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
reasoningEffort: "low",
|
reasoningEffort: "low",
|
||||||
@@ -74,7 +74,7 @@ describe("buildPlanDemoteConfig", () => {
|
|||||||
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(result.model).toBe("openai/gpt-5.2")
|
expect(result.model).toBe("openai/gpt-5.4")
|
||||||
expect(result.variant).toBe("high")
|
expect(result.variant).toBe("high")
|
||||||
expect(result.temperature).toBe(0.5)
|
expect(result.temperature).toBe(0.5)
|
||||||
expect(result.reasoningEffort).toBe("low")
|
expect(result.reasoningEffort).toBe("low")
|
||||||
@@ -89,14 +89,14 @@ describe("buildPlanDemoteConfig", () => {
|
|||||||
reasoningEffort: "high",
|
reasoningEffort: "high",
|
||||||
}
|
}
|
||||||
const planOverride = {
|
const planOverride = {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
const result = buildPlanDemoteConfig(prometheusConfig, planOverride)
|
||||||
|
|
||||||
//#then - plan model wins, rest inherits from prometheus
|
//#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.variant).toBe("max")
|
||||||
expect(result.temperature).toBe(0.1)
|
expect(result.temperature).toBe(0.1)
|
||||||
expect(result.reasoningEffort).toBe("high")
|
expect(result.reasoningEffort).toBe("high")
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ describe("Agent Config Integration", () => {
|
|||||||
// given - config with lowercase keys
|
// given - config with lowercase keys
|
||||||
const config = {
|
const config = {
|
||||||
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
sisyphus: { model: "anthropic/claude-opus-4-6" },
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
librarian: { model: "opencode/big-pickle" },
|
librarian: { model: "opencode/big-pickle" },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ describe("Agent Config Integration", () => {
|
|||||||
// given - config with mixed old and new format
|
// given - config with mixed old and new format
|
||||||
const mixedConfig = {
|
const mixedConfig = {
|
||||||
Sisyphus: { model: "anthropic/claude-opus-4-6" },
|
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" },
|
"Prometheus (Planner)": { model: "anthropic/claude-opus-4-6" },
|
||||||
librarian: { model: "opencode/big-pickle" },
|
librarian: { model: "opencode/big-pickle" },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ describe("resolveAgentVariant", () => {
|
|||||||
sisyphus: { category: "ultrabrain" },
|
sisyphus: { category: "ultrabrain" },
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||||
},
|
},
|
||||||
} as OhMyOpenCodeConfig
|
} as OhMyOpenCodeConfig
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ describe("resolveVariantForModel", () => {
|
|||||||
test("returns undefined for provider not in sisyphus chain", () => {
|
test("returns undefined for provider not in sisyphus chain", () => {
|
||||||
// #given openai is not in sisyphus fallback chain anymore
|
// #given openai is not in sisyphus fallback chain anymore
|
||||||
const config = {} as OhMyOpenCodeConfig
|
const config = {} as OhMyOpenCodeConfig
|
||||||
const model = { providerID: "openai", modelID: "gpt-5.2" }
|
const model = { providerID: "openai", modelID: "gpt-5.4" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const variant = resolveVariantForModel(config, "sisyphus", model)
|
const variant = resolveVariantForModel(config, "sisyphus", model)
|
||||||
@@ -191,7 +191,7 @@ describe("resolveVariantForModel", () => {
|
|||||||
test("returns correct variant for oracle agent with openai", () => {
|
test("returns correct variant for oracle agent with openai", () => {
|
||||||
// given
|
// given
|
||||||
const config = {} as OhMyOpenCodeConfig
|
const config = {} as OhMyOpenCodeConfig
|
||||||
const model = { providerID: "openai", modelID: "gpt-5.2" }
|
const model = { providerID: "openai", modelID: "gpt-5.4" }
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const variant = resolveVariantForModel(config, "oracle", model)
|
const variant = resolveVariantForModel(config, "oracle", model)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe("updateConnectedProvidersCache", () => {
|
|||||||
env: [],
|
env: [],
|
||||||
models: {
|
models: {
|
||||||
"gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
|
"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).not.toBeNull()
|
||||||
expect(cache!.connected).toEqual(["openai", "anthropic"])
|
expect(cache!.connected).toEqual(["openai", "anthropic"])
|
||||||
expect(cache!.models).toEqual({
|
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"],
|
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe("parseJsonc", () => {
|
|||||||
const jsonc = `{
|
const jsonc = `{
|
||||||
// This is an example config
|
// This is an example config
|
||||||
"agents": {
|
"agents": {
|
||||||
"oracle": { "model": "openai/gpt-5.2" }, // GPT for strategic reasoning
|
"oracle": { "model": "openai/gpt-5.4" }, // GPT for strategic reasoning
|
||||||
},
|
},
|
||||||
/* Agent overrides */
|
/* Agent overrides */
|
||||||
"disabled_agents": [],
|
"disabled_agents": [],
|
||||||
@@ -118,7 +118,7 @@ describe("parseJsonc", () => {
|
|||||||
}>(jsonc)
|
}>(jsonc)
|
||||||
|
|
||||||
// then
|
// 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([])
|
expect(result.disabled_agents).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ describe("mergeCategories", () => {
|
|||||||
it("allows user to add custom categories", () => {
|
it("allows user to add custom categories", () => {
|
||||||
//#given
|
//#given
|
||||||
const userCategories = {
|
const userCategories = {
|
||||||
"my-custom": { model: "openai/gpt-5.2", description: "Custom category" },
|
"my-custom": { model: "openai/gpt-5.4", description: "Custom category" },
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
@@ -52,13 +52,13 @@ describe("mergeCategories", () => {
|
|||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(result["my-custom"]).toBeDefined()
|
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", () => {
|
it("allows user to disable custom categories", () => {
|
||||||
//#given
|
//#given
|
||||||
const userCategories = {
|
const userCategories = {
|
||||||
"my-custom": { model: "openai/gpt-5.2", disable: true },
|
"my-custom": { model: "openai/gpt-5.4", disable: true },
|
||||||
}
|
}
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe("migrateAgentNames", () => {
|
|||||||
test("preserves current agent names unchanged", () => {
|
test("preserves current agent names unchanged", () => {
|
||||||
// given: Config with current agent names
|
// given: Config with current agent names
|
||||||
const agents = {
|
const agents = {
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
librarian: { model: "google/gemini-3-flash" },
|
librarian: { model: "google/gemini-3-flash" },
|
||||||
explore: { model: "opencode/gpt-5-nano" },
|
explore: { model: "opencode/gpt-5-nano" },
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ describe("migrateAgentNames", () => {
|
|||||||
|
|
||||||
// then: Current names should remain unchanged
|
// then: Current names should remain unchanged
|
||||||
expect(changed).toBe(false)
|
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["librarian"]).toEqual({ model: "google/gemini-3-flash" })
|
||||||
expect(migrated["explore"]).toEqual({ model: "opencode/gpt-5-nano" })
|
expect(migrated["explore"]).toEqual({ model: "opencode/gpt-5-nano" })
|
||||||
})
|
})
|
||||||
@@ -57,7 +57,7 @@ describe("migrateAgentNames", () => {
|
|||||||
const agents = {
|
const agents = {
|
||||||
SISYPHUS: { model: "test" },
|
SISYPHUS: { model: "test" },
|
||||||
"planner-sisyphus": { prompt: "test" },
|
"planner-sisyphus": { prompt: "test" },
|
||||||
"Orchestrator-Sisyphus": { model: "openai/gpt-5.2" },
|
"Orchestrator-Sisyphus": { model: "openai/gpt-5.4" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate agent names
|
// when: Migrate agent names
|
||||||
@@ -66,7 +66,7 @@ describe("migrateAgentNames", () => {
|
|||||||
// then: Case-insensitive lookup should migrate correctly
|
// then: Case-insensitive lookup should migrate correctly
|
||||||
expect(migrated["sisyphus"]).toEqual({ model: "test" })
|
expect(migrated["sisyphus"]).toEqual({ model: "test" })
|
||||||
expect(migrated["prometheus"]).toEqual({ prompt: "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", () => {
|
test("passes through unknown agent names unchanged", () => {
|
||||||
@@ -441,11 +441,11 @@ describe("migrateConfigFile", () => {
|
|||||||
expect(rawConfig.disabled_hooks).toContain("anthropic-context-window-limit-recovery")
|
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
|
// given: Config with old model version in agents
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
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
|
// then: Model version should remain unchanged
|
||||||
expect(needsWrite).toBe(false)
|
expect(needsWrite).toBe(false)
|
||||||
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
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", () => {
|
test("migrates model versions in categories", () => {
|
||||||
@@ -479,7 +479,7 @@ describe("migrateConfigFile", () => {
|
|||||||
// given: Config with current model versions
|
// given: Config with current model versions
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
agents: {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
},
|
},
|
||||||
categories: {
|
categories: {
|
||||||
"my-category": { model: "anthropic/claude-opus-4-6" },
|
"my-category": { model: "anthropic/claude-opus-4-6" },
|
||||||
@@ -514,10 +514,10 @@ describe("migration maps", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("MODEL_VERSION_MAP", () => {
|
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
|
// given/when: Check MODEL_VERSION_MAP
|
||||||
// then: openai/gpt-5.2-codex should not be migrated
|
// then: openai/gpt-5.4-codex should not be migrated
|
||||||
expect(MODEL_VERSION_MAP["openai/gpt-5.2-codex"]).toBeUndefined()
|
expect(MODEL_VERSION_MAP["openai/gpt-5.4-codex"]).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
test("maps anthropic/claude-opus-4-5 to anthropic/claude-opus-4-6", () => {
|
test("maps anthropic/claude-opus-4-5 to anthropic/claude-opus-4-6", () => {
|
||||||
@@ -528,10 +528,10 @@ describe("MODEL_VERSION_MAP", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("migrateModelVersions", () => {
|
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", () => {
|
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.2-codex model
|
// given: Agent config with gpt-5.4-codex model
|
||||||
const agents = {
|
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
|
// when: Migrate model versions
|
||||||
@@ -540,7 +540,7 @@ describe("migrateModelVersions", () => {
|
|||||||
// then: Model should remain unchanged
|
// then: Model should remain unchanged
|
||||||
expect(changed).toBe(false)
|
expect(changed).toBe(false)
|
||||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
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)
|
expect(sisyphus.temperature).toBe(0.1)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -562,7 +562,7 @@ describe("migrateModelVersions", () => {
|
|||||||
test("leaves unknown model strings untouched", () => {
|
test("leaves unknown model strings untouched", () => {
|
||||||
// given: Agent config with unknown model
|
// given: Agent config with unknown model
|
||||||
const agents = {
|
const agents = {
|
||||||
oracle: { model: "openai/gpt-5.2", temperature: 0.5 },
|
oracle: { model: "openai/gpt-5.4", temperature: 0.5 },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate model versions
|
// when: Migrate model versions
|
||||||
@@ -571,7 +571,7 @@ describe("migrateModelVersions", () => {
|
|||||||
// then: Config should remain unchanged
|
// then: Config should remain unchanged
|
||||||
expect(changed).toBe(false)
|
expect(changed).toBe(false)
|
||||||
const oracle = migrated["oracle"] as Record<string, unknown>
|
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", () => {
|
test("handles agent config with no model field", () => {
|
||||||
@@ -605,9 +605,9 @@ describe("migrateModelVersions", () => {
|
|||||||
test("migrates multiple agents in one pass", () => {
|
test("migrates multiple agents in one pass", () => {
|
||||||
// given: Multiple agents with old models
|
// given: Multiple agents with old models
|
||||||
const agents = {
|
const agents = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
prometheus: { model: "anthropic/claude-opus-4-5" },
|
||||||
oracle: { model: "openai/gpt-5.2" },
|
oracle: { model: "openai/gpt-5.4" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate model versions
|
// when: Migrate model versions
|
||||||
@@ -615,9 +615,9 @@ describe("migrateModelVersions", () => {
|
|||||||
|
|
||||||
// then: Only mapped models should be updated
|
// then: Only mapped models should be updated
|
||||||
expect(changed).toBe(true)
|
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["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", () => {
|
test("handles empty object", () => {
|
||||||
@@ -635,9 +635,9 @@ describe("migrateModelVersions", () => {
|
|||||||
test("skips already-applied migrations", () => {
|
test("skips already-applied migrations", () => {
|
||||||
// given: Agent config with old model, but migration already applied
|
// given: Agent config with old model, but migration already applied
|
||||||
const agents = {
|
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
|
// when: Migrate with applied migrations
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
||||||
@@ -646,32 +646,32 @@ describe("migrateModelVersions", () => {
|
|||||||
expect(changed).toBe(false)
|
expect(changed).toBe(false)
|
||||||
expect(newMigrations).toHaveLength(0)
|
expect(newMigrations).toHaveLength(0)
|
||||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
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", () => {
|
test("applies new migrations and records them", () => {
|
||||||
// given: Agent config with old model, no prior migrations
|
// given: Agent config with old model, no prior migrations
|
||||||
const agents = {
|
const agents = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate without applied migrations
|
// when: Migrate without applied migrations
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
|
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(changed).toBe(false)
|
||||||
expect(newMigrations).toEqual([])
|
expect(newMigrations).toEqual([])
|
||||||
const sisyphus = migrated["sisyphus"] as Record<string, unknown>
|
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", () => {
|
test("handles mixed: some applied, some new", () => {
|
||||||
// given: Multiple agents, one migration already applied
|
// given: Multiple agents, one migration already applied
|
||||||
const agents = {
|
const agents = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
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
|
// when: Migrate with partial history
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
const { migrated, changed, newMigrations } = migrateModelVersions(agents, appliedMigrations)
|
||||||
@@ -679,23 +679,23 @@ describe("migrateModelVersions", () => {
|
|||||||
// then: Only prometheus should be migrated
|
// then: Only prometheus should be migrated
|
||||||
expect(changed).toBe(true)
|
expect(changed).toBe(true)
|
||||||
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
|
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")
|
expect((migrated["prometheus"] as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("backward compatible without appliedMigrations param", () => {
|
test("backward compatible without appliedMigrations param", () => {
|
||||||
// given: Agent config with old model, no appliedMigrations param
|
// given: Agent config with old model, no appliedMigrations param
|
||||||
const agents = {
|
const agents = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate without the param (backward compat)
|
// when: Migrate without the param (backward compat)
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(agents)
|
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(changed).toBe(false)
|
||||||
expect(newMigrations).toHaveLength(0)
|
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 configPath = `${tmpDir}/oh-my-opencode.json`
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
agents: {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate config file
|
// when: Migrate config file
|
||||||
const result = migrateConfigFile(configPath, rawConfig)
|
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(result).toBe(false)
|
||||||
expect(rawConfig._migrations).toBeUndefined()
|
expect(rawConfig._migrations).toBeUndefined()
|
||||||
|
|
||||||
@@ -727,9 +727,9 @@ describe("migrateConfigFile _migrations tracking", () => {
|
|||||||
const configPath = `${tmpDir}/oh-my-opencode.json`
|
const configPath = `${tmpDir}/oh-my-opencode.json`
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
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
|
// when: Migrate config file
|
||||||
@@ -738,7 +738,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
|||||||
// then: Should NOT rewrite (model stays as user set it)
|
// then: Should NOT rewrite (model stays as user set it)
|
||||||
// Note: result may be true due to other migrations, but model should NOT change
|
// 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
|
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
|
// cleanup
|
||||||
fs.rmSync(tmpDir, { recursive: true })
|
fs.rmSync(tmpDir, { recursive: true })
|
||||||
@@ -752,7 +752,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
|||||||
agents: {
|
agents: {
|
||||||
prometheus: { model: "anthropic/claude-opus-4-5" },
|
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
|
// when: Migrate config file
|
||||||
@@ -761,7 +761,7 @@ describe("migrateConfigFile _migrations tracking", () => {
|
|||||||
// then: New migration appended, old one preserved
|
// then: New migration appended, old one preserved
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
expect(rawConfig._migrations).toEqual([
|
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",
|
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -825,7 +825,7 @@ describe("migrateAgentConfigToCategory", () => {
|
|||||||
const configs = [
|
const configs = [
|
||||||
{ model: "google/gemini-3.1-pro" },
|
{ model: "google/gemini-3.1-pro" },
|
||||||
{ model: "google/gemini-3-flash" },
|
{ model: "google/gemini-3-flash" },
|
||||||
{ model: "openai/gpt-5.2" },
|
{ model: "openai/gpt-5.4" },
|
||||||
{ model: "anthropic/claude-haiku-4-5" },
|
{ model: "anthropic/claude-haiku-4-5" },
|
||||||
{ model: "anthropic/claude-opus-4-6" },
|
{ model: "anthropic/claude-opus-4-6" },
|
||||||
{ model: "anthropic/claude-sonnet-4-6" },
|
{ model: "anthropic/claude-sonnet-4-6" },
|
||||||
@@ -847,7 +847,7 @@ describe("migrateAgentConfigToCategory", () => {
|
|||||||
test("preserves non-model fields during migration", () => {
|
test("preserves non-model fields during migration", () => {
|
||||||
// given: Config with multiple fields
|
// given: Config with multiple fields
|
||||||
const config = {
|
const config = {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.1,
|
temperature: 0.1,
|
||||||
top_p: 0.95,
|
top_p: 0.95,
|
||||||
maxTokens: 4096,
|
maxTokens: 4096,
|
||||||
@@ -1020,7 +1020,7 @@ describe("migrateConfigFile with backup", () => {
|
|||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
agents: {
|
||||||
"multimodal-looker": { model: "anthropic/claude-haiku-4-5" },
|
"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" },
|
"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>>
|
const agents = rawConfig.agents as Record<string, Record<string, unknown>>
|
||||||
expect(agents["multimodal-looker"].model).toBe("anthropic/claude-haiku-4-5")
|
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")
|
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", () => {
|
test("skips already-applied migrations", () => {
|
||||||
// given: Config with old model and migration already applied
|
// given: Config with old model and migration already applied
|
||||||
const configs = {
|
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
|
// when: Migrate model versions
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
||||||
@@ -1150,32 +1150,32 @@ describe("migrateModelVersions with applied migrations", () => {
|
|||||||
// then: Migration should be skipped (user reverted)
|
// then: Migration should be skipped (user reverted)
|
||||||
expect(changed).toBe(false)
|
expect(changed).toBe(false)
|
||||||
expect(newMigrations).toEqual([])
|
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", () => {
|
test("applies new migrations not in history", () => {
|
||||||
// given: Config with old model, no migration history
|
// given: Config with old model, no migration history
|
||||||
const configs = {
|
const configs = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
}
|
}
|
||||||
const appliedMigrations = new Set<string>()
|
const appliedMigrations = new Set<string>()
|
||||||
|
|
||||||
// when: Migrate model versions
|
// when: Migrate model versions
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
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(changed).toBe(false)
|
||||||
expect(newMigrations).toEqual([])
|
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", () => {
|
test("handles mixed: skip applied, apply new", () => {
|
||||||
// given: Config with 2 old models, 1 already migrated
|
// given: Config with 2 old models, 1 already migrated
|
||||||
const configs = {
|
const configs = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
oracle: { model: "anthropic/claude-opus-4-5" },
|
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
|
// when: Migrate model versions
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
const { migrated, changed, newMigrations } = migrateModelVersions(configs, appliedMigrations)
|
||||||
@@ -1183,29 +1183,29 @@ describe("migrateModelVersions with applied migrations", () => {
|
|||||||
// then: Skip sisyphus (already applied), apply oracle
|
// then: Skip sisyphus (already applied), apply oracle
|
||||||
expect(changed).toBe(true)
|
expect(changed).toBe(true)
|
||||||
expect(newMigrations).toEqual(["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"])
|
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")
|
expect((migrated.oracle as Record<string, unknown>).model).toBe("anthropic/claude-opus-4-6")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("backward compatible: no appliedMigrations param", () => {
|
test("backward compatible: no appliedMigrations param", () => {
|
||||||
// given: Config with old model, no appliedMigrations param (legacy call)
|
// given: Config with old model, no appliedMigrations param (legacy call)
|
||||||
const configs = {
|
const configs = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate model versions (without appliedMigrations)
|
// when: Migrate model versions (without appliedMigrations)
|
||||||
const { migrated, changed, newMigrations } = migrateModelVersions(configs)
|
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(changed).toBe(false)
|
||||||
expect(newMigrations).toEqual([])
|
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", () => {
|
test("returns empty newMigrations when no migrations applied", () => {
|
||||||
// given: Config with no old models
|
// given: Config with no old models
|
||||||
const configs = {
|
const configs = {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
}
|
}
|
||||||
|
|
||||||
// when: Migrate model versions
|
// when: Migrate model versions
|
||||||
@@ -1235,7 +1235,7 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
|||||||
const testConfigPath = "/tmp/test-config-migrations-1.json"
|
const testConfigPath = "/tmp/test-config-migrations-1.json"
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
agents: {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||||
@@ -1244,10 +1244,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
|||||||
// when: Migrate config file
|
// when: Migrate config file
|
||||||
const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
|
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(needsWrite).toBe(false)
|
||||||
expect(rawConfig._migrations).toBeUndefined()
|
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", () => {
|
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 testConfigPath = "/tmp/test-config-migrations-2.json"
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
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))
|
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||||
cleanupPaths.push(testConfigPath)
|
cleanupPaths.push(testConfigPath)
|
||||||
@@ -1267,8 +1267,8 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
|||||||
|
|
||||||
// then: Should not migrate (user reverted)
|
// then: Should not migrate (user reverted)
|
||||||
expect(needsWrite).toBe(false)
|
expect(needsWrite).toBe(false)
|
||||||
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._migrations).toEqual(["model-version:openai/gpt-5.2-codex->openai/gpt-5.3-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", () => {
|
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 testConfigPath = "/tmp/test-config-migrations-3.json"
|
||||||
const rawConfig: Record<string, unknown> = {
|
const rawConfig: Record<string, unknown> = {
|
||||||
agents: {
|
agents: {
|
||||||
sisyphus: { model: "openai/gpt-5.2-codex" },
|
sisyphus: { model: "openai/gpt-5.4-codex" },
|
||||||
oracle: { model: "anthropic/claude-opus-4-5" },
|
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))
|
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
|
||||||
cleanupPaths.push(testConfigPath)
|
cleanupPaths.push(testConfigPath)
|
||||||
@@ -1289,10 +1289,10 @@ describe("migrateConfigFile with _migrations tracking", () => {
|
|||||||
|
|
||||||
// then: Should skip sisyphus, migrate oracle, append to _migrations
|
// then: Should skip sisyphus, migrate oracle, append to _migrations
|
||||||
expect(needsWrite).toBe(true)
|
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.agents as Record<string, Record<string, unknown>>).oracle.model).toBe("anthropic/claude-opus-4-6")
|
||||||
expect(rawConfig._migrations).toEqual([
|
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",
|
"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> = {
|
export const MODEL_TO_CATEGORY_MAP: Record<string, string> = {
|
||||||
"google/gemini-3.1-pro": "visual-engineering",
|
"google/gemini-3.1-pro": "visual-engineering",
|
||||||
"google/gemini-3-flash": "writing",
|
"google/gemini-3-flash": "writing",
|
||||||
"openai/gpt-5.2": "ultrabrain",
|
"openai/gpt-5.4": "ultrabrain",
|
||||||
"anthropic/claude-haiku-4-5": "quick",
|
"anthropic/claude-haiku-4-5": "quick",
|
||||||
"anthropic/claude-opus-4-6": "unspecified-high",
|
"anthropic/claude-opus-4-6": "unspecified-high",
|
||||||
"anthropic/claude-sonnet-4-6": "unspecified-low",
|
"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 () => {
|
it("#given cache file with models #when fetchAvailableModels called with connectedProviders #then returns Set of model IDs", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
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" } } },
|
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).toBeInstanceOf(Set)
|
||||||
expect(result.size).toBe(3)
|
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("anthropic/claude-opus-4-6")).toBe(true)
|
||||||
expect(result.has("google/gemini-3.1-pro")).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 () => {
|
it("#given connectedProviders unknown #when fetchAvailableModels called without options #then returns empty Set", async () => {
|
||||||
writeModelsCache({
|
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()
|
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 () => {
|
it("#given cache read twice #when second call made with same providers #then reads fresh each time", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
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"] })
|
const result2 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] })
|
||||||
|
|
||||||
expect(result1.size).toBe(result2.size)
|
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 () => {
|
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
|
// then return the matching model
|
||||||
it("should match substring in model name", () => {
|
it("should match substring in model name", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"openai/gpt-5.3-codex",
|
"openai/gpt-5.3-codex",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given available model with preview suffix
|
// given available model with preview suffix
|
||||||
@@ -213,12 +213,12 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then return exact match if it exists
|
// then return exact match if it exists
|
||||||
it("should prefer exact match over substring match", () => {
|
it("should prefer exact match over substring match", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"openai/gpt-5.3-codex",
|
"openai/gpt-5.3-codex",
|
||||||
"openai/gpt-5.2-ultra",
|
"openai/gpt-5.4-ultra",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given available models with multiple substring matches
|
// given available models with multiple substring matches
|
||||||
@@ -226,11 +226,11 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then return the shorter model name (more specific)
|
// then return the shorter model name (more specific)
|
||||||
it("should prefer shorter model name when multiple matches exist", () => {
|
it("should prefer shorter model name when multiple matches exist", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2-ultra",
|
"openai/gpt-5.4-ultra",
|
||||||
"openai/gpt-5.2-ultra-mega",
|
"openai/gpt-5.4-ultra-mega",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||||
expect(result).toBe("openai/gpt-5.2-ultra")
|
expect(result).toBe("openai/gpt-5.4-ultra")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given available models with claude variants
|
// given available models with claude variants
|
||||||
@@ -271,12 +271,12 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then only search models from specified providers
|
// then only search models from specified providers
|
||||||
it("should filter by provider when providers array is given", () => {
|
it("should filter by provider when providers array is given", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"google/gemini-3",
|
"google/gemini-3",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
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
|
// given available models from multiple providers
|
||||||
@@ -284,7 +284,7 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then return null
|
// then return null
|
||||||
it("should return null when provider filter excludes all matches", () => {
|
it("should return null when provider filter excludes all matches", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("claude", available, ["openai"])
|
const result = fuzzyMatchModel("claude", available, ["openai"])
|
||||||
@@ -296,7 +296,7 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then return null
|
// then return null
|
||||||
it("should return null when no match found", () => {
|
it("should return null when no match found", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gemini", available)
|
const result = fuzzyMatchModel("gemini", available)
|
||||||
@@ -308,11 +308,11 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then match case-insensitively
|
// then match case-insensitively
|
||||||
it("should match case-insensitively", () => {
|
it("should match case-insensitively", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("GPT-5.2", available)
|
const result = fuzzyMatchModel("GPT-5.4", available)
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given available models with exact match and longer variants
|
// given available models with exact match and longer variants
|
||||||
@@ -356,11 +356,11 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then return shortest full string (preserves tie-break behavior)
|
// then return shortest full string (preserves tie-break behavior)
|
||||||
it("should use shortest tie-break when multiple providers have same model ID", () => {
|
it("should use shortest tie-break when multiple providers have same model ID", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"opencode/gpt-5.2",
|
"opencode/gpt-5.4",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt-5.2", available)
|
const result = fuzzyMatchModel("gpt-5.4", available)
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given available models with multiple providers
|
// given available models with multiple providers
|
||||||
@@ -368,12 +368,12 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then search all specified providers
|
// then search all specified providers
|
||||||
it("should search all specified providers", () => {
|
it("should search all specified providers", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"google/gemini-3",
|
"google/gemini-3",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt", available, ["openai", "google"])
|
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
|
// given available models with provider prefix
|
||||||
@@ -381,11 +381,11 @@ describe("fuzzyMatchModel", () => {
|
|||||||
// then only match models with correct provider prefix
|
// then only match models with correct provider prefix
|
||||||
it("should only match models with correct provider prefix", () => {
|
it("should only match models with correct provider prefix", () => {
|
||||||
const available = new Set([
|
const available = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/gpt-something",
|
"anthropic/gpt-something",
|
||||||
])
|
])
|
||||||
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
const result = fuzzyMatchModel("gpt", available, ["openai"])
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
// given empty available set
|
// given empty available set
|
||||||
@@ -513,7 +513,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
|||||||
// then only returns models from that provider
|
// then only returns models from that provider
|
||||||
it("should filter models by connected providers", async () => {
|
it("should filter models by connected providers", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
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.size).toBe(1)
|
||||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
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)
|
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
|
// then returns models from all specified providers
|
||||||
it("should filter models by multiple connected providers", async () => {
|
it("should filter models by multiple connected providers", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||||
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
|
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.size).toBe(2)
|
||||||
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
expect(result.has("anthropic/claude-opus-4-6")).toBe(true)
|
||||||
expect(result.has("google/gemini-3.1-pro")).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
|
// given cache with models
|
||||||
@@ -553,7 +553,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
|||||||
// then returns empty set
|
// then returns empty set
|
||||||
it("should return empty set when connectedProviders is empty", async () => {
|
it("should return empty set when connectedProviders is empty", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
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)
|
// then returns empty set (triggers fallback in resolver)
|
||||||
it("should return empty set when connectedProviders not specified", async () => {
|
it("should return empty set when connectedProviders not specified", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
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
|
// then returns empty set for that provider
|
||||||
it("should handle provider not in cache gracefully", async () => {
|
it("should handle provider not in cache gracefully", async () => {
|
||||||
writeModelsCache({
|
writeModelsCache({
|
||||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await fetchAvailableModels(undefined, {
|
const result = await fetchAvailableModels(undefined, {
|
||||||
@@ -598,7 +598,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
|||||||
// then returns models only from matching providers
|
// then returns models only from matching providers
|
||||||
it("should return models from providers that exist in both cache and connected list", async () => {
|
it("should return models from providers that exist in both cache and connected list", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
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)
|
// then does NOT use cache (dynamic per-session)
|
||||||
it("should not cache filtered results", async () => {
|
it("should not cache filtered results", async () => {
|
||||||
writeModelsCache({
|
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" } } },
|
anthropic: { models: { "claude-opus-4-6": { id: "claude-opus-4-6" } } },
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -630,7 +630,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
|||||||
connectedProviders: ["openai"]
|
connectedProviders: ["openai"]
|
||||||
})
|
})
|
||||||
expect(result2.size).toBe(1)
|
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
|
// given connectedProviders unknown
|
||||||
@@ -638,7 +638,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
|
|||||||
// then always returns empty set (triggers fallback)
|
// then always returns empty set (triggers fallback)
|
||||||
it("should return empty set when connectedProviders unknown", async () => {
|
it("should return empty set when connectedProviders unknown", async () => {
|
||||||
writeModelsCache({
|
writeModelsCache({
|
||||||
openai: { models: { "gpt-5.2": { id: "gpt-5.2" } } },
|
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } },
|
||||||
})
|
})
|
||||||
|
|
||||||
const result1 = await fetchAvailableModels()
|
const result1 = await fetchAvailableModels()
|
||||||
@@ -696,7 +696,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
|||||||
connected: ["opencode", "anthropic"]
|
connected: ["opencode", "anthropic"]
|
||||||
})
|
})
|
||||||
writeModelsCache({
|
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": {} } }
|
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/big-pickle")).toBe(true)
|
||||||
expect(result.has("opencode/gpt-5-nano")).toBe(true)
|
expect(result.has("opencode/gpt-5-nano")).toBe(true)
|
||||||
expect(result.has("anthropic/claude-opus-4-6")).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)
|
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)
|
// then falls back to models.json (no whitelist filtering)
|
||||||
it("should fallback to models.json when provider-models cache not found", async () => {
|
it("should fallback to models.json when provider-models cache not found", async () => {
|
||||||
writeModelsCache({
|
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, {
|
const result = await fetchAvailableModels(undefined, {
|
||||||
@@ -748,7 +748,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
|
|||||||
expect(result.size).toBe(3)
|
expect(result.size).toBe(3)
|
||||||
expect(result.has("opencode/big-pickle")).toBe(true)
|
expect(result.has("opencode/big-pickle")).toBe(true)
|
||||||
expect(result.has("opencode/gpt-5-nano")).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
|
// given provider-models cache with whitelist
|
||||||
@@ -907,7 +907,7 @@ describe("fallback model availability", () => {
|
|||||||
|
|
||||||
it("returns null for completely unknown model", () => {
|
it("returns null for completely unknown model", () => {
|
||||||
// given
|
// 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
|
// when
|
||||||
const result = fuzzyMatchModel("non-existent-model-family", available)
|
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", () => {
|
it("returns true when models do not match but provider is connected", () => {
|
||||||
// given
|
// 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"])
|
const availableModels = new Set(["anthropic/claude-opus-4-6"])
|
||||||
writeConnectedProvidersCache(["openai"])
|
writeConnectedProvidersCache(["openai"])
|
||||||
|
|
||||||
@@ -932,25 +932,25 @@ describe("fallback model availability", () => {
|
|||||||
it("returns first resolved fallback model from chain", () => {
|
it("returns first resolved fallback model from chain", () => {
|
||||||
// given
|
// given
|
||||||
const fallbackChain = [
|
const fallbackChain = [
|
||||||
{ providers: ["openai"], model: "gpt-5.2" },
|
{ providers: ["openai"], model: "gpt-5.4" },
|
||||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||||
]
|
]
|
||||||
const availableModels = new Set([
|
const availableModels = new Set([
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
"openai/gpt-5.2-preview",
|
"openai/gpt-5.4-preview",
|
||||||
])
|
])
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = resolveFirstAvailableFallback(fallbackChain, availableModels)
|
const result = resolveFirstAvailableFallback(fallbackChain, availableModels)
|
||||||
|
|
||||||
// then
|
// 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", () => {
|
it("returns null when no fallback model resolves", () => {
|
||||||
// given
|
// given
|
||||||
const fallbackChain = [
|
const fallbackChain = [
|
||||||
{ providers: ["openai"], model: "gpt-5.2" },
|
{ providers: ["openai"], model: "gpt-5.4" },
|
||||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||||
]
|
]
|
||||||
const availableModels = new Set(["google/gemini-3.1-pro"])
|
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
|
* 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 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"])
|
* @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
|
* @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.
|
* If providers array is given, only models starting with "provider/" are considered.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* const available = new Set(["openai/gpt-5.2", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
* const available = new Set(["openai/gpt-5.4", "openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"])
|
||||||
* fuzzyMatchModel("gpt-5.2", available) // → "openai/gpt-5.2"
|
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4"
|
||||||
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
* fuzzyMatchModel("claude", available, ["openai"]) // → null (provider filter excludes anthropic)
|
||||||
*/
|
*/
|
||||||
function normalizeModelName(name: string): string {
|
function normalizeModelName(name: string): string {
|
||||||
@@ -82,7 +82,7 @@ export function fuzzyMatchModel(
|
|||||||
|
|
||||||
// Priority 2: Exact model ID match (part after provider/)
|
// 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"
|
// 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 exactModelIdMatches = matches.filter((model) => {
|
||||||
const modelId = model.split("/").slice(1).join("/")
|
const modelId = model.split("/").slice(1).join("/")
|
||||||
return normalizeModelName(modelId) === targetNormalized
|
return normalizeModelName(modelId) === targetNormalized
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ import {
|
|||||||
} from "./model-requirements"
|
} from "./model-requirements"
|
||||||
|
|
||||||
describe("AGENT_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
|
// given - oracle agent requirement
|
||||||
const oracle = AGENT_MODEL_REQUIREMENTS["oracle"]
|
const oracle = AGENT_MODEL_REQUIREMENTS["oracle"]
|
||||||
|
|
||||||
// when - accessing oracle requirement
|
// 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).toBeDefined()
|
||||||
expect(oracle.fallbackChain).toBeArray()
|
expect(oracle.fallbackChain).toBeArray()
|
||||||
expect(oracle.fallbackChain.length).toBeGreaterThan(0)
|
expect(oracle.fallbackChain.length).toBeGreaterThan(0)
|
||||||
|
|
||||||
const primary = oracle.fallbackChain[0]
|
const primary = oracle.fallbackChain[0]
|
||||||
expect(primary.providers).toContain("openai")
|
expect(primary.providers).toContain("openai")
|
||||||
expect(primary.model).toBe("gpt-5.2")
|
expect(primary.model).toBe("gpt-5.4")
|
||||||
expect(primary.variant).toBe("high")
|
expect(primary.variant).toBe("high")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ describe("ModelRequirement type", () => {
|
|||||||
const requirement: ModelRequirement = {
|
const requirement: ModelRequirement = {
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic", "github-copilot"], model: "claude-opus-4-6", variant: "max" },
|
{ 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).toBeArray()
|
||||||
expect(requirement.fallbackChain).toHaveLength(2)
|
expect(requirement.fallbackChain).toHaveLength(2)
|
||||||
expect(requirement.fallbackChain[0].model).toBe("claude-opus-4-6")
|
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", () => {
|
test("ModelRequirement variant is optional", () => {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
model: "gpt-5.3-codex",
|
model: "gpt-5.3-codex",
|
||||||
variant: "medium",
|
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"],
|
requiresProvider: ["openai", "github-copilot", "venice", "opencode"],
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{
|
{
|
||||||
providers: ["openai", "github-copilot", "opencode"],
|
providers: ["openai", "github-copilot", "opencode"],
|
||||||
model: "gpt-5.2",
|
model: "gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -119,7 +119,7 @@ export const AGENT_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
providers: ["openai", "github-copilot", "opencode"],
|
providers: ["openai", "github-copilot", "opencode"],
|
||||||
model: "gpt-5.2",
|
model: "gpt-5.4",
|
||||||
variant: "high",
|
variant: "high",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -226,7 +226,7 @@ export const CATEGORY_MODEL_REQUIREMENTS: Record<string, ModelRequirement> = {
|
|||||||
model: "claude-opus-4-6",
|
model: "claude-opus-4-6",
|
||||||
variant: "max",
|
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",
|
requiresModel: "gemini-3.1-pro",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe("resolveModel", () => {
|
|||||||
// given
|
// given
|
||||||
const input: ModelResolutionInput = {
|
const input: ModelResolutionInput = {
|
||||||
userModel: "anthropic/claude-opus-4-6",
|
userModel: "anthropic/claude-opus-4-6",
|
||||||
inheritedModel: "openai/gpt-5.2",
|
inheritedModel: "openai/gpt-5.4",
|
||||||
systemDefault: "google/gemini-3.1-pro",
|
systemDefault: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ describe("resolveModel", () => {
|
|||||||
// given
|
// given
|
||||||
const input: ModelResolutionInput = {
|
const input: ModelResolutionInput = {
|
||||||
userModel: undefined,
|
userModel: undefined,
|
||||||
inheritedModel: "openai/gpt-5.2",
|
inheritedModel: "openai/gpt-5.4",
|
||||||
systemDefault: "google/gemini-3.1-pro",
|
systemDefault: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ describe("resolveModel", () => {
|
|||||||
const result = resolveModel(input)
|
const result = resolveModel(input)
|
||||||
|
|
||||||
// then
|
// 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", () => {
|
test("returns systemDefault when both userModel and inheritedModel are undefined", () => {
|
||||||
@@ -56,7 +56,7 @@ describe("resolveModel", () => {
|
|||||||
// given
|
// given
|
||||||
const input: ModelResolutionInput = {
|
const input: ModelResolutionInput = {
|
||||||
userModel: "",
|
userModel: "",
|
||||||
inheritedModel: "openai/gpt-5.2",
|
inheritedModel: "openai/gpt-5.4",
|
||||||
systemDefault: "google/gemini-3.1-pro",
|
systemDefault: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ describe("resolveModel", () => {
|
|||||||
const result = resolveModel(input)
|
const result = resolveModel(input)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).toBe("openai/gpt-5.2")
|
expect(result).toBe("openai/gpt-5.4")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("treats whitespace-only string as unset, uses fallback", () => {
|
test("treats whitespace-only string as unset, uses fallback", () => {
|
||||||
@@ -88,7 +88,7 @@ describe("resolveModel", () => {
|
|||||||
// given
|
// given
|
||||||
const input: ModelResolutionInput = {
|
const input: ModelResolutionInput = {
|
||||||
userModel: "anthropic/claude-opus-4-6",
|
userModel: "anthropic/claude-opus-4-6",
|
||||||
inheritedModel: "openai/gpt-5.2",
|
inheritedModel: "openai/gpt-5.4",
|
||||||
systemDefault: "google/gemini-3.1-pro",
|
systemDefault: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,9 +292,9 @@ describe("resolveModelWithFallback", () => {
|
|||||||
// given
|
// given
|
||||||
const input: ExtendedModelResolutionInput = {
|
const input: ExtendedModelResolutionInput = {
|
||||||
fallbackChain: [
|
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",
|
systemDefaultModel: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,7 +302,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
const result = resolveModelWithFallback(input)
|
const result = resolveModelWithFallback(input)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result!.model).toBe("openai/gpt-5.2")
|
expect(result!.model).toBe("openai/gpt-5.4")
|
||||||
expect(result!.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -476,7 +476,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
{ 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",
|
systemDefaultModel: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +592,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
test("returns system default when fallbackChain is not provided", () => {
|
test("returns system default when fallbackChain is not provided", () => {
|
||||||
// given
|
// given
|
||||||
const input: ExtendedModelResolutionInput = {
|
const input: ExtendedModelResolutionInput = {
|
||||||
availableModels: new Set(["openai/gpt-5.2"]),
|
availableModels: new Set(["openai/gpt-5.4"]),
|
||||||
systemDefaultModel: "google/gemini-3.1-pro",
|
systemDefaultModel: "google/gemini-3.1-pro",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +613,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
// when
|
// when
|
||||||
const result = resolveModelWithFallback({
|
const result = resolveModelWithFallback({
|
||||||
fallbackChain: [
|
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" },
|
{ providers: ["anthropic", "github-copilot", "opencode"], model: "claude-opus-4-6", variant: "max" },
|
||||||
],
|
],
|
||||||
availableModels,
|
availableModels,
|
||||||
@@ -632,7 +632,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
// when
|
// when
|
||||||
const result = resolveModelWithFallback({
|
const result = resolveModelWithFallback({
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["openai", "anthropic"], model: "gpt-5.2" },
|
{ providers: ["openai", "anthropic"], model: "gpt-5.4" },
|
||||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||||
],
|
],
|
||||||
availableModels,
|
availableModels,
|
||||||
@@ -647,14 +647,14 @@ describe("resolveModelWithFallback", () => {
|
|||||||
test("returns first matching entry even if later entries have better matches", () => {
|
test("returns first matching entry even if later entries have better matches", () => {
|
||||||
// given
|
// given
|
||||||
const availableModels = new Set([
|
const availableModels = new Set([
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"anthropic/claude-opus-4-6",
|
"anthropic/claude-opus-4-6",
|
||||||
])
|
])
|
||||||
|
|
||||||
// when
|
// when
|
||||||
const result = resolveModelWithFallback({
|
const result = resolveModelWithFallback({
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["openai"], model: "gpt-5.2" },
|
{ providers: ["openai"], model: "gpt-5.4" },
|
||||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||||
],
|
],
|
||||||
availableModels,
|
availableModels,
|
||||||
@@ -662,7 +662,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result!.model).toBe("openai/gpt-5.2")
|
expect(result!.model).toBe("openai/gpt-5.4")
|
||||||
expect(result!.source).toBe("provider-fallback")
|
expect(result!.source).toBe("provider-fallback")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -673,7 +673,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
// when
|
// when
|
||||||
const result = resolveModelWithFallback({
|
const result = resolveModelWithFallback({
|
||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["openai"], model: "gpt-5.2" },
|
{ providers: ["openai"], model: "gpt-5.4" },
|
||||||
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
{ providers: ["anthropic"], model: "claude-opus-4-6" },
|
||||||
{ providers: ["google"], model: "gemini-3.1-pro" },
|
{ providers: ["google"], model: "gemini-3.1-pro" },
|
||||||
],
|
],
|
||||||
@@ -884,7 +884,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
fallbackChain: [
|
fallbackChain: [
|
||||||
{ providers: ["anthropic"], model: "nonexistent-model" },
|
{ providers: ["anthropic"], model: "nonexistent-model" },
|
||||||
],
|
],
|
||||||
availableModels: new Set(["openai/gpt-5.2"]),
|
availableModels: new Set(["openai/gpt-5.4"]),
|
||||||
systemDefaultModel: undefined,
|
systemDefaultModel: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,7 +898,7 @@ describe("resolveModelWithFallback", () => {
|
|||||||
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
|
test("returns undefined when no fallbackChain and systemDefaultModel is undefined", () => {
|
||||||
// given
|
// given
|
||||||
const input: ExtendedModelResolutionInput = {
|
const input: ExtendedModelResolutionInput = {
|
||||||
availableModels: new Set(["openai/gpt-5.2"]),
|
availableModels: new Set(["openai/gpt-5.4"]),
|
||||||
systemDefaultModel: undefined,
|
systemDefaultModel: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ describe("parseModelSuggestion", () => {
|
|||||||
data: {
|
data: {
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
modelID: "gpt-5",
|
modelID: "gpt-5",
|
||||||
suggestions: ["gpt-5.2"],
|
suggestions: ["gpt-5.4"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -82,7 +82,7 @@ describe("parseModelSuggestion", () => {
|
|||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
modelID: "gpt-5",
|
modelID: "gpt-5",
|
||||||
suggestion: "gpt-5.2",
|
suggestion: "gpt-5.4",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ describe("executeSyncContinuation - toast cleanup error paths", () => {
|
|||||||
data: [
|
data: [
|
||||||
{ info: { id: "msg_001", role: "user", time: { created: 1000 }, agent: "oracle" } },
|
{ 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" }],
|
parts: [{ type: "text", text: "Response" }],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const TEST_AVAILABLE_MODELS = new Set([
|
|||||||
"anthropic/claude-haiku-4-5",
|
"anthropic/claude-haiku-4-5",
|
||||||
"google/gemini-3.1-pro",
|
"google/gemini-3.1-pro",
|
||||||
"google/gemini-3-flash",
|
"google/gemini-3-flash",
|
||||||
"openai/gpt-5.2",
|
"openai/gpt-5.4",
|
||||||
"openai/gpt-5.3-codex",
|
"openai/gpt-5.3-codex",
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ describe("sisyphus-task", () => {
|
|||||||
models: {
|
models: {
|
||||||
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6", "claude-haiku-4-5"],
|
||||||
google: ["gemini-3.1-pro", "gemini-3-flash"],
|
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"],
|
connected: ["anthropic", "google", "openai"],
|
||||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||||
@@ -824,7 +824,7 @@ describe("sisyphus-task", () => {
|
|||||||
const categoryName = "my-custom"
|
const categoryName = "my-custom"
|
||||||
const userCategories = {
|
const userCategories = {
|
||||||
"my-custom": {
|
"my-custom": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
prompt_append: "You are a custom agent",
|
prompt_append: "You are a custom agent",
|
||||||
},
|
},
|
||||||
@@ -835,7 +835,7 @@ describe("sisyphus-task", () => {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
expect(result).not.toBeNull()
|
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!.config.temperature).toBe(0.5)
|
||||||
expect(result!.promptAppend).toBe("You are a custom agent")
|
expect(result!.promptAppend).toBe("You are a custom agent")
|
||||||
})
|
})
|
||||||
@@ -948,7 +948,7 @@ describe("sisyphus-task", () => {
|
|||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
userCategories: {
|
userCategories: {
|
||||||
ultrabrain: { model: "openai/gpt-5.2", variant: "xhigh" },
|
ultrabrain: { model: "openai/gpt-5.4", variant: "xhigh" },
|
||||||
},
|
},
|
||||||
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
connectedProvidersOverride: TEST_CONNECTED_PROVIDERS,
|
||||||
availableModelsOverride: createTestAvailableModels(),
|
availableModelsOverride: createTestAvailableModels(),
|
||||||
@@ -976,7 +976,7 @@ describe("sisyphus-task", () => {
|
|||||||
// then
|
// then
|
||||||
expect(launchInput.model).toEqual({
|
expect(launchInput.model).toEqual({
|
||||||
providerID: "openai",
|
providerID: "openai",
|
||||||
modelID: "gpt-5.2",
|
modelID: "gpt-5.4",
|
||||||
variant: "xhigh",
|
variant: "xhigh",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1040,9 +1040,9 @@ describe("sisyphus-task", () => {
|
|||||||
|
|
||||||
// then - variant MUST be "max" from DEFAULT_CATEGORIES
|
// then - variant MUST be "max" from DEFAULT_CATEGORIES
|
||||||
expect(launchInput.model).toEqual({
|
expect(launchInput.model).toEqual({
|
||||||
providerID: "anthropic",
|
providerID: "openai",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "gpt-5.4",
|
||||||
variant: "max",
|
variant: "high",
|
||||||
})
|
})
|
||||||
}, { timeout: 20000 })
|
}, { timeout: 20000 })
|
||||||
|
|
||||||
@@ -1101,10 +1101,10 @@ describe("sisyphus-task", () => {
|
|||||||
|
|
||||||
// then - variant MUST be "max" from DEFAULT_CATEGORIES (passed as separate field)
|
// then - variant MUST be "max" from DEFAULT_CATEGORIES (passed as separate field)
|
||||||
expect(promptBody.model).toEqual({
|
expect(promptBody.model).toEqual({
|
||||||
providerID: "anthropic",
|
providerID: "openai",
|
||||||
modelID: "claude-opus-4-6",
|
modelID: "gpt-5.4",
|
||||||
})
|
})
|
||||||
expect(promptBody.variant).toBe("max")
|
expect(promptBody.variant).toBe("high")
|
||||||
}, { timeout: 20000 })
|
}, { 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({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
@@ -2185,7 +2185,7 @@ describe("sisyphus-task", () => {
|
|||||||
client: mockClient,
|
client: mockClient,
|
||||||
userCategories: {
|
userCategories: {
|
||||||
"my-unstable-cat": {
|
"my-unstable-cat": {
|
||||||
model: "openai/gpt-5.2",
|
model: "openai/gpt-5.4",
|
||||||
is_unstable_agent: true,
|
is_unstable_agent: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2568,7 +2568,7 @@ describe("sisyphus-task", () => {
|
|||||||
const tool = createDelegateTask({
|
const tool = createDelegateTask({
|
||||||
manager: mockManager,
|
manager: mockManager,
|
||||||
client: mockClient,
|
client: mockClient,
|
||||||
sisyphusJuniorModel: "openai/gpt-5.2",
|
sisyphusJuniorModel: "openai/gpt-5.4",
|
||||||
userCategories: {
|
userCategories: {
|
||||||
"my-custom": { temperature: 0.5 },
|
"my-custom": { temperature: 0.5 },
|
||||||
},
|
},
|
||||||
@@ -2595,7 +2595,7 @@ describe("sisyphus-task", () => {
|
|||||||
|
|
||||||
// then - sisyphus-junior override model should be used as fallback
|
// then - sisyphus-junior override model should be used as fallback
|
||||||
expect(launchInput.model.providerID).toBe("openai")
|
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: {
|
app: {
|
||||||
agents: async () => ({
|
agents: async () => ({
|
||||||
data: [
|
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: {
|
app: {
|
||||||
agents: async () => ({
|
agents: async () => ({
|
||||||
data: [
|
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
|
// 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)
|
// 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.2
|
// Since openai is in connectedProviders, should resolve to openai/gpt-5.4
|
||||||
expect(promptBody.model).toBeDefined()
|
expect(promptBody.model).toBeDefined()
|
||||||
expect(promptBody.model.providerID).toBe("openai")
|
expect(promptBody.model.providerID).toBe("openai")
|
||||||
expect(promptBody.model.modelID).toContain("gpt-5.2")
|
expect(promptBody.model.modelID).toContain("gpt-5.4")
|
||||||
}, { timeout: 20000 })
|
}, { timeout: 20000 })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user