Merge pull request #3486 from code-yeongyu/chore/bump-claude-opus-4-7

refactor(models): bump claude-opus-4-6 to claude-opus-4-7 across fallback chains, categories, and docs
This commit is contained in:
YeonGyu-Kim
2026-04-17 15:42:38 +09:00
committed by GitHub
109 changed files with 40043 additions and 38253 deletions
+1 -1
View File
@@ -183,7 +183,7 @@ import type { AgentConfig } from "./types";
export const myAgent: AgentConfig = { export const myAgent: AgentConfig = {
name: "my-agent", name: "my-agent",
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
description: "Description of what this agent does", description: "Description of what this agent does",
prompt: `Your agent's system prompt here`, prompt: `Your agent's system prompt here`,
temperature: 0.1, temperature: 0.1,
+2 -2
View File
@@ -170,11 +170,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
<td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td>
</tr></table> </tr></table>
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) はあなたのメインのオーケストレーターです。計画を立て、専門家に委任し、攻撃的な並列実行でタスクを完了まで推進します。途中で投げ出すことはありません。 **Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) はあなたのメインのオーケストレーターです。計画を立て、専門家に委任し、攻撃的な並列実行でタスクを完了まで推進します。途中で投げ出すことはありません。
**Hephaestus** (`gpt-5.4`) はあなたの自律的なディープワーカーです。レシピではなく、目標を与えてください。手取り足取り教えなくても、コードベースを探索し、パターンを研究し、端から端まで実行します。*正当なる職人 (The Legitimate Craftsman).* **Hephaestus** (`gpt-5.4`) はあなたの自律的なディープワーカーです。レシピではなく、目標を与えてください。手取り足取り教えなくても、コードベースを探索し、パターンを研究し、端から端まで実行します。*正当なる職人 (The Legitimate Craftsman).*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) はあなたの戦略プランナーです。インタビューモードで動作し、コードに触れる前に質問をしてスコープを特定し、詳細な計画を構築します。 **Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) はあなたの戦略プランナーです。インタビューモードで動作し、コードに触れる前に質問をしてスコープを特定し、詳細な計画を構築します。
すべてのエージェントは、それぞれのモデルの強みに合わせてチューニングされています。手動でモデルを切り替える必要はありません。[詳しくはこちら →](docs/guide/overview.md) すべてのエージェントは、それぞれのモデルの強みに合わせてチューニングされています。手動でモデルを切り替える必要はありません。[詳しくはこちら →](docs/guide/overview.md)
+2 -2
View File
@@ -164,11 +164,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
<td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td>
</tr></table> </tr></table>
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 메인 오케스트레이터입니다. 공격적인 병렬 실행으로 계획을 세우고, 전문가들에게 위임하며, 완료될 때까지 밀어붙입니다. 중간에 포기하는 법이 없습니다. **Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 메인 오케스트레이터입니다. 공격적인 병렬 실행으로 계획을 세우고, 전문가들에게 위임하며, 완료될 때까지 밀어붙입니다. 중간에 포기하는 법이 없습니다.
**Hephaestus** (`gpt-5.4`)는 당신의 자율 딥 워커입니다. 레시피가 아니라 목표를 주세요. 베이비시터 없이 알아서 코드베이스를 탐색하고, 패턴을 연구하며, 끝에서 끝까지 전부 해냅니다. *진정한 장인(The Legitimate Craftsman).* **Hephaestus** (`gpt-5.4`)는 당신의 자율 딥 워커입니다. 레시피가 아니라 목표를 주세요. 베이비시터 없이 알아서 코드베이스를 탐색하고, 패턴을 연구하며, 끝에서 끝까지 전부 해냅니다. *진정한 장인(The Legitimate Craftsman).*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 전략 플래너입니다. 인터뷰 모드로 작동합니다. 코드 한 줄 만지기 전에 질문을 던져 스코프를 파악하고 상세한 계획부터 세웁니다. **Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**)는 당신의 전략 플래너입니다. 인터뷰 모드로 작동합니다. 코드 한 줄 만지기 전에 질문을 던져 스코프를 파악하고 상세한 계획부터 세웁니다.
모든 에이전트는 해당 모델의 특장점에 맞춰 튜닝되어 있습니다. 수동으로 모델 바꿔가며 뻘짓하지 마세요. [더 알아보기 →](docs/guide/overview.md) 모든 에이전트는 해당 모델의 특장점에 맞춰 튜닝되어 있습니다. 수동으로 모델 바꿔가며 뻘짓하지 마세요. [더 알아보기 →](docs/guide/overview.md)
+2 -2
View File
@@ -166,11 +166,11 @@ Even only with following subscriptions, ultrawork will work well (this project i
<td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td>
</tr></table> </tr></table>
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`** ) is your main orchestrator. He plans, delegates to specialists, and drives tasks to completion with aggressive parallel execution. He does not stop halfway. **Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`** ) is your main orchestrator. He plans, delegates to specialists, and drives tasks to completion with aggressive parallel execution. He does not stop halfway.
**Hephaestus** (`gpt-5.4`) is your autonomous deep worker. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. *The Legitimate Craftsman.* **Hephaestus** (`gpt-5.4`) is your autonomous deep worker. Give him a goal, not a recipe. He explores the codebase, researches patterns, and executes end-to-end without hand-holding. *The Legitimate Craftsman.*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`** ) is your strategic planner. Interview mode: it questions, identifies scope, and builds a detailed plan before a single line of code is touched. **Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`** ) is your strategic planner. Interview mode: it questions, identifies scope, and builds a detailed plan before a single line of code is touched.
Every agent is tuned to its model's specific strengths. No manual model-juggling. [Learn more →](docs/guide/overview.md) Every agent is tuned to its model's specific strengths. No manual model-juggling. [Learn more →](docs/guide/overview.md)
+2 -2
View File
@@ -154,11 +154,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
<table><tr> <td align="center"><img src=".github/assets/sisyphus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> </tr></table> <table><tr> <td align="center"><img src=".github/assets/sisyphus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> </tr></table>
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — главный оркестратор. Он планирует, делегирует задачи специалистам и доводит их до завершения с агрессивным параллельным выполнением. Он не останавливается на полпути. **Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) — главный оркестратор. Он планирует, делегирует задачи специалистам и доводит их до завершения с агрессивным параллельным выполнением. Он не останавливается на полпути.
**Hephaestus** (`gpt-5.4`) — автономный глубокий исполнитель. Дайте ему цель, а не рецепт. Он исследует кодовую базу, изучает паттерны и выполняет задачи сквозным образом без лишних подсказок. *Законный Мастер.* **Hephaestus** (`gpt-5.4`) — автономный глубокий исполнитель. Дайте ему цель, а не рецепт. Он исследует кодовую базу, изучает паттерны и выполняет задачи сквозным образом без лишних подсказок. *Законный Мастер.*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) — стратегический планировщик. Режим интервью: задаёт вопросы, определяет объём работ и формирует детальный план до того, как написана хотя бы одна строка кода. **Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) — стратегический планировщик. Режим интервью: задаёт вопросы, определяет объём работ и формирует детальный план до того, как написана хотя бы одна строка кода.
Каждый агент настроен под сильные стороны своей модели. Никакого ручного переключения между моделями. Подробнее → Каждый агент настроен под сильные стороны своей модели. Никакого ручного переключения между моделями. Подробнее →
+2 -2
View File
@@ -171,11 +171,11 @@ Read this and tell me why it's not just another boilerplate: https://raw.githubu
<td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td> <td align="center"><img src=".github/assets/hephaestus.png" height="300" /></td>
</tr></table> </tr></table>
**Sisyphus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) 是你的主指挥官。他负责制定计划、分配任务给专家团队,并以极其激进的并行策略推动任务直至完成。他从不半途而废。 **Sisyphus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) 是你的主指挥官。他负责制定计划、分配任务给专家团队,并以极其激进的并行策略推动任务直至完成。他从不半途而废。
**Hephaestus** (`gpt-5.4`) 是你的自主深度工作者。你只需要给他目标,不要给他具体做法。他会自动探索代码库模式,从头到尾独立执行任务,绝不会中途要你当保姆。*名副其实的正牌工匠。* **Hephaestus** (`gpt-5.4`) 是你的自主深度工作者。你只需要给他目标,不要给他具体做法。他会自动探索代码库模式,从头到尾独立执行任务,绝不会中途要你当保姆。*名副其实的正牌工匠。*
**Prometheus** (`claude-opus-4-6` / **`kimi-k2.5`** / **`glm-5`**) 是你的战略规划师。他通过访谈模式,在动一行代码之前,先通过提问确定范围并构建详尽的执行计划。 **Prometheus** (`claude-opus-4-7` / **`kimi-k2.5`** / **`glm-5`**) 是你的战略规划师。他通过访谈模式,在动一行代码之前,先通过提问确定范围并构建详尽的执行计划。
每一个 Agent 都针对其底层模型的特点进行了专门调优。你无需手动来回切换模型。[阅读背景设定了解更多 →](docs/guide/overview.md) 每一个 Agent 都针对其底层模型的特点进行了专门调优。你无需手动来回切换模型。[阅读背景设定了解更多 →](docs/guide/overview.md)
+1 -1
View File
@@ -8,7 +8,7 @@
// Primary orchestrator: aggressive parallel delegation // Primary orchestrator: aggressive parallel delegation
"sisyphus": { "sisyphus": {
"model": "kimi-for-coding/k2p5", "model": "kimi-for-coding/k2p5",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
"prompt_append": "Delegate heavily to hephaestus for implementation. Parallelize exploration.", "prompt_append": "Delegate heavily to hephaestus for implementation. Parallelize exploration.",
}, },
+3 -3
View File
@@ -7,8 +7,8 @@
"agents": { "agents": {
// Main orchestrator: handles delegation and drives tasks to completion // Main orchestrator: handles delegation and drives tasks to completion
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
}, },
// Deep autonomous worker: end-to-end implementation // Deep autonomous worker: end-to-end implementation
@@ -50,7 +50,7 @@
"categories": { "categories": {
"quick": { "model": "opencode/gpt-5-nano" }, "quick": { "model": "opencode/gpt-5-nano" },
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" }, "unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "unspecified-high": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
"writing": { "model": "google/gemini-3-flash" }, "writing": { "model": "google/gemini-3-flash" },
"visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" }, "visual-engineering": { "model": "google/gemini-3.1-pro", "variant": "high" },
"deep": { "model": "openai/gpt-5.4" }, "deep": { "model": "openai/gpt-5.4" },
+5 -5
View File
@@ -7,8 +7,8 @@
"agents": { "agents": {
// Orchestrator: delegates to planning agents first // Orchestrator: delegates to planning agents first
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
"prompt_append": "Always consult prometheus and atlas for planning. Never rush to implementation.", "prompt_append": "Always consult prometheus and atlas for planning. Never rush to implementation.",
}, },
@@ -20,7 +20,7 @@
// Primary planner: deep interview mode // Primary planner: deep interview mode
"prometheus": { "prometheus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"thinking": { "type": "enabled", "budgetTokens": 160000 }, "thinking": { "type": "enabled", "budgetTokens": 160000 },
"prompt_append": "Interview extensively. Question assumptions. Build exhaustive plans with milestones, risks, and contingencies. Use deep & quick agents heavily in parallel for research.", "prompt_append": "Interview extensively. Question assumptions. Build exhaustive plans with milestones, risks, and contingencies. Use deep & quick agents heavily in parallel for research.",
}, },
@@ -43,7 +43,7 @@
// Plan review and refinement: heavily utilized // Plan review and refinement: heavily utilized
"metis": { "metis": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"prompt_append": "Critically evaluate plans. Identify gaps, risks, and improvements. Be thorough.", "prompt_append": "Critically evaluate plans. Identify gaps, risks, and improvements. Be thorough.",
}, },
@@ -98,7 +98,7 @@
"openai": 3, "openai": 3,
}, },
"modelConcurrency": { "modelConcurrency": {
"anthropic/claude-opus-4-6": 2, "anthropic/claude-opus-4-7": 2,
"openai/gpt-5.4": 2, "openai/gpt-5.4": 2,
}, },
}, },
+14 -14
View File
@@ -64,8 +64,8 @@ These agents have Claude-optimized prompts — long, detailed, mechanics-driven.
| Agent | Role | Fallback Chain | Notes | | Agent | Role | Fallback Chain | Notes |
| ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- | | ------------ | ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Sisyphus** | Main orchestrator | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → opencode-go\|vercel/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix\|vercel/kimi-k2.5 → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (medium) → zai-coding-plan\|opencode\|vercel/glm-5 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Sisyphus** | Main orchestrator | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → opencode-go\|vercel/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix\|vercel/kimi-k2.5 → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (medium) → zai-coding-plan\|opencode\|vercel/glm-5 → opencode/big-pickle | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan gap analyzer | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → opencode-go\|vercel/glm-5 → kimi-for-coding/k2p5 | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Metis** | Plan gap analyzer | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → opencode-go\|vercel/glm-5 → kimi-for-coding/k2p5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Dual-Prompt Agents → Claude preferred, GPT supported ### Dual-Prompt Agents → Claude preferred, GPT supported
@@ -73,7 +73,7 @@ These agents ship separate prompts for Claude and GPT families. They auto-detect
| Agent | Role | Fallback Chain | Notes | | Agent | Role | Fallback Chain | Notes |
| -------------- | ----------------- | -------------------------------------- | -------------------------------------------------------------------- | | -------------- | ----------------- | -------------------------------------- | -------------------------------------------------------------------- |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → opencode-go\|vercel/glm-5 → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → opencode-go\|vercel/glm-5 → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → opencode-go\|vercel/kimi-k2.5 → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (medium) → opencode-go\|vercel/minimax-m2.7 | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → opencode-go\|vercel/kimi-k2.5 → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (medium) → opencode-go\|vercel/minimax-m2.7 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Deep Specialists → GPT ### Deep Specialists → GPT
@@ -83,8 +83,8 @@ 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 | openai\|github-copilot\|venice\|opencode\|vercel/gpt-5.4 (medium) | Single-entry chain. Requires one of those providers. The craftsman. | | **Hephaestus** | Autonomous deep worker | openai\|github-copilot\|venice\|opencode\|vercel/gpt-5.4 (medium) | Single-entry chain. Requires one of those providers. The craftsman. |
| **Oracle** | Architecture consultant | openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → opencode-go\|vercel/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Oracle** | Architecture consultant | openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → opencode-go\|vercel/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Momus** | Ruthless reviewer | openai\|github-copilot\|opencode\|vercel/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → opencode-go\|vercel/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. | | **Momus** | Ruthless reviewer | openai\|github-copilot\|opencode\|vercel/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → opencode-go\|vercel/glm-5 | Exact runtime chain from `src/shared/model-requirements.ts`. |
### Utility Runners → Speed over Intelligence ### Utility Runners → Speed over Intelligence
@@ -107,7 +107,7 @@ Communicative, instruction-following, structured output. Best for agents that ne
| Model | Strengths | | Model | Strengths |
| --------------------- | ---------------------------------------------------------------------------- | | --------------------- | ---------------------------------------------------------------------------- |
| **Claude Opus 4.6** | Best overall. Highest compliance with complex prompts. Default for Sisyphus. | | **Claude Opus 4.7** | Best overall. Highest compliance with complex prompts. Default for Sisyphus. |
| **Claude Sonnet 4.6** | Faster, cheaper. Good balance for everyday tasks. | | **Claude Sonnet 4.6** | Faster, cheaper. Good balance for everyday tasks. |
| **Claude Haiku 4.5** | Fast and cheap. Good for quick tasks and utility work. | | **Claude Haiku 4.5** | Fast and cheap. Good for quick tasks and utility work. |
| **Kimi K2.5** | Behaves very similarly to Claude. Great all-rounder at lower cost. | | **Kimi K2.5** | Behaves very similarly to Claude. Great all-rounder at lower cost. |
@@ -169,12 +169,12 @@ When agents delegate work, they don't pick a model name — they pick a **catego
| Category | When Used | Fallback Chain | | Category | When Used | Fallback Chain |
| -------------------- | -------------------------- | -------------------------------------------- | | -------------------- | -------------------------- | -------------------------------------------- |
| `visual-engineering` | Frontend, UI, CSS, design | google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → zai-coding-plan\|opencode\|vercel/glm-5 → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → opencode-go\|vercel/glm-5 → kimi-for-coding/k2p5 | | `visual-engineering` | Frontend, UI, CSS, design | google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → zai-coding-plan\|opencode\|vercel/glm-5 → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → opencode-go\|vercel/glm-5 → kimi-for-coding/k2p5 |
| `ultrabrain` | Maximum reasoning needed | openai\|opencode\|vercel/gpt-5.4 (xhigh) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → opencode-go\|vercel/glm-5 | | `ultrabrain` | Maximum reasoning needed | openai\|opencode\|vercel/gpt-5.4 (xhigh) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → opencode-go\|vercel/glm-5 |
| `deep` | Deep coding, complex logic | openai\|github-copilot\|venice\|opencode\|vercel/gpt-5.4 (medium) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) | | `deep` | Deep coding, complex logic | openai\|github-copilot\|venice\|opencode\|vercel/gpt-5.4 (medium) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) |
| `artistry` | Creative, novel approaches | google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 | | `artistry` | Creative, novel approaches | google\|github-copilot\|opencode\|vercel/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 |
| `quick` | Simple, fast tasks | openai\|github-copilot\|opencode\|vercel/gpt-5.4-mini → anthropic\|github-copilot\|opencode\|vercel/claude-haiku-4-5 → google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/minimax-m2.7 → opencode\|vercel/gpt-5-nano | | `quick` | Simple, fast tasks | openai\|github-copilot\|opencode\|vercel/gpt-5.4-mini → anthropic\|github-copilot\|opencode\|vercel/claude-haiku-4-5 → google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/minimax-m2.7 → opencode\|vercel/gpt-5-nano |
| `unspecified-high` | General complex work | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-6 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → zai-coding-plan\|opencode\|vercel/glm-5 → kimi-for-coding/k2p5 → opencode-go\|vercel/glm-5 → opencode\|vercel/kimi-k2.5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix\|vercel/kimi-k2.5 | | `unspecified-high` | General complex work | anthropic\|github-copilot\|opencode\|vercel/claude-opus-4-7 (max) → openai\|github-copilot\|opencode\|vercel/gpt-5.4 (high) → zai-coding-plan\|opencode\|vercel/glm-5 → kimi-for-coding/k2p5 → opencode-go\|vercel/glm-5 → opencode\|vercel/kimi-k2.5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix\|vercel/kimi-k2.5 |
| `unspecified-low` | General standard work | anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → openai\|opencode\|vercel/gpt-5.3-codex (medium) → opencode-go\|vercel/kimi-k2.5 → google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/minimax-m2.7 | | `unspecified-low` | General standard work | anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → openai\|opencode\|vercel/gpt-5.3-codex (medium) → opencode-go\|vercel/kimi-k2.5 → google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/minimax-m2.7 |
| `writing` | Text, docs, prose | google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/kimi-k2.5 → anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → opencode-go\|vercel/minimax-m2.7 | | `writing` | Text, docs, prose | google\|github-copilot\|opencode\|vercel/gemini-3-flash → opencode-go\|vercel/kimi-k2.5 → anthropic\|github-copilot\|opencode\|vercel/claude-sonnet-4-6 → opencode-go\|vercel/minimax-m2.7 |
@@ -198,7 +198,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch
// Main orchestrator: Claude Opus or Kimi K2.5 work best // Main orchestrator: Claude Opus or Kimi K2.5 work best
"sisyphus": { "sisyphus": {
"model": "kimi-for-coding/k2p5", "model": "kimi-for-coding/k2p5",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
}, },
// Research agents: cheaper models are fine // Research agents: cheaper models are fine
@@ -217,7 +217,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch
"categories": { "categories": {
"quick": { "model": "opencode/gpt-5-nano" }, "quick": { "model": "opencode/gpt-5-nano" },
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" }, "unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "unspecified-high": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
"visual-engineering": { "visual-engineering": {
"model": "google/gemini-3.1-pro", "model": "google/gemini-3.1-pro",
"variant": "high", "variant": "high",
@@ -234,7 +234,7 @@ See the [Orchestration System Guide](./orchestration.md) for how agents dispatch
"zai-coding-plan": 10, "zai-coding-plan": 10,
}, },
"modelConcurrency": { "modelConcurrency": {
"anthropic/claude-opus-4-6": 2, "anthropic/claude-opus-4-7": 2,
"opencode/gpt-5-nano": 20, "opencode/gpt-5-nano": 20,
}, },
}, },
+10 -10
View File
@@ -225,7 +225,7 @@ When GitHub Copilot is the best available provider, install-time defaults are ag
| Agent | Model | | Agent | Model |
| ------------- | ---------------------------------- | | ------------- | ---------------------------------- |
| **Sisyphus** | `github-copilot/claude-opus-4.6` | | **Sisyphus** | `github-copilot/claude-opus-4.7` |
| **Oracle** | `github-copilot/gpt-5.4` | | **Oracle** | `github-copilot/gpt-5.4` |
| **Explore** | `github-copilot/grok-code-fast-1` | | **Explore** | `github-copilot/grok-code-fast-1` |
| **Atlas** | `github-copilot/claude-sonnet-4.6` | | **Atlas** | `github-copilot/claude-sonnet-4.6` |
@@ -247,13 +247,13 @@ If Z.ai is your main provider, the most important fallbacks are:
#### OpenCode Zen #### OpenCode Zen
OpenCode Zen provides access to `opencode/` prefixed models including `opencode/claude-opus-4-6`, `opencode/gpt-5.4`, `opencode/gpt-5.3-codex`, `opencode/gpt-5-nano`, `opencode/glm-5`, `opencode/big-pickle`, `opencode/minimax-m2.7`, and `opencode/minimax-m2.7-highspeed`. OpenCode Zen provides access to `opencode/` prefixed models including `opencode/claude-opus-4-7`, `opencode/gpt-5.4`, `opencode/gpt-5.3-codex`, `opencode/gpt-5-nano`, `opencode/glm-5`, `opencode/big-pickle`, `opencode/minimax-m2.7`, and `opencode/minimax-m2.7-highspeed`.
When OpenCode Zen is the best available provider, these are the most relevant source-backed examples: When OpenCode Zen is the best available provider, these are the most relevant source-backed examples:
| Agent | Model | | Agent | Model |
| ------------- | ---------------------------------------------------- | | ------------- | ---------------------------------------------------- |
| **Sisyphus** | `opencode/claude-opus-4-6` | | **Sisyphus** | `opencode/claude-opus-4-7` |
| **Oracle** | `opencode/gpt-5.4` | | **Oracle** | `opencode/gpt-5.4` |
| **Explore** | `opencode/minimax-m2.7` | | **Explore** | `opencode/minimax-m2.7` |
@@ -287,7 +287,7 @@ Not all models behave the same way. Understanding which models are "similar" hel
| Model | Provider(s) | Notes | | Model | Provider(s) | Notes |
| ------------------------ | ----------------------------------- | ----------------------------------------------------------------------- | | ------------------------ | ----------------------------------- | ----------------------------------------------------------------------- |
| **Claude Opus 4.6** | anthropic, github-copilot, opencode | Best overall. Default for Sisyphus. | | **Claude Opus 4.7** | anthropic, github-copilot, opencode | Best overall. Default for Sisyphus. |
| **Claude Sonnet 4.6** | anthropic, github-copilot, opencode | Faster, cheaper. Good balance. | | **Claude Sonnet 4.6** | anthropic, github-copilot, opencode | Faster, cheaper. Good balance. |
| **Claude Haiku 4.5** | anthropic, opencode | Fast and cheap. Good for quick tasks. | | **Claude Haiku 4.5** | anthropic, opencode | Fast and cheap. Good for quick tasks. |
| **Kimi K2.5** | kimi-for-coding, opencode-go, opencode, moonshotai, moonshotai-cn, firmware, ollama-cloud, aihubmix | Behaves very similarly to Claude. Great all-rounder that appears in several orchestration fallback chains. | | **Kimi K2.5** | kimi-for-coding, opencode-go, opencode, moonshotai, moonshotai-cn, firmware, ollama-cloud, aihubmix | Behaves very similarly to Claude. Great all-rounder that appears in several orchestration fallback chains. |
@@ -330,8 +330,8 @@ 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 | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Primary coding agent. Exact runtime chain from `src/shared/model-requirements.ts`. | | **Sisyphus** | Main ultraworker | anthropic\|github-copilot\|opencode/claude-opus-4-7 (max) → opencode-go/kimi-k2.5 → kimi-for-coding/k2p5 → opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → zai-coding-plan\|opencode/glm-5 → opencode/big-pickle | Primary coding agent. Exact runtime chain from `src/shared/model-requirements.ts`. |
| **Metis** | Plan review | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Reviews Prometheus plans for gaps. Exact runtime chain from `src/shared/model-requirements.ts`. | | **Metis** | Plan review | anthropic\|github-copilot\|opencode/claude-opus-4-7 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → kimi-for-coding/k2p5 | Reviews Prometheus plans for gaps. Exact runtime chain from `src/shared/model-requirements.ts`. |
**Dual-Prompt Agents** (auto-switch between Claude and GPT prompts): **Dual-Prompt Agents** (auto-switch between Claude and GPT prompts):
@@ -341,7 +341,7 @@ Priority: **Claude > GPT > Claude-like models**
| Agent | Role | Default Chain | GPT Prompt? | | Agent | Role | Default Chain | GPT Prompt? |
| -------------- | ----------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- | | -------------- | ----------------- | ---------------------------------------------------------- | ---------------------------------------------------------------- |
| **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) | | **Prometheus** | Strategic planner | anthropic\|github-copilot\|opencode/claude-opus-4-7 (max) → openai\|github-copilot\|opencode/gpt-5.4 (high) → opencode-go/glm-5 → google\|github-copilot\|opencode/gemini-3.1-pro | Yes — XML-tagged, principle-driven (~300 lines vs ~1,100 Claude) |
| **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | Yes - GPT-optimized todo management | | **Atlas** | Todo orchestrator | anthropic\|github-copilot\|opencode/claude-sonnet-4-6 → opencode-go/kimi-k2.5 → openai\|github-copilot\|opencode/gpt-5.4 (medium) → opencode-go/minimax-m2.7 | 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):
@@ -349,8 +349,8 @@ Priority: **Claude > GPT > Claude-like models**
| Agent | Role | Default Chain | Notes | | Agent | Role | Default Chain | Notes |
| -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ | | -------------- | ---------------------- | -------------------------------------- | ------------------------------------------------------ |
| **Hephaestus** | Deep autonomous worker | GPT-5.4 (medium) only | "Codex on steroids." No fallback. Requires GPT access. | | **Hephaestus** | Deep autonomous worker | GPT-5.4 (medium) only | "Codex on steroids." No fallback. Requires GPT access. |
| **Oracle** | Architecture/debugging | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → opencode-go/glm-5 | High-IQ strategic backup. GPT preferred. | | **Oracle** | Architecture/debugging | openai\|github-copilot\|opencode/gpt-5.4 (high) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → anthropic\|github-copilot\|opencode/claude-opus-4-7 (max) → opencode-go/glm-5 | High-IQ strategic backup. GPT preferred. |
| **Momus** | High-accuracy reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-6 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Verification agent. GPT preferred. | | **Momus** | High-accuracy reviewer | openai\|github-copilot\|opencode/gpt-5.4 (xhigh) → anthropic\|github-copilot\|opencode/claude-opus-4-7 (max) → google\|github-copilot\|opencode/gemini-3.1-pro (high) → opencode-go/glm-5 | Verification agent. GPT preferred. |
**Utility Agents** (speed over intelligence): **Utility Agents** (speed over intelligence):
@@ -463,7 +463,7 @@ gh api --silent --method PUT /user/starred/code-yeongyu/oh-my-openagent >/dev/nu
Tell the user of following: Tell the user of following:
1. **Sisyphus agent strongly recommends Opus 4.6 model. Using other models may result in significantly degraded experience.** 1. **Sisyphus agent strongly recommends Opus 4.7 model. Using other models may result in significantly degraded experience.**
2. **Feeling lazy?** Just include `ultrawork` (or `ulw`) in your prompt. That's it. The agent figures out the rest. 2. **Feeling lazy?** Just include `ultrawork` (or `ulw`) in your prompt. That's it. The agent figures out the rest.
+12 -12
View File
@@ -35,9 +35,9 @@ The orchestration system uses a three-layer architecture that solves context ove
flowchart TB flowchart TB
subgraph Planning["Planning Layer (Human + Prometheus)"] subgraph Planning["Planning Layer (Human + Prometheus)"]
User[(" User")] User[(" User")]
Prometheus[" Prometheus<br/>(Planner)<br/>claude-opus-4-6 / gpt-5.4 / glm-5"] Prometheus[" Prometheus<br/>(Planner)<br/>claude-opus-4-7 / gpt-5.4 / glm-5"]
Metis[" Metis<br/>(Consultant)<br/>claude-opus-4-6 / gpt-5.4 / glm-5"] Metis[" Metis<br/>(Consultant)<br/>claude-opus-4-7 / gpt-5.4 / glm-5"]
Momus[" Momus<br/>(Reviewer)<br/>gpt-5.4 / claude-opus-4-6 / gemini-3.1-pro / glm-5"] Momus[" Momus<br/>(Reviewer)<br/>gpt-5.4 / claude-opus-4-7 / gemini-3.1-pro / glm-5"]
end end
subgraph Execution["Execution Layer (Orchestrator)"] subgraph Execution["Execution Layer (Orchestrator)"]
@@ -46,10 +46,10 @@ 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 / kimi-k2.5 / gpt-5.4 / minimax-m2.7"] Junior[" Sisyphus-Junior<br/>(Task Executor)<br/>claude-sonnet-4-6 / kimi-k2.5 / gpt-5.4 / minimax-m2.7"]
Oracle[" Oracle<br/>(Architecture)<br/>gpt-5.4 / gemini-3.1-pro / claude-opus-4-6 / glm-5"] Oracle[" Oracle<br/>(Architecture)<br/>gpt-5.4 / gemini-3.1-pro / claude-opus-4-7 / glm-5"]
Explore[" Explore<br/>(Codebase Grep)<br/>grok-code-fast-1 / minimax-m2.7-highspeed / claude-haiku-4-5"] Explore[" Explore<br/>(Codebase Grep)<br/>grok-code-fast-1 / minimax-m2.7-highspeed / claude-haiku-4-5"]
Librarian[" Librarian<br/>(Docs/OSS)<br/>minimax-m2.7 / minimax-m2.7-highspeed / claude-haiku-4-5"] Librarian[" Librarian<br/>(Docs/OSS)<br/>minimax-m2.7 / minimax-m2.7-highspeed / claude-haiku-4-5"]
Frontend[" visual-engineering<br/>(category + frontend-ui-ux)<br/>gemini-3.1-pro / glm-5 / claude-opus-4-6"] Frontend[" visual-engineering<br/>(category + frontend-ui-ux)<br/>gemini-3.1-pro / glm-5 / claude-opus-4-7"]
end end
User -->|"Describe work"| Prometheus User -->|"Describe work"| Prometheus
@@ -282,7 +282,7 @@ This "boulder pushing" mechanism is why the system is named after Sisyphus.
```typescript ```typescript
// OLD: Model name creates distributional bias // OLD: Model name creates distributional bias
task({ agent: "gpt-5.4", prompt: "..." }); // Model knows its limitations task({ agent: "gpt-5.4", prompt: "..." }); // Model knows its limitations
task({ agent: "claude-opus-4-6", prompt: "..." }); // Different self-perception task({ agent: "claude-opus-4-7", prompt: "..." }); // Different self-perception
``` ```
**The Solution: Semantic Categories:** **The Solution: Semantic Categories:**
@@ -298,13 +298,13 @@ task({ category: "quick", prompt: "..." }); // "Just get it done fast"
| Category | Default config | Runtime fallback order | When to Use | | Category | Default config | Runtime fallback order | When to Use |
| -------------------- | ------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | -------------------- | ------------------------------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `visual-engineering` | `google/gemini-3.1-pro high` | `gemini-3.1-pro``glm-5``claude-opus-4-6``glm-5``k2p5` | Frontend, UI/UX, design, styling, animation | | `visual-engineering` | `google/gemini-3.1-pro high` | `gemini-3.1-pro``glm-5``claude-opus-4-7``glm-5``k2p5` | Frontend, UI/UX, design, styling, animation |
| `ultrabrain` | `openai/gpt-5.4 xhigh` | `gpt-5.4``gemini-3.1-pro``claude-opus-4-6``glm-5` | Deep logical reasoning, complex architecture decisions | | `ultrabrain` | `openai/gpt-5.4 xhigh` | `gpt-5.4``gemini-3.1-pro``claude-opus-4-7``glm-5` | Deep logical reasoning, complex architecture decisions |
| `deep` | `openai/gpt-5.4 medium` | `gpt-5.4``claude-opus-4-6``gemini-3.1-pro` | Goal-oriented autonomous problem-solving, thorough research | | `deep` | `openai/gpt-5.4 medium` | `gpt-5.4``claude-opus-4-7``gemini-3.1-pro` | Goal-oriented autonomous problem-solving, thorough research |
| `artistry` | `google/gemini-3.1-pro high` | `gemini-3.1-pro``claude-opus-4-6``gpt-5.4` | Highly creative or artistic tasks, novel ideas | | `artistry` | `google/gemini-3.1-pro high` | `gemini-3.1-pro``claude-opus-4-7``gpt-5.4` | Highly creative or artistic tasks, novel ideas |
| `quick` | `openai/gpt-5.4-mini` | `gpt-5.4-mini``claude-haiku-4-5``gemini-3-flash``minimax-m2.7``gpt-5-nano` | Trivial tasks, single file changes, typo fixes | | `quick` | `openai/gpt-5.4-mini` | `gpt-5.4-mini``claude-haiku-4-5``gemini-3-flash``minimax-m2.7``gpt-5-nano` | Trivial tasks, single file changes, typo fixes |
| `unspecified-low` | `anthropic/claude-sonnet-4-6` | `claude-sonnet-4-6``gpt-5.3-codex``kimi-k2.5``gemini-3-flash``minimax-m2.7` | Tasks that don't fit other categories, low effort | | `unspecified-low` | `anthropic/claude-sonnet-4-6` | `claude-sonnet-4-6``gpt-5.3-codex``kimi-k2.5``gemini-3-flash``minimax-m2.7` | Tasks that don't fit other categories, low effort |
| `unspecified-high` | `anthropic/claude-opus-4-6 max` | `claude-opus-4-6``gpt-5.4``glm-5``k2p5``kimi-k2.5` | Tasks that don't fit other categories, high effort | | `unspecified-high` | `anthropic/claude-opus-4-7 max` | `claude-opus-4-7``gpt-5.4``glm-5``k2p5``kimi-k2.5` | Tasks that don't fit other categories, high effort |
| `writing` | `kimi-for-coding/k2p5` | `gemini-3-flash``kimi-k2.5``claude-sonnet-4-6``minimax-m2.7` | Documentation, prose, technical writing | | `writing` | `kimi-for-coding/k2p5` | `gemini-3-flash``kimi-k2.5``claude-sonnet-4-6``minimax-m2.7` | Documentation, prose, technical writing |
### Skills: Domain-Specific Instructions ### Skills: Domain-Specific Instructions
@@ -423,7 +423,7 @@ Atlas is automatically activated when you run `/start-work`. You don't need to m
| Aspect | Hephaestus | Sisyphus + `ulw` / `ultrawork` | | Aspect | Hephaestus | Sisyphus + `ulw` / `ultrawork` |
| --------------- | ------------------------------------------ | ---------------------------------------------------- | | --------------- | ------------------------------------------ | ---------------------------------------------------- |
| **Model** | `gpt-5.4` (`medium`) | `claude-opus-4-6` / `kimi-k2.5` / `gpt-5.4` / `glm-5` depending on setup | | **Model** | `gpt-5.4` (`medium`) | `claude-opus-4-7` / `kimi-k2.5` / `gpt-5.4` / `glm-5` depending on setup |
| **Approach** | Autonomous deep worker | Keyword-activated ultrawork mode | | **Approach** | Autonomous deep worker | Keyword-activated ultrawork mode |
| **Best For** | Complex architectural work, deep reasoning | General complex tasks, "just do it" scenarios | | **Best For** | Complex architectural work, deep reasoning | General complex tasks, "just do it" scenarios |
| **Planning** | Self-plans during execution | Uses Prometheus plans if available | | **Planning** | Self-plans during execution | Uses Prometheus plans if available |
+6 -6
View File
@@ -82,11 +82,11 @@ Sisyphus is your main orchestrator. He plans, delegates to specialists, and driv
**Recommended models:** **Recommended models:**
- **Claude Opus 4.6** — Best overall experience. Sisyphus was built with Claude-optimized prompts. - **Claude Opus 4.7** — Best overall experience. Sisyphus was built with Claude-optimized prompts.
- **Kimi K2.5** — Great Claude-like alternative. Many users run this combo exclusively. - **Kimi K2.5** — Great Claude-like alternative. Many users run this combo exclusively.
- **GLM 5** — Solid option, especially via Z.ai. - **GLM 5** — Solid option, especially via Z.ai.
Sisyphus works best on Claude Opus 4.6, Kimi K2.5, and GLM 5. GPT-5.4 now has a dedicated prompt path, but older GPT models are still a poor fit and should route to Hephaestus instead. Sisyphus works best on Claude Opus 4.7, Kimi K2.5, and GLM 5. GPT-5.4 now has a dedicated prompt path, but older GPT models are still a poor fit and should route to Hephaestus instead.
### Hephaestus: The Legitimate Craftsman ### Hephaestus: The Legitimate Craftsman
@@ -173,7 +173,7 @@ You can override specific agents or categories in your config:
// Main orchestrator: Claude Opus or Kimi K2.5 work best // Main orchestrator: Claude Opus or Kimi K2.5 work best
"sisyphus": { "sisyphus": {
"model": "kimi-for-coding/k2p5", "model": "kimi-for-coding/k2p5",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
}, },
// Research agents: cheaper models are fine // Research agents: cheaper models are fine
@@ -207,10 +207,10 @@ You can override specific agents or categories in your config:
"unspecified-low": { "model": "openai/gpt-5.4-mini" }, "unspecified-low": { "model": "openai/gpt-5.4-mini" },
// High-effort fallback: best available // High-effort fallback: best available
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "unspecified-high": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
// Prose and documentation // Prose and documentation
"writing": { "model": "anthropic/claude-opus-4-6", "variant": "high" }, "writing": { "model": "anthropic/claude-opus-4-7", "variant": "high" },
}, },
} }
``` ```
@@ -219,7 +219,7 @@ You can override specific agents or categories in your config:
**Claude-like models** (instruction-following, structured output): **Claude-like models** (instruction-following, structured output):
- Claude Opus 4.6, Claude Haiku 4.5 - Claude Opus 4.7, Claude Haiku 4.5
- Kimi K2.5 — behaves very similarly to Claude - Kimi K2.5 — behaves very similarly to Claude
- GLM 5 — Claude-like behavior, good for broad tasks - GLM 5 — Claude-like behavior, good for broad tasks
+20 -20
View File
@@ -78,7 +78,7 @@ Here's a practical starting configuration:
// Main orchestrator: Claude Opus or Kimi K2.5 work best // Main orchestrator: Claude Opus or Kimi K2.5 work best
"sisyphus": { "sisyphus": {
"model": "kimi-for-coding/k2p5", "model": "kimi-for-coding/k2p5",
"ultrawork": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "ultrawork": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
}, },
// Research agents: cheap fast models are fine // Research agents: cheap fast models are fine
@@ -102,7 +102,7 @@ Here's a practical starting configuration:
"unspecified-low": { "model": "anthropic/claude-sonnet-4-6" }, "unspecified-low": { "model": "anthropic/claude-sonnet-4-6" },
// unspecified-high - complex work // unspecified-high - complex work
"unspecified-high": { "model": "anthropic/claude-opus-4-6", "variant": "max" }, "unspecified-high": { "model": "anthropic/claude-opus-4-7", "variant": "max" },
// writing - docs/prose // writing - docs/prose
"writing": { "model": "google/gemini-3-flash" }, "writing": { "model": "google/gemini-3-flash" },
@@ -130,7 +130,7 @@ Here's a practical starting configuration:
"zai-coding-plan": 10, "zai-coding-plan": 10,
}, },
"modelConcurrency": { "modelConcurrency": {
"anthropic/claude-opus-4-6": 2, "anthropic/claude-opus-4-7": 2,
"opencode/gpt-5-nano": 20, "opencode/gpt-5-nano": 20,
}, },
}, },
@@ -229,7 +229,7 @@ Control what tools an agent can use:
{ {
"agents": { "agents": {
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"fallback_models": [ "fallback_models": [
// Simple string fallback // Simple string fallback
"openai/gpt-5.4", "openai/gpt-5.4",
@@ -293,7 +293,7 @@ Domain-specific model delegation used by the `task()` tool. When Sisyphus delega
| `artistry` | `google/gemini-3.1-pro` (high) | Creative/unconventional approaches | | `artistry` | `google/gemini-3.1-pro` (high) | Creative/unconventional approaches |
| `quick` | `openai/gpt-5.4-mini` | Trivial tasks, typo fixes, single-file changes | | `quick` | `openai/gpt-5.4-mini` | Trivial tasks, typo fixes, single-file changes |
| `unspecified-low` | `anthropic/claude-sonnet-4-6` | General tasks, low effort | | `unspecified-low` | `anthropic/claude-sonnet-4-6` | General tasks, low effort |
| `unspecified-high` | `anthropic/claude-opus-4-6` (max) | General tasks, high effort | | `unspecified-high` | `anthropic/claude-opus-4-7` (max) | General tasks, high effort |
| `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing | | `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing |
> **Note**: Built-in defaults only apply if the category is present in your config. Otherwise the system default model is used. > **Note**: Built-in defaults only apply if the category is present in your config. Otherwise the system default model is used.
@@ -355,28 +355,28 @@ Capability data comes from provider runtime metadata first. OmO also ships bundl
| Agent | Default Model | Provider Priority | | Agent | Default Model | Provider Priority |
| --------------------- | ------------------- | ---------------------------------------------------------------------------- | | --------------------- | ------------------- | ---------------------------------------------------------------------------- |
| **Sisyphus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle` | | **Sisyphus** | `claude-opus-4-7` | `anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle` |
| **Hephaestus** | `gpt-5.4` | `gpt-5.4 (medium)` | | **Hephaestus** | `gpt-5.4` | `gpt-5.4 (medium)` |
| **oracle** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (high)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` | | **oracle** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (high)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``opencode-go/glm-5` |
| **librarian** | `minimax-m2.7` | `opencode-go/minimax-m2.7``opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` | | **librarian** | `minimax-m2.7` | `opencode-go/minimax-m2.7``opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **explore** | `grok-code-fast-1` | `github-copilot\|xai/grok-code-fast-1``opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` | | **explore** | `grok-code-fast-1` | `github-copilot\|xai/grok-code-fast-1``opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano` |
| **multimodal-looker** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (medium)``opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano` | | **multimodal-looker** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (medium)``opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano` |
| **Prometheus** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro` | | **Prometheus** | `claude-opus-4-7` | `anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro` |
| **Metis** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5` | | **Metis** | `claude-opus-4-7` | `anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **Momus** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (xhigh)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5` | | **Momus** | `gpt-5.4` | `openai\|github-copilot\|opencode/gpt-5.4 (xhigh)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5` |
| **Atlas** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7` | | **Atlas** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``opencode-go/minimax-m2.7` |
#### Category Provider Chains #### Category Provider Chains
| Category | Default Model | Provider Priority | | Category | Default Model | Provider Priority |
| ---------------------- | ------------------- | -------------------------------------------------------------- | | ---------------------- | ------------------- | -------------------------------------------------------------- |
| **visual-engineering** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``zai-coding-plan\|opencode/glm-5``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5``kimi-for-coding/k2p5` | | **visual-engineering** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``zai-coding-plan\|opencode/glm-5``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``opencode-go/glm-5``kimi-for-coding/k2p5` |
| **ultrabrain** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (xhigh)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5` | | **ultrabrain** | `gpt-5.4` | `openai\|opencode/gpt-5.4 (xhigh)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``opencode-go/glm-5` |
| **deep** | `gpt-5.4` | `openai\|github-copilot\|venice\|opencode/gpt-5.4 (medium)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)` | | **deep** | `gpt-5.4` | `openai\|github-copilot\|venice\|opencode/gpt-5.4 (medium)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)` |
| **artistry** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4` | | **artistry** | `gemini-3.1-pro` | `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``openai\|github-copilot\|opencode/gpt-5.4` |
| **quick** | `gpt-5.4-mini` | `openai\|github-copilot\|opencode/gpt-5.4-mini``anthropic\|github-copilot\|opencode/claude-haiku-4-5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7``opencode/gpt-5-nano` | | **quick** | `gpt-5.4-mini` | `openai\|github-copilot\|opencode/gpt-5.4-mini``anthropic\|github-copilot\|opencode/claude-haiku-4-5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7``opencode/gpt-5-nano` |
| **unspecified-low** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``openai\|opencode/gpt-5.3-codex (medium)``opencode-go/kimi-k2.5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7` | | **unspecified-low** | `claude-sonnet-4-6` | `anthropic\|github-copilot\|opencode/claude-sonnet-4-6``openai\|opencode/gpt-5.3-codex (medium)``opencode-go/kimi-k2.5``google\|github-copilot\|opencode/gemini-3-flash``opencode-go/minimax-m2.7` |
| **unspecified-high** | `claude-opus-4-6` | `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``zai-coding-plan\|opencode/glm-5``kimi-for-coding/k2p5``opencode-go/glm-5``opencode/kimi-k2.5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5` | | **unspecified-high** | `claude-opus-4-7` | `anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``openai\|github-copilot\|opencode/gpt-5.4 (high)``zai-coding-plan\|opencode/glm-5``kimi-for-coding/k2p5``opencode-go/glm-5``opencode/kimi-k2.5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5` |
| **writing** | `gemini-3-flash` | `google\|github-copilot\|opencode/gemini-3-flash``opencode-go/kimi-k2.5``anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/minimax-m2.7` | | **writing** | `gemini-3-flash` | `google\|github-copilot\|opencode/gemini-3-flash``opencode-go/kimi-k2.5``anthropic\|github-copilot\|opencode/claude-sonnet-4-6``opencode-go/minimax-m2.7` |
Run `bunx oh-my-opencode doctor --verbose` to see effective model resolution for your config. Run `bunx oh-my-opencode doctor --verbose` to see effective model resolution for your config.
@@ -395,7 +395,7 @@ Control parallel agent execution and concurrency limits.
"defaultConcurrency": 5, "defaultConcurrency": 5,
"staleTimeoutMs": 180000, "staleTimeoutMs": 180000,
"providerConcurrency": { "anthropic": 3, "openai": 5, "google": 10 }, "providerConcurrency": { "anthropic": 3, "openai": 5, "google": 10 },
"modelConcurrency": { "anthropic/claude-opus-4-6": 2 } "modelConcurrency": { "anthropic/claude-opus-4-7": 2 }
} }
} }
``` ```
@@ -678,7 +678,7 @@ Define `fallback_models` per agent or category:
{ {
"agents": { "agents": {
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"fallback_models": [ "fallback_models": [
"openai/gpt-5.4", "openai/gpt-5.4",
{ {
@@ -697,7 +697,7 @@ Define `fallback_models` per agent or category:
{ {
"agents": { "agents": {
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"fallback_models": [ "fallback_models": [
"openai/gpt-5.4", "openai/gpt-5.4",
{ {
@@ -798,7 +798,7 @@ Mix string entries and object entries when only some fallback models need specia
{ {
"agents": { "agents": {
"sisyphus": { "sisyphus": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"fallback_models": [ "fallback_models": [
"openai/gpt-5.4", "openai/gpt-5.4",
{ {
@@ -832,7 +832,7 @@ Mix string entries and object entries when only some fallback models need specia
"maxTokens": 12000 "maxTokens": 12000
}, },
{ {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"variant": "max", "variant": "max",
"temperature": 0.2 "temperature": 0.2
}, },
+8 -8
View File
@@ -10,9 +10,9 @@ Core-agent tab cycling is deterministic via injected runtime order field. The fi
| 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: `opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle`. | | **Sisyphus** | `claude-opus-4-7` | 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: `opencode-go/kimi-k2.5``kimi-for-coding/k2p5``opencode\|moonshotai\|moonshotai-cn\|firmware\|ollama-cloud\|aihubmix/kimi-k2.5``openai\|github-copilot\|opencode/gpt-5.4 (medium)``zai-coding-plan\|opencode/glm-5``opencode/big-pickle`. |
| **Hephaestus** | `gpt-5.4` | 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. Requires a GPT-capable provider. | | **Hephaestus** | `gpt-5.4` | 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. Requires a GPT-capable provider. |
| **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``opencode-go/glm-5`. | | **Oracle** | `gpt-5.4` | Architecture decisions, code review, debugging. Read-only consultation with stellar logical reasoning and deep analysis. Inspired by AmpCode. Fallback: `google\|github-copilot\|opencode/gemini-3.1-pro (high)``anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``opencode-go/glm-5`. |
| **Librarian** | `minimax-m2.7` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. | | **Librarian** | `minimax-m2.7` | Multi-repo analysis, documentation lookup, OSS implementation examples. Deep codebase understanding with evidence-based answers. Fallback: `opencode/minimax-m2.7-highspeed``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. | | **Explore** | `grok-code-fast-1` | Fast codebase exploration and contextual grep. Fallback: `opencode-go/minimax-m2.7-highspeed``opencode/minimax-m2.7``anthropic\|opencode/claude-haiku-4-5``opencode/gpt-5-nano`. |
| **Multimodal-Looker** | `gpt-5.4` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano`. | | **Multimodal-Looker** | `gpt-5.4` | Visual content specialist. Analyzes PDFs, images, diagrams to extract information. Fallback: `opencode-go/kimi-k2.5``zai-coding-plan/glm-4.6v``openai\|github-copilot\|opencode/gpt-5-nano`. |
@@ -20,9 +20,9 @@ Core-agent tab cycling is deterministic via injected runtime order field. The fi
| Agent | Model | Purpose | | Agent | Model | Purpose |
| -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Prometheus** | `claude-opus-4-6` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro`. | | **Prometheus** | `claude-opus-4-7` | Strategic planner with interview mode. Creates detailed work plans through iterative questioning. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``google\|github-copilot\|opencode/gemini-3.1-pro`. |
| **Metis** | `claude-opus-4-6` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5`. | | **Metis** | `claude-opus-4-7` | Plan consultant — pre-planning analysis. Identifies hidden intentions, ambiguities, and AI failure points. Fallback: `openai\|github-copilot\|opencode/gpt-5.4 (high)``opencode-go/glm-5``kimi-for-coding/k2p5`. |
| **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `anthropic\|github-copilot\|opencode/claude-opus-4-6 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5`. | | **Momus** | `gpt-5.4` | Plan reviewer — validates plans against clarity, verifiability, and completeness standards. Fallback: `anthropic\|github-copilot\|opencode/claude-opus-4-7 (max)``google\|github-copilot\|opencode/gemini-3.1-pro (high)``opencode-go/glm-5`. |
### Orchestration Agents ### Orchestration Agents
@@ -115,7 +115,7 @@ By combining these two concepts, you can generate optimal agents through `task`.
| `artistry` | `google/gemini-3.1-pro` (high) | Highly creative/artistic tasks, novel ideas | | `artistry` | `google/gemini-3.1-pro` (high) | Highly creative/artistic tasks, novel ideas |
| `quick` | `openai/gpt-5.4-mini` | Trivial tasks - single file changes, typo fixes, simple modifications | | `quick` | `openai/gpt-5.4-mini` | Trivial tasks - single file changes, typo fixes, simple modifications |
| `unspecified-low` | `anthropic/claude-sonnet-4-6` | Tasks that don't fit other categories, low effort required | | `unspecified-low` | `anthropic/claude-sonnet-4-6` | Tasks that don't fit other categories, low effort required |
| `unspecified-high` | `anthropic/claude-opus-4-6` (max) | Tasks that don't fit other categories, high effort required | | `unspecified-high` | `anthropic/claude-opus-4-7` (max) | Tasks that don't fit other categories, high effort required |
| `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing | | `writing` | `google/gemini-3-flash` | Documentation, prose, technical writing |
### Usage ### Usage
@@ -138,7 +138,7 @@ You can define custom categories in your plugin config file. During the rename t
| Field | Type | Description | | Field | Type | Description |
| ------------------- | ------- | --------------------------------------------------------------------------- | | ------------------- | ------- | --------------------------------------------------------------------------- |
| `description` | string | Human-readable description of the category's purpose. Shown in task prompt. | | `description` | string | Human-readable description of the category's purpose. Shown in task prompt. |
| `model` | string | AI model ID to use (e.g., `anthropic/claude-opus-4-6`) | | `model` | string | AI model ID to use (e.g., `anthropic/claude-opus-4-7`) |
| `variant` | string | Model variant (e.g., `max`, `xhigh`) | | `variant` | string | Model variant (e.g., `max`, `xhigh`) |
| `temperature` | number | Creativity level (0.0 ~ 2.0). Lower is more deterministic. | | `temperature` | number | Creativity level (0.0 ~ 2.0). Lower is more deterministic. |
| `top_p` | number | Nucleus sampling parameter (0.0 ~ 1.0) | | `top_p` | number | Nucleus sampling parameter (0.0 ~ 1.0) |
@@ -170,7 +170,7 @@ You can define custom categories in your plugin config file. During the rename t
// 3. Configure thinking model and restrict tools // 3. Configure thinking model and restrict tools
"deep-reasoning": { "deep-reasoning": {
"model": "anthropic/claude-opus-4-6", "model": "anthropic/claude-opus-4-7",
"thinking": { "thinking": {
"type": "enabled", "type": "enabled",
"budgetTokens": 32000, "budgetTokens": 32000,
+5 -5
View File
@@ -10,16 +10,16 @@ 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 | k2p5 -> kimi-k2.5 -> gpt-5.4 medium -> glm-5 -> big-pickle | Main orchestrator, plans + delegates | | **Sisyphus** | claude-opus-4-7 max | 0.1 | all | k2p5 -> kimi-k2.5 -> gpt-5.4 medium -> glm-5 -> big-pickle | Main orchestrator, plans + delegates |
| **Hephaestus** | gpt-5.4 medium | 0.1 | all | — | Autonomous deep worker | | **Hephaestus** | gpt-5.4 medium | 0.1 | all | — | Autonomous deep worker |
| **Oracle** | gpt-5.4 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-7 max | Read-only consultation |
| **Librarian** | minimax-m2.7 | 0.1 | subagent | minimax-m2.7-highspeed -> claude-haiku-4-5 -> gpt-5-nano | External docs/code search | | **Librarian** | minimax-m2.7 | 0.1 | subagent | minimax-m2.7-highspeed -> claude-haiku-4-5 -> gpt-5-nano | External docs/code search |
| **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.7-highspeed -> minimax-m2.7 -> claude-haiku-4-5 -> gpt-5-nano | Contextual grep | | **Explore** | grok-code-fast-1 | 0.1 | subagent | minimax-m2.7-highspeed -> minimax-m2.7 -> 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.4 high -> gemini-3.1-pro high | Pre-planning consultant | | **Metis** | claude-opus-4-7 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-7 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 | — | internal planner | Strategic planner (internal) | | **Prometheus** | claude-opus-4-7 max | 0.1 | — | internal planner | Strategic planner (internal) |
| **Sisyphus-Junior** | claude-sonnet-4-6 | 0.1 | all | user-configurable | Category-spawned executor | | **Sisyphus-Junior** | claude-sonnet-4-6 | 0.1 | all | user-configurable | Category-spawned executor |
## TOOL RESTRICTIONS ## TOOL RESTRICTIONS
+4 -4
View File
@@ -57,7 +57,7 @@ describe("Sisyphus prompt identity", () => {
describe("#given a Sisyphus agent created with default model", () => { describe("#given a Sisyphus agent created with default model", () => {
describe("#when checking the prompt", () => { describe("#when checking the prompt", () => {
it("#then contains the agent identity section with override directive", () => { it("#then contains the agent identity section with override directive", () => {
const config = createSisyphusAgent("anthropic/claude-opus-4-6") const config = createSisyphusAgent("anthropic/claude-opus-4-7")
expect(config.prompt).toContain("<agent-identity>") expect(config.prompt).toContain("<agent-identity>")
expect(config.prompt).toContain("Sisyphus") expect(config.prompt).toContain("Sisyphus")
@@ -65,7 +65,7 @@ describe("Sisyphus prompt identity", () => {
}) })
it("#then identity section appears before the Role section", () => { it("#then identity section appears before the Role section", () => {
const config = createSisyphusAgent("anthropic/claude-opus-4-6") const config = createSisyphusAgent("anthropic/claude-opus-4-7")
const prompt = config.prompt ?? "" const prompt = config.prompt ?? ""
const identityIndex = prompt.indexOf("<agent-identity>") const identityIndex = prompt.indexOf("<agent-identity>")
const roleIndex = prompt.indexOf("<Role>") const roleIndex = prompt.indexOf("<Role>")
@@ -115,7 +115,7 @@ describe("Agent identity preservation through overrides", () => {
describe("#given a Sisyphus agent with prompt_append override", () => { describe("#given a Sisyphus agent with prompt_append override", () => {
describe("#when merging the override", () => { describe("#when merging the override", () => {
it("#then identity section is preserved in the merged prompt", () => { it("#then identity section is preserved in the merged prompt", () => {
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-6") const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-7")
const merged = mergeAgentConfig(baseConfig, { prompt_append: "Extra instructions here" }) const merged = mergeAgentConfig(baseConfig, { prompt_append: "Extra instructions here" })
expect(merged.prompt).toContain("<agent-identity>") expect(merged.prompt).toContain("<agent-identity>")
@@ -129,7 +129,7 @@ describe("Agent identity preservation through overrides", () => {
describe("#given a Sisyphus agent with model override only", () => { describe("#given a Sisyphus agent with model override only", () => {
describe("#when merging the override", () => { describe("#when merging the override", () => {
it("#then identity section is preserved unchanged", () => { it("#then identity section is preserved unchanged", () => {
const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-6") const baseConfig = createSisyphusAgent("anthropic/claude-opus-4-7")
const merged = mergeAgentConfig(baseConfig, { model: "openai/gpt-5.4" }) const merged = mergeAgentConfig(baseConfig, { model: "openai/gpt-5.4" })
expect(merged.prompt).toContain("<agent-identity>") expect(merged.prompt).toContain("<agent-identity>")
@@ -43,7 +43,7 @@ describe("maybeCreateSisyphusConfig", () => {
// given // given
const agentOverrides: AgentOverrides = { const agentOverrides: AgentOverrides = {
sisyphus: { sisyphus: {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
permission: { permission: {
apply_patch: "allow", apply_patch: "allow",
}, },
@@ -55,8 +55,8 @@ describe("maybeCreateSisyphusConfig", () => {
const config = maybeCreateSisyphusConfig({ const config = maybeCreateSisyphusConfig({
disabledAgents: [], disabledAgents: [],
agentOverrides, agentOverrides,
availableModels: new Set(["anthropic/claude-opus-4-6"]), availableModels: new Set(["anthropic/claude-opus-4-7"]),
systemDefaultModel: "anthropic/claude-opus-4-6", systemDefaultModel: "anthropic/claude-opus-4-7",
isFirstRunNoCache: false, isFirstRunNoCache: false,
availableAgents: [], availableAgents: [],
availableSkills: [], availableSkills: [],
@@ -67,7 +67,7 @@ describe("maybeCreateSisyphusConfig", () => {
// then // then
expect(config).toBeDefined(); expect(config).toBeDefined();
expect(config?.model).toBe("anthropic/claude-opus-4-6"); expect(config?.model).toBe("anthropic/claude-opus-4-7");
// Claude models should allow the user override // Claude models should allow the user override
expect(config?.permission).toHaveProperty("apply_patch", "allow"); expect(config?.permission).toHaveProperty("apply_patch", "allow");
}); });
@@ -2,13 +2,13 @@ import { describe, expect, spyOn, test } from "bun:test"
import { createBuiltinAgents } from "./builtin-agents" import { createBuiltinAgents } from "./builtin-agents"
import * as shared from "../shared" import * as shared from "../shared"
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6" const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-7"
describe("createBuiltinAgents custom agent visibility", () => { describe("createBuiltinAgents custom agent visibility", () => {
test("#given runtime custom agents #when orchestrator prompts are built #then custom agents are not advertised for automatic delegation", async () => { test("#given runtime custom agents #when orchestrator prompts are built #then custom agents are not advertised for automatic delegation", 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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
try { try {
@@ -211,7 +211,7 @@ describe("buildParallelDelegationSection", () => {
it("#given Claude model #when building #then returns empty", () => { it("#given Claude model #when building #then returns empty", () => {
//#given //#given
const model = "anthropic/claude-opus-4-6" const model = "anthropic/claude-opus-4-7"
const categories = [deepCategory] const categories = [deepCategory]
//#when //#when
+8 -8
View File
@@ -56,7 +56,7 @@ describe("getHephaestusPromptSource", () => {
test("returns 'gpt' for non-GPT models and undefined", () => { test("returns 'gpt' for non-GPT models and undefined", () => {
// given // given
const model1 = "anthropic/claude-opus-4-6"; const model1 = "anthropic/claude-opus-4-7";
const model2 = undefined; const model2 = undefined;
// when // when
@@ -124,7 +124,7 @@ describe("getHephaestusPrompt", () => {
test("Claude model returns generic GPT prompt (Hephaestus default)", () => { test("Claude model returns generic GPT prompt (Hephaestus default)", () => {
// given // given
const model = "anthropic/claude-opus-4-6"; const model = "anthropic/claude-opus-4-7";
// when // when
const prompt = getHephaestusPrompt(model); const prompt = getHephaestusPrompt(model);
@@ -149,7 +149,7 @@ describe("getHephaestusPrompt", () => {
test("useTaskSystem=false includes Todo Discipline for Claude models", () => { test("useTaskSystem=false includes Todo Discipline for Claude models", () => {
// given // given
const model = "anthropic/claude-opus-4-6"; const model = "anthropic/claude-opus-4-7";
// when // when
const prompt = getHephaestusPrompt(model, false); const prompt = getHephaestusPrompt(model, false);
@@ -239,7 +239,7 @@ describe("createHephaestusAgent", () => {
// given // given
const gpt54Model = "openai/gpt-5.4"; const gpt54Model = "openai/gpt-5.4";
const gptGenericModel = "openai/gpt-4o"; const gptGenericModel = "openai/gpt-4o";
const claudeModel = "anthropic/claude-opus-4-6"; const claudeModel = "anthropic/claude-opus-4-7";
// when // when
const gpt54Config = createHephaestusAgent(gpt54Model); const gpt54Config = createHephaestusAgent(gpt54Model);
@@ -322,7 +322,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
// given // given
const agentOverrides: AgentOverrides = { const agentOverrides: AgentOverrides = {
hephaestus: { hephaestus: {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
permission: { permission: {
apply_patch: "allow", apply_patch: "allow",
}, },
@@ -334,8 +334,8 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
const config = maybeCreateHephaestusConfig({ const config = maybeCreateHephaestusConfig({
disabledAgents: [], disabledAgents: [],
agentOverrides, agentOverrides,
availableModels: new Set(["anthropic/claude-opus-4-6"]), availableModels: new Set(["anthropic/claude-opus-4-7"]),
systemDefaultModel: "anthropic/claude-opus-4-6", systemDefaultModel: "anthropic/claude-opus-4-7",
isFirstRunNoCache: false, isFirstRunNoCache: false,
availableAgents: [], availableAgents: [],
availableSkills: [], availableSkills: [],
@@ -346,7 +346,7 @@ describe("maybeCreateHephaestusConfig GPT apply_patch guard", () => {
// then // then
expect(config).toBeDefined(); expect(config).toBeDefined();
expect(config?.model).toBe("anthropic/claude-opus-4-6"); expect(config?.model).toBe("anthropic/claude-opus-4-7");
expect(config?.permission).toHaveProperty("apply_patch", "allow"); expect(config?.permission).toHaveProperty("apply_patch", "allow");
}); });
}); });
+7 -7
View File
@@ -18,7 +18,7 @@ describe("isGpt5_4Model", () => {
}); });
test("does not match non-GPT models", () => { test("does not match non-GPT models", () => {
expect(isGpt5_4Model("anthropic/claude-opus-4-6")).toBe(false); expect(isGpt5_4Model("anthropic/claude-opus-4-7")).toBe(false);
expect(isGpt5_4Model("google/gemini-3.1-pro")).toBe(false); expect(isGpt5_4Model("google/gemini-3.1-pro")).toBe(false);
expect(isGpt5_4Model("openai/o1")).toBe(false); expect(isGpt5_4Model("openai/o1")).toBe(false);
}); });
@@ -64,7 +64,7 @@ describe("isGptModel", () => {
}); });
test("claude models are not gpt", () => { test("claude models are not gpt", () => {
expect(isGptModel("anthropic/claude-opus-4-6")).toBe(false); expect(isGptModel("anthropic/claude-opus-4-7")).toBe(false);
expect(isGptModel("anthropic/claude-sonnet-4-6")).toBe(false); expect(isGptModel("anthropic/claude-sonnet-4-6")).toBe(false);
expect(isGptModel("litellm/anthropic.claude-opus-4-5")).toBe(false); expect(isGptModel("litellm/anthropic.claude-opus-4-5")).toBe(false);
}); });
@@ -75,7 +75,7 @@ describe("isGptModel", () => {
}); });
test("opencode provider is not gpt", () => { test("opencode provider is not gpt", () => {
expect(isGptModel("opencode/claude-opus-4-6")).toBe(false); expect(isGptModel("opencode/claude-opus-4-7")).toBe(false);
}); });
}); });
@@ -95,7 +95,7 @@ describe("isMiniMaxModel", () => {
test("does not match non-minimax models", () => { test("does not match non-minimax models", () => {
expect(isMiniMaxModel("openai/gpt-5.4")).toBe(false); expect(isMiniMaxModel("openai/gpt-5.4")).toBe(false);
expect(isMiniMaxModel("anthropic/claude-opus-4-6")).toBe(false); expect(isMiniMaxModel("anthropic/claude-opus-4-7")).toBe(false);
expect(isMiniMaxModel("google/gemini-3.1-pro")).toBe(false); expect(isMiniMaxModel("google/gemini-3.1-pro")).toBe(false);
expect(isMiniMaxModel("opencode-go/kimi-k2.5")).toBe(false); expect(isMiniMaxModel("opencode-go/kimi-k2.5")).toBe(false);
}); });
@@ -116,7 +116,7 @@ describe("isGlmModel", () => {
test("#given non-GLM models #then returns false", () => { test("#given non-GLM models #then returns false", () => {
expect(isGlmModel("openai/gpt-5.4")).toBe(false); expect(isGlmModel("openai/gpt-5.4")).toBe(false);
expect(isGlmModel("anthropic/claude-opus-4-6")).toBe(false); expect(isGlmModel("anthropic/claude-opus-4-7")).toBe(false);
expect(isGlmModel("google/gemini-3.1-pro")).toBe(false); expect(isGlmModel("google/gemini-3.1-pro")).toBe(false);
}); });
}); });
@@ -156,11 +156,11 @@ describe("isGeminiModel", () => {
}); });
test("#given claude models #then returns false", () => { test("#given claude models #then returns false", () => {
expect(isGeminiModel("anthropic/claude-opus-4-6")).toBe(false); expect(isGeminiModel("anthropic/claude-opus-4-7")).toBe(false);
expect(isGeminiModel("anthropic/claude-sonnet-4-6")).toBe(false); expect(isGeminiModel("anthropic/claude-sonnet-4-6")).toBe(false);
}); });
test("#given opencode provider #then returns false", () => { test("#given opencode provider #then returns false", () => {
expect(isGeminiModel("opencode/claude-opus-4-6")).toBe(false); expect(isGeminiModel("opencode/claude-opus-4-7")).toBe(false);
}); });
}); });
+23 -23
View File
@@ -7,7 +7,7 @@ import * as connectedProvidersCache from "../shared/connected-providers-cache"
import * as modelAvailability from "../shared/model-availability" import * as modelAvailability from "../shared/model-availability"
import * as shared from "../shared" import * as shared from "../shared"
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6" const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-7"
let createBuiltinAgents: (typeof import("./builtin-agents"))["createBuiltinAgents"] let createBuiltinAgents: (typeof import("./builtin-agents"))["createBuiltinAgents"]
async function importFreshBuiltinAgentsModule(): Promise<typeof import("./builtin-agents")> { async function importFreshBuiltinAgentsModule(): Promise<typeof import("./builtin-agents")> {
@@ -32,7 +32,7 @@ describe("createBuiltinAgents with model overrides", () => {
// #given // #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([ new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"kimi-for-coding/k2p5", "kimi-for-coding/k2p5",
"opencode/kimi-k2.5-free", "opencode/kimi-k2.5-free",
"zai-coding-plan/glm-5", "zai-coding-plan/glm-5",
@@ -45,7 +45,7 @@ describe("createBuiltinAgents with model overrides", () => {
const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {}) const agents = await createBuiltinAgents([], {}, undefined, TEST_DEFAULT_MODEL, undefined, undefined, [], {})
// #then // #then
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6") expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 }) expect(agents.sisyphus.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.sisyphus.reasoningEffort).toBeUndefined() expect(agents.sisyphus.reasoningEffort).toBeUndefined()
} finally { } finally {
@@ -170,7 +170,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus is created on first run when no availableModels or cache exist", async () => { test("Sisyphus is created on first run when no availableModels or cache exist", async () => {
// #given // #given
const systemDefaultModel = "anthropic/claude-opus-4-6" const systemDefaultModel = "anthropic/claude-opus-4-7"
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null) const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set()) const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
@@ -180,7 +180,7 @@ describe("createBuiltinAgents with model overrides", () => {
// #then // #then
expect(agents.sisyphus).toBeDefined() expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6") expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.7")
} finally { } finally {
cacheSpy.mockRestore() cacheSpy.mockRestore()
fetchSpy.mockRestore() fetchSpy.mockRestore()
@@ -299,7 +299,7 @@ describe("createBuiltinAgents with model overrides", () => {
// #given // #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([ new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"kimi-for-coding/k2p5", "kimi-for-coding/k2p5",
"opencode/kimi-k2.5-free", "opencode/kimi-k2.5-free",
"zai-coding-plan/glm-5", "zai-coding-plan/glm-5",
@@ -341,7 +341,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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
const customAgentSummaries = [ const customAgentSummaries = [
@@ -377,7 +377,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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
const customAgentSummaries = [ const customAgentSummaries = [
@@ -413,7 +413,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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
const disabledAgents = ["ReSeArChEr"] const disabledAgents = ["ReSeArChEr"]
@@ -449,7 +449,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("does not advertise duplicate custom agents case-insensitively", async () => { test("does not advertise duplicate 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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
const customAgentSummaries = [ const customAgentSummaries = [
@@ -481,7 +481,7 @@ describe("createBuiltinAgents with model overrides", () => {
test("does not surface custom agent strings in orchestrator prompts", async () => { test("does not surface custom agent strings in 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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
const customAgentSummaries = [ const customAgentSummaries = [
@@ -555,7 +555,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
]) ])
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set([ new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"kimi-for-coding/k2p5", "kimi-for-coding/k2p5",
"opencode/kimi-k2.5-free", "opencode/kimi-k2.5-free",
"zai-coding-plan/glm-5", "zai-coding-plan/glm-5",
@@ -569,7 +569,7 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
// #then // #then
expect(agents.sisyphus).toBeDefined() expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6") expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
} finally { } finally {
cacheSpy.mockRestore() cacheSpy.mockRestore()
fetchSpy.mockRestore() fetchSpy.mockRestore()
@@ -590,7 +590,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
const providers = options?.connectedProviders ?? [] const providers = options?.connectedProviders ?? []
return providers.includes("openai") return providers.includes("openai")
? new Set(["openai/gpt-5.3-codex"]) ? new Set(["openai/gpt-5.3-codex"])
: new Set(["anthropic/claude-opus-4-6"]) : new Set(["anthropic/claude-opus-4-7"])
}) })
try { try {
@@ -609,7 +609,7 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
test("hephaestus is not created when no required provider is connected", async () => { test("hephaestus is not created when no required provider is connected", async () => {
// #given - only anthropic models available, not in hephaestus requiresProvider // #given - only anthropic models available, not in hephaestus requiresProvider
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6"]) new Set(["anthropic/claude-opus-4-7"])
) )
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"]) const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["anthropic"])
@@ -699,10 +699,10 @@ describe("createBuiltinAgents with requiresProvider gating (hephaestus)", () =>
test("hephaestus is created when explicit config provided even if provider unavailable", async () => { test("hephaestus is created when explicit config provided even if provider unavailable", async () => {
// #given // #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6"]) new Set(["anthropic/claude-opus-4-7"])
) )
const overrides = { const overrides = {
hephaestus: { model: "anthropic/claude-opus-4-6" }, hephaestus: { model: "anthropic/claude-opus-4-7" },
} }
try { try {
@@ -781,7 +781,7 @@ describe("Sisyphus and Librarian environment context toggle", () => {
beforeEach(() => { beforeEach(() => {
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6", "google/gemini-3-flash"]) new Set(["anthropic/claude-opus-4-7", "google/gemini-3-flash"])
) )
}) })
@@ -840,7 +840,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.4"]) new Set(["anthropic/claude-opus-4-7", "openai/gpt-5.4"])
) )
}) })
@@ -893,7 +893,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
test("sisyphus is created when at least one fallback model is available", async () => { test("sisyphus is created when at least one fallback model is available", async () => {
// #given // #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue( const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(
new Set(["anthropic/claude-opus-4-6"]) new Set(["anthropic/claude-opus-4-7"])
) )
try { try {
@@ -918,7 +918,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
// #then // #then
expect(agents.sisyphus).toBeDefined() expect(agents.sisyphus).toBeDefined()
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.6") expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4.7")
} finally { } finally {
cacheSpy.mockRestore() cacheSpy.mockRestore()
fetchSpy.mockRestore() fetchSpy.mockRestore()
@@ -929,7 +929,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
// #given // #given
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set()) const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = { const overrides = {
sisyphus: { model: "anthropic/claude-opus-4-6" }, sisyphus: { model: "anthropic/claude-opus-4-7" },
} }
try { try {
@@ -1039,7 +1039,7 @@ describe("createBuiltinAgents with requiresAnyModel gating (sisyphus)", () => {
describe("buildAgent with category and skills", () => { describe("buildAgent with category and skills", () => {
const { buildAgent } = require("./agent-builder") const { buildAgent } = require("./agent-builder")
const TEST_MODEL = "anthropic/claude-opus-4-6" const TEST_MODEL = "anthropic/claude-opus-4-7"
beforeEach(() => { beforeEach(() => {
clearSkillCache() clearSkillCache()
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -46,7 +46,7 @@ Model Providers (Priority: Native > Copilot > OpenCode Zen > Z.ai > Kimi > Verce
OpenAI Native openai/ models (GPT-5.4 for Oracle) OpenAI Native openai/ models (GPT-5.4 for Oracle)
Gemini Native google/ models (Gemini 3.1 Pro, Flash) Gemini Native google/ models (Gemini 3.1 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-7, etc.)
Z.ai zai-coding-plan/glm-5 (visual-engineering fallback) Z.ai zai-coding-plan/glm-5 (visual-engineering fallback)
Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback) Kimi kimi-for-coding/k2p5 (Sisyphus/Prometheus fallback)
Vercel vercel/ models (universal proxy, always last fallback) Vercel vercel/ models (universal proxy, always last fallback)
@@ -26,8 +26,8 @@ describe("generateOmoConfig - model fallback system", () => {
//#then //#then
expect([ expect([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"github-copilot/claude-opus-4-6", "github-copilot/claude-opus-4-7",
]).toContain((result.agents as Record<string, { model: string }>).sisyphus.model) ]).toContain((result.agents as Record<string, { model: string }>).sisyphus.model)
}) })
@@ -74,7 +74,7 @@ describe("generateOmoConfig - model fallback system", () => {
//#then //#then
expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7") expect((result.agents as Record<string, { model: string }>).librarian.model).toBe("zai-coding-plan/glm-4.7")
expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-6") expect((result.agents as Record<string, { model: string }>).sisyphus.model).toBe("anthropic/claude-opus-4-7")
}) })
test("uses native OpenAI models when only ChatGPT available", () => { test("uses native OpenAI models when only ChatGPT available", () => {
@@ -131,7 +131,7 @@ describe("generateOmoConfig - model fallback system", () => {
}> }>
//#then //#then
expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-6") expect(agents.sisyphus.model).toBe("anthropic/claude-opus-4-7")
expect(agents.sisyphus.fallback_models).toEqual([ expect(agents.sisyphus.fallback_models).toEqual([
{ {
model: "openai/gpt-5.4", model: "openai/gpt-5.4",
@@ -141,7 +141,7 @@ describe("generateOmoConfig - model fallback system", () => {
expect(categories.deep.model).toBe("openai/gpt-5.4") expect(categories.deep.model).toBe("openai/gpt-5.4")
expect(categories.deep.fallback_models).toEqual([ expect(categories.deep.fallback_models).toEqual([
{ {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
}, },
]) ])
@@ -34,7 +34,7 @@ describe("loadAvailableModelsFromCache", () => {
join(tempDir, "cache", "opencode", "models.json"), join(tempDir, "cache", "opencode", "models.json"),
JSON.stringify({ JSON.stringify({
openai: { models: { "gpt-5.4": {} } }, openai: { models: { "gpt-5.4": {} } },
anthropic: { models: { "claude-opus-4-6": {}, "claude-sonnet-4-6": {} } }, anthropic: { models: { "claude-opus-4-7": {}, "claude-sonnet-4-6": {} } },
}) })
) )
@@ -14,7 +14,7 @@ describe("model-resolution check", () => {
// then: Should have agent entries // then: Should have agent entries
const sisyphus = info.agents.find((a) => a.name === "sisyphus") const sisyphus = info.agents.find((a) => a.name === "sisyphus")
expect(sisyphus).toBeDefined() expect(sisyphus).toBeDefined()
expect(sisyphus!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-6") expect(sisyphus!.requirement.fallbackChain[0]?.model).toBe("claude-opus-4-7")
expect(sisyphus!.requirement.fallbackChain[0]?.providers).toContain("anthropic") expect(sisyphus!.requirement.fallbackChain[0]?.providers).toContain("anthropic")
}) })
@@ -42,7 +42,7 @@ describe("model-resolution check", () => {
// given: User has override for oracle agent // given: User has override for oracle agent
const mockConfig = { const mockConfig = {
agents: { agents: {
oracle: { model: "anthropic/claude-opus-4-6" }, oracle: { model: "anthropic/claude-opus-4-7" },
}, },
} }
@@ -51,8 +51,8 @@ describe("model-resolution check", () => {
// then: Oracle should show the override // then: Oracle should show the override
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("anthropic/claude-opus-4-6") expect(oracle!.userOverride).toBe("anthropic/claude-opus-4-7")
expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-6") expect(oracle!.effectiveResolution).toBe("User override: anthropic/claude-opus-4-7")
}) })
it("shows user override for category when configured", async () => { it("shows user override for category when configured", async () => {
@@ -169,13 +169,13 @@ describe("model-resolution check", () => {
const info = getModelResolutionInfoWithOverrides({ const info = getModelResolutionInfoWithOverrides({
agents: { agents: {
oracle: { model: "anthropic/claude-opus-4-6-thinking" }, oracle: { model: "anthropic/claude-opus-4-7-thinking" },
}, },
}) })
const oracle = info.agents.find((agent) => agent.name === "oracle") const oracle = info.agents.find((agent) => agent.name === "oracle")
expect(oracle).toBeDefined() expect(oracle).toBeDefined()
expect(oracle!.effectiveModel).toBe("anthropic/claude-opus-4-6-thinking") expect(oracle!.effectiveModel).toBe("anthropic/claude-opus-4-7-thinking")
expect(oracle!.capabilityDiagnostics).toMatchObject({ expect(oracle!.capabilityDiagnostics).toMatchObject({
resolutionMode: "alias-backed", resolutionMode: "alias-backed",
canonicalization: { canonicalization: {
+3 -3
View File
@@ -381,7 +381,7 @@ describe("generateModelConfig", () => {
const result = generateModelConfig(config) const result = generateModelConfig(config)
// #then // #then
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6") expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
}) })
test("Sisyphus is created when multiple fallback providers are available", () => { test("Sisyphus is created when multiple fallback providers are available", () => {
@@ -398,7 +398,7 @@ describe("generateModelConfig", () => {
const result = generateModelConfig(config) const result = generateModelConfig(config)
// #then // #then
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6") expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
}) })
test("Sisyphus resolves to gpt-5.4 medium when only OpenAI is available", () => { test("Sisyphus resolves to gpt-5.4 medium when only OpenAI is available", () => {
@@ -668,7 +668,7 @@ describe("generateModelConfig", () => {
const result = generateModelConfig(config) const result = generateModelConfig(config)
// #then should prefer native anthropic over gateway // #then should prefer native anthropic over gateway
expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-6") expect(result.agents?.sisyphus?.model).toBe("anthropic/claude-opus-4-7")
}) })
}) })
+23 -23
View File
@@ -5,16 +5,16 @@ import { transformModelForProvider as transformSharedModelForProvider } from "..
describe("transformModelForProvider", () => { describe("transformModelForProvider", () => {
describe("github-copilot provider", () => { describe("github-copilot provider", () => {
test("transforms claude-opus-4-6 to claude-opus-4.6", () => { test("transforms claude-opus-4-7 to claude-opus-4.7", () => {
// #given github-copilot provider and claude-opus-4-6 model // #given github-copilot provider and claude-opus-4-7 model
const provider = "github-copilot" const provider = "github-copilot"
const model = "claude-opus-4-6" const model = "claude-opus-4-7"
// #when transformModelForProvider is called // #when transformModelForProvider is called
const result = transformModelForProvider(provider, model) const result = transformModelForProvider(provider, model)
// #then should transform to claude-opus-4.6 // #then should transform to claude-opus-4.7
expect(result).toBe("claude-opus-4.6") expect(result).toBe("claude-opus-4.7")
}) })
test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => { test("transforms claude-sonnet-4-5 to claude-sonnet-4.5", () => {
@@ -152,29 +152,29 @@ describe("transformModelForProvider", () => {
}) })
test("does not transform claude models for google provider", () => { test("does not transform claude models for google provider", () => {
// #given google provider and claude-opus-4-6 model // #given google provider and claude-opus-4-7 model
const provider = "google" const provider = "google"
const model = "claude-opus-4-6" const model = "claude-opus-4-7"
// #when transformModelForProvider is called // #when transformModelForProvider is called
const result = transformModelForProvider(provider, model) const result = transformModelForProvider(provider, model)
// #then should pass through unchanged (google doesn't use claude) // #then should pass through unchanged (google doesn't use claude)
expect(result).toBe("claude-opus-4-6") expect(result).toBe("claude-opus-4-7")
}) })
}) })
describe("anthropic provider", () => { describe("anthropic provider", () => {
test("preserves hyphenated claude-opus-4-6 for config output (regression: installer must not write dotted IDs)", () => { test("preserves hyphenated claude-opus-4-7 for config output (regression: installer must not write dotted IDs)", () => {
// #given anthropic provider and claude-opus-4-6 model // #given anthropic provider and claude-opus-4-7 model
const provider = "anthropic" const provider = "anthropic"
const model = "claude-opus-4-6" const model = "claude-opus-4-7"
// #when transformModelForProvider is called // #when transformModelForProvider is called
const result = transformModelForProvider(provider, model) const result = transformModelForProvider(provider, model)
// #then should keep hyphenated form so Anthropic provider resolution succeeds on fresh installs // #then should keep hyphenated form so Anthropic provider resolution succeeds on fresh installs
expect(result).toBe("claude-opus-4-6") expect(result).toBe("claude-opus-4-7")
}) })
test("preserves hyphenated claude-sonnet-4-6 for config output", () => { test("preserves hyphenated claude-sonnet-4-6 for config output", () => {
@@ -204,12 +204,12 @@ describe("transformModelForProvider", () => {
describe("vercel provider", () => { describe("vercel provider", () => {
test("prepends anthropic/ and applies anthropic transform for claude models", () => { test("prepends anthropic/ and applies anthropic transform for claude models", () => {
// #given vercel provider and claude-opus-4-6 model // #given vercel provider and claude-opus-4-7 model
// #when transformModelForProvider is called // #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "claude-opus-4-6") const result = transformModelForProvider("vercel", "claude-opus-4-7")
// #then should produce anthropic/claude-opus-4.6 // #then should produce anthropic/claude-opus-4.7
expect(result).toBe("anthropic/claude-opus-4.6") expect(result).toBe("anthropic/claude-opus-4.7")
}) })
test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => { test("prepends anthropic/ and applies anthropic transform for claude-sonnet", () => {
@@ -267,12 +267,12 @@ describe("transformModelForProvider", () => {
}) })
test("delegates to sub-provider when model already has sub-provider prefix", () => { test("delegates to sub-provider when model already has sub-provider prefix", () => {
// #given vercel provider and anthropic/claude-opus-4-6 (already prefixed) // #given vercel provider and anthropic/claude-opus-4-7 (already prefixed)
// #when transformModelForProvider is called // #when transformModelForProvider is called
const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-6") const result = transformModelForProvider("vercel", "anthropic/claude-opus-4-7")
// #then should apply anthropic transform within the prefix // #then should apply anthropic transform within the prefix
expect(result).toBe("anthropic/claude-opus-4.6") expect(result).toBe("anthropic/claude-opus-4.7")
}) })
test("prepends minimax/ for minimax models", () => { test("prepends minimax/ for minimax models", () => {
@@ -340,14 +340,14 @@ describe("transformModelForProvider", () => {
test("uses a CLI-local transform implementation distinct from the shared runtime transform", () => { test("uses a CLI-local transform implementation distinct from the shared runtime transform", () => {
// #given the CLI transform (used by the installer) and the shared runtime transform // #given the CLI transform (used by the installer) and the shared runtime transform
const cliResult = transformModelForProvider("anthropic", "claude-opus-4-6") const cliResult = transformModelForProvider("anthropic", "claude-opus-4-7")
const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-6") const sharedResult = transformSharedModelForProvider("anthropic", "claude-opus-4-7")
// #when both are called with the same anthropic claude input // #when both are called with the same anthropic claude input
// #then the CLI preserves hyphenated form for config output, // #then the CLI preserves hyphenated form for config output,
// the shared runtime transform converts dash→dot for API calls // the shared runtime transform converts dash→dot for API calls
expect(transformModelForProvider).not.toBe(transformSharedModelForProvider) expect(transformModelForProvider).not.toBe(transformSharedModelForProvider)
expect(cliResult).toBe("claude-opus-4-6") expect(cliResult).toBe("claude-opus-4-7")
expect(sharedResult).toBe("claude-opus-4.6") expect(sharedResult).toBe("claude-opus-4.7")
}) })
}) })
+1 -1
View File
@@ -54,7 +54,7 @@ export function transformModelForProvider(provider: string, model: string): stri
} }
if (provider === "anthropic") { if (provider === "anthropic") {
// Installer writes hyphenated IDs (claude-opus-4-6) to the config. The // Installer writes hyphenated IDs (claude-opus-4-7) to the config. The
// runtime provider-model-id-transform converts dash→dot when calling the // runtime provider-model-id-transform converts dash→dot when calling the
// Anthropic API. Keeping the dotted form in the config breaks fresh // Anthropic API. Keeping the dotted form in the config breaks fresh
// installs with ProviderModelNotFoundError because Anthropic's provider // installs with ProviderModelNotFoundError because Anthropic's provider
+1 -1
View File
@@ -49,7 +49,7 @@ describe("refreshModelCapabilities", () => {
sourceUrl: "https://override.example/api.json", sourceUrl: "https://override.example/api.json",
models: { models: {
"gpt-5.4": { id: "gpt-5.4" }, "gpt-5.4": { id: "gpt-5.4" },
"claude-opus-4-6": { id: "claude-opus-4-6" }, "claude-opus-4-7": { id: "claude-opus-4-7" },
}, },
})) }))
let stdout = "" let stdout = ""
+11 -11
View File
@@ -98,7 +98,7 @@ describe("message.part.delta handling", () => {
sessionID: "ses_main", sessionID: "ses_main",
role: "assistant", role: "assistant",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
variant: "max", variant: "max",
}, },
}, },
@@ -113,7 +113,7 @@ describe("message.part.delta handling", () => {
//#then //#then
const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("") const rendered = stdoutSpy.mock.calls.map((call) => String(call[0] ?? "")).join("")
expect(rendered).toContain("\u001b[38;2;0;206;209m") expect(rendered).toContain("\u001b[38;2;0;206;209m")
expect(rendered).toContain("claude-opus-4-6 (max)") expect(rendered).toContain("claude-opus-4-7 (max)")
expect(rendered).toContain("└─") expect(rendered).toContain("└─")
expect(rendered).toContain("Sisyphus - Ultraworker") expect(rendered).toContain("Sisyphus - Ultraworker")
stdoutSpy.mockRestore() stdoutSpy.mockRestore()
@@ -128,7 +128,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -187,7 +187,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -242,7 +242,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -309,7 +309,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -353,7 +353,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6", variant: "max" }, info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7", variant: "max" },
}, },
}, },
{ {
@@ -388,7 +388,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_user", sessionID: "ses_main", role: "user", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { id: "msg_user", sessionID: "ses_main", role: "user", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -410,7 +410,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-6" }, info: { id: "msg_assistant", sessionID: "ses_main", role: "assistant", agent: "Sisyphus - Ultraworker", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -619,7 +619,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-6" }, info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
@@ -634,7 +634,7 @@ describe("message.part.delta handling", () => {
{ {
type: "message.updated", type: "message.updated",
properties: { properties: {
info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-6" }, info: { id: "msg_1", sessionID: "ses_main", role: "assistant", agent: "Sisyphus", modelID: "claude-opus-4-7" },
}, },
}, },
{ {
+1 -1
View File
@@ -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.4, etc." }, { value: "yes", label: "Yes", hint: "opencode/claude-opus-4-7, opencode/gpt-5.4, etc." },
], ],
initialValue: initial.opencodeZen, initialValue: initial.opencodeZen,
}) })
+1 -1
View File
@@ -44,7 +44,7 @@ Both must agree before marking a task complete. Prevents premature completion on
## CONCURRENCY MODEL ## CONCURRENCY MODEL
- Key format: `{providerID}/{modelID}` (e.g., `anthropic/claude-opus-4-6`) - Key format: `{providerID}/{modelID}` (e.g., `anthropic/claude-opus-4-7`)
- Default limit: 5 concurrent per key (configurable via `background_task` config) - Default limit: 5 concurrent per key (configurable via `background_task` config)
- FIFO queue: tasks wait in order when slots full - FIFO queue: tasks wait in order when slots full
- Slot released on: completion, error, cancellation - Slot released on: completion, error, cancellation
@@ -83,7 +83,7 @@ describe("findNearestMessageExcludingCompaction", () => {
// given // given
const message = { const message = {
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
} }
writeFileSync(join(tempDir, "001.json"), JSON.stringify(message)) writeFileSync(join(tempDir, "001.json"), JSON.stringify(message))
@@ -94,18 +94,18 @@ describe("findNearestMessageExcludingCompaction", () => {
expect(result).not.toBeNull() expect(result).not.toBeNull()
expect(result?.agent).toBe("sisyphus") expect(result?.agent).toBe("sisyphus")
expect(result?.model?.providerID).toBe("anthropic") expect(result?.model?.providerID).toBe("anthropic")
expect(result?.model?.modelID).toBe("claude-opus-4-6") expect(result?.model?.modelID).toBe("claude-opus-4-7")
}) })
test("skips compaction agent messages", () => { test("skips compaction agent messages", () => {
// given // given
const compactionMessage = { const compactionMessage = {
agent: "compaction", agent: "compaction",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
} }
const validMessage = { const validMessage = {
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
} }
writeFileSync(join(tempDir, "002.json"), JSON.stringify(compactionMessage)) writeFileSync(join(tempDir, "002.json"), JSON.stringify(compactionMessage))
writeFileSync(join(tempDir, "001.json"), JSON.stringify(validMessage)) writeFileSync(join(tempDir, "001.json"), JSON.stringify(validMessage))
@@ -125,12 +125,12 @@ describe("findNearestMessageExcludingCompaction", () => {
writeFileSync(join(tempDir, "002.json"), JSON.stringify({ writeFileSync(join(tempDir, "002.json"), JSON.stringify({
id: compactionMessageID, id: compactionMessageID,
agent: "atlas", agent: "atlas",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
})) }))
writeFileSync(join(tempDir, "001.json"), JSON.stringify({ writeFileSync(join(tempDir, "001.json"), JSON.stringify({
id: "msg_001", id: "msg_001",
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
})) }))
mkdirSync(partDir, { recursive: true }) mkdirSync(partDir, { recursive: true })
writeFileSync(join(partDir, "prt_0001.json"), JSON.stringify({ type: "compaction" })) writeFileSync(join(partDir, "prt_0001.json"), JSON.stringify({ type: "compaction" }))
@@ -94,7 +94,7 @@ describe("ConcurrencyManager.getConcurrencyLimit", () => {
// when // when
const modelLimit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-6") const modelLimit = manager.getConcurrencyLimit("anthropic/claude-sonnet-4-6")
const providerLimit = manager.getConcurrencyLimit("anthropic/claude-opus-4-6") const providerLimit = manager.getConcurrencyLimit("anthropic/claude-opus-4-7")
const defaultLimit = manager.getConcurrencyLimit("google/gemini-3.1-pro") const defaultLimit = manager.getConcurrencyLimit("google/gemini-3.1-pro")
// then // then
+28 -28
View File
@@ -855,7 +855,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
{ {
info: { info: {
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
}, },
}, },
{ {
@@ -890,7 +890,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
//#then //#then
expect(capturedBody?.agent).toBe("sisyphus") expect(capturedBody?.agent).toBe("sisyphus")
expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" }) expect(capturedBody?.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
manager.shutdown() manager.shutdown()
}) })
@@ -913,7 +913,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
} }
const currentMessage: CurrentMessage = { const currentMessage: CurrentMessage = {
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
// when // when
@@ -921,7 +921,7 @@ describe("BackgroundManager.notifyParentSession - dynamic message lookup", () =>
// then - uses currentMessage values, not task.parentModel/parentAgent // then - uses currentMessage values, not task.parentModel/parentAgent
expect(promptBody.agent).toBe("sisyphus") expect(promptBody.agent).toBe("sisyphus")
expect(promptBody.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" }) expect(promptBody.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
}) })
test("should fallback to parentAgent when currentMessage.agent is undefined", async () => { test("should fallback to parentAgent when currentMessage.agent is undefined", async () => {
@@ -1155,7 +1155,7 @@ describe("BackgroundManager.notifyParentSession - notifications toggle", () => {
agent: "explore", agent: "explore",
model: { model: {
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
variant: "high", variant: "high",
}, },
}, },
@@ -1211,7 +1211,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
agent: "explore", agent: "explore",
model: { model: {
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
variant: "max", variant: "max",
}, },
}, },
@@ -1231,7 +1231,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
status: "completed", status: "completed",
startedAt: new Date(), startedAt: new Date(),
completedAt: new Date(), completedAt: new Date(),
model: { providerID: "anthropic", modelID: "claude-opus-4.6", variant: "high" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7", variant: "high" },
} }
getPendingByParent(manager).set("session-parent", new Set([task.id])) getPendingByParent(manager).set("session-parent", new Set([task.id]))
@@ -1272,7 +1272,7 @@ describe("BackgroundManager.notifyParentSession - variant propagation", () => {
status: "completed", status: "completed",
startedAt: new Date(), startedAt: new Date(),
completedAt: new Date(), completedAt: new Date(),
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
getPendingByParent(manager).set("session-parent", new Set([task.id])) getPendingByParent(manager).set("session-parent", new Set([task.id]))
@@ -1349,7 +1349,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
test("should release concurrency and clear key on completion", async () => { test("should release concurrency and clear key on completion", async () => {
// given // given
const concurrencyKey = "anthropic/claude-opus-4.6" const concurrencyKey = "anthropic/claude-opus-4.7"
const concurrencyManager = getConcurrencyManager(manager) const concurrencyManager = getConcurrencyManager(manager)
await concurrencyManager.acquire(concurrencyKey) await concurrencyManager.acquire(concurrencyKey)
@@ -1378,7 +1378,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
test("should prevent double completion and double release", async () => { test("should prevent double completion and double release", async () => {
// given // given
const concurrencyKey = "anthropic/claude-opus-4.6" const concurrencyKey = "anthropic/claude-opus-4.7"
const concurrencyManager = getConcurrencyManager(manager) const concurrencyManager = getConcurrencyManager(manager)
await concurrencyManager.acquire(concurrencyKey) await concurrencyManager.acquire(concurrencyKey)
@@ -1508,7 +1508,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
test("should release task concurrencyKey when startTask throws after assigning it", async () => { test("should release task concurrencyKey when startTask throws after assigning it", async () => {
// given // given
const concurrencyKey = "anthropic/claude-opus-4.6" const concurrencyKey = "anthropic/claude-opus-4.7"
const concurrencyManager = getConcurrencyManager(manager) const concurrencyManager = getConcurrencyManager(manager)
const task = createMockTask({ const task = createMockTask({
@@ -1524,7 +1524,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
agent: task.agent, agent: task.agent,
parentSessionID: task.parentSessionID, parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID, parentMessageID: task.parentMessageID,
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
getTaskMap(manager).set(task.id, task) getTaskMap(manager).set(task.id, task)
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }]) getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
@@ -1544,7 +1544,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
test("should mark task as error when startTask throws after session creation", async () => { test("should mark task as error when startTask throws after session creation", async () => {
//#given - startTask creates session but fails before sending prompt //#given - startTask creates session but fails before sending prompt
const concurrencyKey = "anthropic/claude-opus-4.6" const concurrencyKey = "anthropic/claude-opus-4.7"
const task = createMockTask({ const task = createMockTask({
id: "task-zombie-session", id: "task-zombie-session",
@@ -1561,7 +1561,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
agent: task.agent, agent: task.agent,
parentSessionID: task.parentSessionID, parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID, parentMessageID: task.parentMessageID,
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
getTaskMap(manager).set(task.id, task) getTaskMap(manager).set(task.id, task)
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }]) getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
@@ -1585,7 +1585,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
test("should release queue slot when queued task is already interrupt", async () => { test("should release queue slot when queued task is already interrupt", async () => {
// given // given
const concurrencyKey = "anthropic/claude-opus-4.6" const concurrencyKey = "anthropic/claude-opus-4.7"
const concurrencyManager = getConcurrencyManager(manager) const concurrencyManager = getConcurrencyManager(manager)
const task = createMockTask({ const task = createMockTask({
@@ -1601,7 +1601,7 @@ describe("BackgroundManager.tryCompleteTask", () => {
agent: task.agent, agent: task.agent,
parentSessionID: task.parentSessionID, parentSessionID: task.parentSessionID,
parentMessageID: task.parentMessageID, parentMessageID: task.parentMessageID,
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
getTaskMap(manager).set(task.id, task) getTaskMap(manager).set(task.id, task)
getQueuesByKey(manager).set(concurrencyKey, [{ task, input }]) getQueuesByKey(manager).set(concurrencyKey, [{ task, input }])
@@ -2104,7 +2104,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
agent: "test-agent", agent: "test-agent",
parentSessionID: "parent-session", parentSessionID: "parent-session",
parentMessageID: "parent-message", parentMessageID: "parent-message",
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
} }
const launchInputWithoutModel = { const launchInputWithoutModel = {
description: "Test task without model", description: "Test task without model",
@@ -2124,7 +2124,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
expect(taskWithModel.status).toBe("pending") expect(taskWithModel.status).toBe("pending")
expect(taskWithoutModel.status).toBe("pending") expect(taskWithoutModel.status).toBe("pending")
expect(promptBodies).toHaveLength(2) expect(promptBodies).toHaveLength(2)
expect(promptBodies[0].model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.6" }) expect(promptBodies[0].model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4.7" })
expect(promptBodies[0].agent).toBe("test-agent") expect(promptBodies[0].agent).toBe("test-agent")
expect(promptBodies[1].agent).toBe("test-agent") expect(promptBodies[1].agent).toBe("test-agent")
expect("model" in promptBodies[1]).toBe(false) expect("model" in promptBodies[1]).toBe(false)
@@ -3245,7 +3245,7 @@ describe("BackgroundManager - Non-blocking Queue Integration", () => {
description: "Task 1", description: "Task 1",
prompt: "Do something", prompt: "Do something",
agent: "test-agent", agent: "test-agent",
model: { providerID: "anthropic", modelID: "claude-opus-4.6" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7" },
parentSessionID: "parent-session", parentSessionID: "parent-session",
parentMessageID: "parent-message", parentMessageID: "parent-message",
} }
@@ -4225,7 +4225,7 @@ describe("BackgroundManager.handleEvent - session.deleted cascade", () => {
describe("BackgroundManager.handleEvent - session.error", () => { describe("BackgroundManager.handleEvent - session.error", () => {
const defaultRetryFallbackChain = [ const defaultRetryFallbackChain = [
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" }, { providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
{ providers: ["anthropic"], model: "gpt-5.3-codex", variant: "high" }, { providers: ["anthropic"], model: "gpt-5.3-codex", variant: "high" },
] ]
@@ -4249,7 +4249,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
agent: "sisyphus", agent: "sisyphus",
status: "running", status: "running",
concurrencyKey: input.concurrencyKey, concurrencyKey: input.concurrencyKey,
model: { providerID: "anthropic", modelID: "claude-opus-4.6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4.7-thinking" },
fallbackChain: input.fallbackChain ?? defaultRetryFallbackChain, fallbackChain: input.fallbackChain ?? defaultRetryFallbackChain,
attemptCount: 0, attemptCount: 0,
}) })
@@ -4394,7 +4394,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
//#given //#given
const manager = createBackgroundManager() const manager = createBackgroundManager()
const concurrencyManager = getConcurrencyManager(manager) const concurrencyManager = getConcurrencyManager(manager)
const concurrencyKey = "anthropic/claude-opus-4.6-thinking" const concurrencyKey = "anthropic/claude-opus-4.7-thinking"
await concurrencyManager.acquire(concurrencyKey) await concurrencyManager.acquire(concurrencyKey)
stubProcessKey(manager) stubProcessKey(manager)
@@ -4406,7 +4406,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
description: "task that should retry", description: "task that should retry",
concurrencyKey, concurrencyKey,
fallbackChain: [ fallbackChain: [
{ providers: ["anthropic"], model: "claude-opus-4-6", variant: "max" }, { providers: ["anthropic"], model: "claude-opus-4-7", variant: "max" },
{ providers: ["anthropic"], model: "claude-opus-4-5", variant: "max" }, { providers: ["anthropic"], model: "claude-opus-4-5", variant: "max" },
], ],
}) })
@@ -4420,7 +4420,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
name: "UnknownError", name: "UnknownError",
data: { data: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.7-thinking\"}}",
}, },
}, },
}, },
@@ -4431,7 +4431,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
expect(task.attemptCount).toBe(1) expect(task.attemptCount).toBe(1)
expect(task.model).toEqual({ expect(task.model).toEqual({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
variant: "max", variant: "max",
}) })
expect(task.concurrencyKey).toBeUndefined() expect(task.concurrencyKey).toBeUndefined()
@@ -4469,7 +4469,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
expect(task.attemptCount).toBe(1) expect(task.attemptCount).toBe(1)
expect(task.model).toEqual({ expect(task.model).toEqual({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
variant: "max", variant: "max",
}) })
@@ -4497,7 +4497,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
name: "UnknownError", name: "UnknownError",
data: { data: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4.7-thinking\"}}",
}, },
}, },
} }
@@ -4514,7 +4514,7 @@ describe("BackgroundManager.handleEvent - session.error", () => {
expect(task.attemptCount).toBe(1) expect(task.attemptCount).toBe(1)
expect(task.model).toEqual({ expect(task.model).toEqual({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
variant: "max", variant: "max",
}) })
@@ -648,7 +648,7 @@ describe("checkAndInterruptStaleTasks", () => {
const task = createRunningTask({ const task = createRunningTask({
startedAt: new Date(Date.now() - 15 * 60 * 1000), startedAt: new Date(Date.now() - 15 * 60 * 1000),
progress: undefined, progress: undefined,
concurrencyKey: "anthropic/claude-opus-4-6", concurrencyKey: "anthropic/claude-opus-4-7",
}) })
//#when //#when
@@ -661,7 +661,7 @@ describe("checkAndInterruptStaleTasks", () => {
}) })
//#then //#then
expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-6") expect(releaseMock).toHaveBeenCalledWith("anthropic/claude-opus-4-7")
expect(task.concurrencyKey).toBeUndefined() expect(task.concurrencyKey).toBeUndefined()
}) })
@@ -23,8 +23,8 @@ describe("mapClaudeModelToOpenCode", () => {
expect(mapClaudeModelToOpenCode("sonnet")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-6" }) expect(mapClaudeModelToOpenCode("sonnet")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-6" })
}) })
it("#when called with opus #then maps to anthropic claude-opus-4-6 object", () => { it("#when called with opus #then maps to anthropic claude-opus-4-7 object", () => {
expect(mapClaudeModelToOpenCode("opus")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) expect(mapClaudeModelToOpenCode("opus")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
}) })
it("#when called with haiku #then maps to anthropic claude-haiku-4-5 object", () => { it("#when called with haiku #then maps to anthropic claude-haiku-4-5 object", () => {
@@ -47,8 +47,8 @@ describe("mapClaudeModelToOpenCode", () => {
expect(mapClaudeModelToOpenCode("claude-sonnet-4-5-20250514")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-5-20250514" }) expect(mapClaudeModelToOpenCode("claude-sonnet-4-5-20250514")).toEqual({ providerID: "anthropic", modelID: "claude-sonnet-4-5-20250514" })
}) })
it("#when called with claude-opus-4-6 #then adds anthropic object format", () => { it("#when called with claude-opus-4-7 #then adds anthropic object format", () => {
expect(mapClaudeModelToOpenCode("claude-opus-4-6")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) expect(mapClaudeModelToOpenCode("claude-opus-4-7")).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
}) })
it("#when called with claude-haiku-4-5-20251001 #then adds anthropic object format", () => { it("#when called with claude-haiku-4-5-20251001 #then adds anthropic object format", () => {
@@ -5,7 +5,7 @@ const ANTHROPIC_PREFIX = "anthropic/"
const CLAUDE_CODE_ALIAS_MAP = new Map<string, string>([ const CLAUDE_CODE_ALIAS_MAP = new Map<string, string>([
["sonnet", `${ANTHROPIC_PREFIX}claude-sonnet-4-6`], ["sonnet", `${ANTHROPIC_PREFIX}claude-sonnet-4-6`],
["opus", `${ANTHROPIC_PREFIX}claude-opus-4-6`], ["opus", `${ANTHROPIC_PREFIX}claude-opus-4-7`],
["haiku", `${ANTHROPIC_PREFIX}claude-haiku-4-5`], ["haiku", `${ANTHROPIC_PREFIX}claude-haiku-4-5`],
]) ])
@@ -38,7 +38,7 @@ describe("readOpencodeConfigAgents", () => {
agents: { agents: {
"my-agent": { "my-agent": {
description: "Custom agent", description: "Custom agent",
model: "claude-opus-4-6", model: "claude-opus-4-7",
mode: "subagent", mode: "subagent",
prompt: "You are a helpful assistant", prompt: "You are a helpful assistant",
}, },
@@ -203,7 +203,7 @@ describe("TaskToastManager", () => {
description: "Task with inherited model", description: "Task with inherited model",
agent: "sisyphus-junior", agent: "sisyphus-junior",
isBackground: false, isBackground: false,
modelInfo: { model: "cliproxy/claude-opus-4-6", type: "inherited" as const }, modelInfo: { model: "cliproxy/claude-opus-4-7", type: "inherited" as const },
} }
// when - addTask is called // when - addTask is called
@@ -213,7 +213,7 @@ describe("TaskToastManager", () => {
expect(mockClient.tui.showToast).toHaveBeenCalled() expect(mockClient.tui.showToast).toHaveBeenCalled()
const call = mockClient.tui.showToast.mock.calls[0][0] const call = mockClient.tui.showToast.mock.calls[0][0]
expect(call.body.message).toContain("[FALLBACK]") expect(call.body.message).toContain("[FALLBACK]")
expect(call.body.message).toContain("cliproxy/claude-opus-4-6") expect(call.body.message).toContain("cliproxy/claude-opus-4-7")
expect(call.body.message).toContain("(inherited from parent)") expect(call.body.message).toContain("(inherited from parent)")
}) })
File diff suppressed because it is too large Load Diff
@@ -84,7 +84,7 @@ describe("executeCompact lock management", () => {
let pluginConfig: ReturnType<typeof OhMyOpenCodeConfigSchema.parse> let pluginConfig: ReturnType<typeof OhMyOpenCodeConfigSchema.parse>
const sessionID = "test-session-123" const sessionID = "test-session-123"
const directory = "/test/dir" const directory = "/test/dir"
const msg = { providerID: "anthropic", modelID: "claude-opus-4-6" } const msg = { providerID: "anthropic", modelID: "claude-opus-4-7" }
beforeEach(() => { beforeEach(() => {
// given: Fresh state for each test // given: Fresh state for each test
@@ -132,7 +132,7 @@ describe("executeCompact lock management", () => {
expect(mockClient.session.summarize).toHaveBeenCalledWith( expect(mockClient.session.summarize).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
path: { id: sessionID }, path: { id: sessionID },
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
}), }),
) )
@@ -157,7 +157,7 @@ describe("executeCompact lock management", () => {
expect(mockClient.session.summarize).toHaveBeenCalledWith( expect(mockClient.session.summarize).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
path: { id: sessionID }, path: { id: sessionID },
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
}), }),
) )
@@ -352,7 +352,7 @@ describe("executeCompact lock management", () => {
expect(mockClient.session.summarize).toHaveBeenCalledWith( expect(mockClient.session.summarize).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
path: { id: sessionID }, path: { id: sessionID },
body: { providerID: "anthropic", modelID: "claude-opus-4-6", auto: true }, body: { providerID: "anthropic", modelID: "claude-opus-4-7", auto: true },
}), }),
) )
+7 -7
View File
@@ -29,7 +29,7 @@ function createMockParams(overrides: {
existingOptions?: Record<string, unknown> existingOptions?: Record<string, unknown>
}): { input: ChatParamsInput; output: ChatParamsOutput } { }): { input: ChatParamsInput; output: ChatParamsOutput } {
const providerID = overrides.providerID ?? "anthropic" const providerID = overrides.providerID ?? "anthropic"
const modelID = overrides.modelID ?? "claude-opus-4-6" const modelID = overrides.modelID ?? "claude-opus-4-7"
const variant = "variant" in overrides ? overrides.variant : "max" const variant = "variant" in overrides ? overrides.variant : "max"
const agentName = overrides.agentName ?? "sisyphus" const agentName = overrides.agentName ?? "sisyphus"
const existingOptions = overrides.existingOptions ?? {} const existingOptions = overrides.existingOptions ?? {}
@@ -71,7 +71,7 @@ describe("createAnthropicEffortHook", () => {
it("injects effort max for dotted opus ids", async () => { it("injects effort max for dotted opus ids", async () => {
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ modelID: "claude-opus-4.6" }) const { input, output } = createMockParams({ modelID: "claude-opus-4.7" })
await hook["chat.params"](input, output) await hook["chat.params"](input, output)
@@ -158,7 +158,7 @@ describe("createAnthropicEffortHook", () => {
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ const { input, output } = createMockParams({
providerID: "github-copilot", providerID: "github-copilot",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
}) })
// when // when
@@ -256,7 +256,7 @@ describe("createAnthropicEffortHook", () => {
// given an Anthropic OAuth session and variant=max on an Opus model // given an Anthropic OAuth session and variant=max on an Opus model
writeAuthFile({ anthropic: { type: "oauth" } }) writeAuthFile({ anthropic: { type: "oauth" } })
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ modelID: "claude-opus-4-6" }) const { input, output } = createMockParams({ modelID: "claude-opus-4-7" })
// when chat.params fires // when chat.params fires
await hook["chat.params"](input, output) await hook["chat.params"](input, output)
@@ -270,7 +270,7 @@ describe("createAnthropicEffortHook", () => {
// given an Anthropic OAuth session and a dotted opus id // given an Anthropic OAuth session and a dotted opus id
writeAuthFile({ anthropic: { type: "oauth" } }) writeAuthFile({ anthropic: { type: "oauth" } })
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ modelID: "claude-opus-4.6" }) const { input, output } = createMockParams({ modelID: "claude-opus-4.7" })
// when chat.params fires // when chat.params fires
await hook["chat.params"](input, output) await hook["chat.params"](input, output)
@@ -284,7 +284,7 @@ describe("createAnthropicEffortHook", () => {
// given an Anthropic API-key session (not OAuth) // given an Anthropic API-key session (not OAuth)
writeAuthFile({ anthropic: { type: "api", key: "sk-ant-xxx" } }) writeAuthFile({ anthropic: { type: "api", key: "sk-ant-xxx" } })
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ modelID: "claude-opus-4-6" }) const { input, output } = createMockParams({ modelID: "claude-opus-4-7" })
// when chat.params fires // when chat.params fires
await hook["chat.params"](input, output) await hook["chat.params"](input, output)
@@ -298,7 +298,7 @@ describe("createAnthropicEffortHook", () => {
// given OAuth entries for unrelated providers only // given OAuth entries for unrelated providers only
writeAuthFile({ "github-copilot": { type: "oauth" }, opencode: { type: "api", key: "sk-x" } }) writeAuthFile({ "github-copilot": { type: "oauth" }, opencode: { type: "api", key: "sk-x" } })
const hook = createAnthropicEffortHook() const hook = createAnthropicEffortHook()
const { input, output } = createMockParams({ modelID: "claude-opus-4-6", providerID: "anthropic" }) const { input, output } = createMockParams({ modelID: "claude-opus-4-7", providerID: "anthropic" })
// when chat.params fires for the anthropic provider // when chat.params fires for the anthropic provider
await hook["chat.params"](input, output) await hook["chat.params"](input, output)
@@ -58,7 +58,7 @@ describe("atlas hook compaction agent filtering", () => {
join(messageDir, fileName), join(messageDir, fileName),
JSON.stringify({ JSON.stringify({
agent, agent,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}), }),
) )
} }
@@ -91,7 +91,7 @@ describe("Atlas final-wave approval gate regressions", () => {
join(messageDirectory, "msg_test001.json"), join(messageDirectory, "msg_test001.json"),
JSON.stringify({ JSON.stringify({
agent: "atlas", agent: "atlas",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}), }),
) )
} }
@@ -99,7 +99,7 @@ describe("Atlas final verification approval gate", () => {
join(messageDirectory, "msg_test001.json"), join(messageDirectory, "msg_test001.json"),
JSON.stringify({ JSON.stringify({
agent: "atlas", agent: "atlas",
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}), }),
) )
} }
+1 -1
View File
@@ -79,7 +79,7 @@ describe("atlas hook", () => {
} }
const messageData = { const messageData = {
agent, agent,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
} }
writeFileSync(join(messageDir, "msg_test001.json"), JSON.stringify(messageData)) writeFileSync(join(messageDir, "msg_test001.json"), JSON.stringify(messageData))
} }
+19 -19
View File
@@ -24,7 +24,7 @@ const selectFallbackProviderMock = mock((providers: string[], preferredProviderI
const transformModelForProviderMock = mock((provider: string, model: string) => { const transformModelForProviderMock = mock((provider: string, model: string) => {
if (provider === "github-copilot") { if (provider === "github-copilot") {
return model return model
.replace("claude-opus-4-6", "claude-opus-4.6") .replace("claude-opus-4-7", "claude-opus-4.7")
.replace("claude-sonnet-4-6", "claude-sonnet-4.6") .replace("claude-sonnet-4-6", "claude-sonnet-4.6")
.replace("claude-sonnet-4-5", "claude-sonnet-4.5") .replace("claude-sonnet-4-5", "claude-sonnet-4.5")
.replace("claude-haiku-4-5", "claude-haiku-4.5") .replace("claude-haiku-4-5", "claude-haiku-4.5")
@@ -96,13 +96,13 @@ describe("model fallback hook", () => {
"ses_model_fallback_main", "ses_model_fallback_main",
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"anthropic", "anthropic",
"claude-opus-4-6-thinking", "claude-opus-4-7-thinking",
) )
expect(set).toBe(true) expect(set).toBe(true)
const output = { const output = {
message: { message: {
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
variant: "max", variant: "max",
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
@@ -117,7 +117,7 @@ describe("model fallback hook", () => {
//#then //#then
expect(output.message["model"]).toEqual({ expect(output.message["model"]).toEqual({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
}) })
}) })
@@ -132,12 +132,12 @@ describe("model fallback hook", () => {
const sessionID = "ses_model_fallback_main" const sessionID = "ses_model_fallback_main"
expect( expect(
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6-thinking"), setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7-thinking"),
).toBe(true) ).toBe(true)
const firstOutput = { const firstOutput = {
message: { message: {
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
variant: "max", variant: "max",
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
@@ -149,17 +149,17 @@ describe("model fallback hook", () => {
//#then //#then
expect(firstOutput.message["model"]).toEqual({ expect(firstOutput.message["model"]).toEqual({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
}) })
//#when - second error re-arms fallback and should advance to next entry //#when - second error re-arms fallback and should advance to next entry
expect( expect(
setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-6"), setPendingModelFallback(sessionID, "Sisyphus - Ultraworker", "anthropic", "claude-opus-4-7"),
).toBe(true) ).toBe(true)
const secondOutput = { const secondOutput = {
message: { message: {
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
} }
@@ -183,13 +183,13 @@ describe("model fallback hook", () => {
sessionID, sessionID,
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"anthropic", "anthropic",
"claude-opus-4-6-thinking", "claude-opus-4-7-thinking",
) )
const secondSet = setPendingModelFallback( const secondSet = setPendingModelFallback(
sessionID, sessionID,
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"anthropic", "anthropic",
"claude-opus-4-6-thinking", "claude-opus-4-7-thinking",
) )
//#then //#then
@@ -211,7 +211,7 @@ describe("model fallback hook", () => {
} }
setSessionFallbackChain(sessionID, [ setSessionFallbackChain(sessionID, [
{ providers: ["anthropic"], model: "claude-opus-4-6" }, { providers: ["anthropic"], model: "claude-opus-4-7" },
{ providers: ["opencode"], model: "kimi-k2.5-free" }, { providers: ["opencode"], model: "kimi-k2.5-free" },
]) ])
@@ -220,13 +220,13 @@ describe("model fallback hook", () => {
sessionID, sessionID,
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"anthropic", "anthropic",
"claude-opus-4-6", "claude-opus-4-7",
), ),
).toBe(true) ).toBe(true)
const output = { const output = {
message: { message: {
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
} }
@@ -255,7 +255,7 @@ describe("model fallback hook", () => {
} }
setSessionFallbackChain(sessionID, [ setSessionFallbackChain(sessionID, [
{ providers: ["quotio"], model: "claude-opus-4-6", variant: "max" }, { providers: ["quotio"], model: "claude-opus-4-7", variant: "max" },
{ providers: ["quotio"], model: "gpt-5.2" }, { providers: ["quotio"], model: "gpt-5.2" },
]) ])
@@ -264,13 +264,13 @@ describe("model fallback hook", () => {
sessionID, sessionID,
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"quotio", "quotio",
"claude-opus-4-6", "claude-opus-4-7",
), ),
).toBe(true) ).toBe(true)
const output = { const output = {
message: { message: {
model: { providerID: "quotio", modelID: "claude-opus-4-6" }, model: { providerID: "quotio", modelID: "claude-opus-4-7" },
variant: "max", variant: "max",
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
@@ -369,13 +369,13 @@ describe("model fallback hook", () => {
"ses_model_fallback_toast", "ses_model_fallback_toast",
"Sisyphus - Ultraworker", "Sisyphus - Ultraworker",
"anthropic", "anthropic",
"claude-opus-4-6-thinking", "claude-opus-4-7-thinking",
) )
expect(set).toBe(true) expect(set).toBe(true)
const output = { const output = {
message: { message: {
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
variant: "max", variant: "max",
}, },
parts: [{ type: "text", text: "continue" }], parts: [{ type: "text", text: "continue" }],
@@ -30,12 +30,12 @@ describe("no-hephaestus-non-gpt hook", () => {
await hook["chat.message"]?.({ await hook["chat.message"]?.({
sessionID: "ses_1", sessionID: "ses_1",
agent: HEPHAESTUS_DISPLAY, agent: HEPHAESTUS_DISPLAY,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output1) }, output1)
await hook["chat.message"]?.({ await hook["chat.message"]?.({
sessionID: "ses_1", sessionID: "ses_1",
agent: HEPHAESTUS_DISPLAY, agent: HEPHAESTUS_DISPLAY,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output2) }, output2)
// then - toast is shown and agent is switched to sisyphus // then - toast is shown and agent is switched to sisyphus
@@ -66,7 +66,7 @@ describe("no-hephaestus-non-gpt hook", () => {
await hook["chat.message"]?.({ await hook["chat.message"]?.({
sessionID: "ses_opt_out", sessionID: "ses_opt_out",
agent: HEPHAESTUS_DISPLAY, agent: HEPHAESTUS_DISPLAY,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output) }, output)
// then - warning toast is shown but agent is not switched // then - warning toast is shown but agent is not switched
@@ -114,7 +114,7 @@ describe("no-hephaestus-non-gpt hook", () => {
await hook["chat.message"]?.({ await hook["chat.message"]?.({
sessionID: "ses_3", sessionID: "ses_3",
agent: SISYPHUS_DISPLAY, agent: SISYPHUS_DISPLAY,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output) }, output)
// then - no toast // then - no toast
@@ -136,7 +136,7 @@ describe("no-hephaestus-non-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: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output) }, output)
// then - toast shown via session-agent fallback, switched to sisyphus // then - toast shown via session-agent fallback, switched to sisyphus
+1 -1
View File
@@ -83,7 +83,7 @@ describe("no-sisyphus-gpt hook", () => {
await hook["chat.message"]?.({ await hook["chat.message"]?.({
sessionID: "ses_2", sessionID: "ses_2",
agent: SISYPHUS_DISPLAY, agent: SISYPHUS_DISPLAY,
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, output) }, output)
// then - no toast // then - no toast
+2 -2
View File
@@ -125,10 +125,10 @@ describe("createRuntimeFallbackHook dispose", () => {
const fallbackTimeout = setTimeout(() => {}, 60_000) const fallbackTimeout = setTimeout(() => {}, 60_000)
capturedDeps?.sessionStates.set("session-1", { capturedDeps?.sessionStates.set("session-1", {
originalModel: "anthropic/claude-opus-4-6", originalModel: "anthropic/claude-opus-4-7",
currentModel: "openai/gpt-5.4", currentModel: "openai/gpt-5.4",
fallbackIndex: 1, fallbackIndex: 1,
failedModels: new Map([["anthropic/claude-opus-4-6", 1]]), failedModels: new Map([["anthropic/claude-opus-4-7", 1]]),
attemptCount: 1, attemptCount: 1,
}) })
capturedDeps?.sessionLastAccess.set("session-1", Date.now()) capturedDeps?.sessionLastAccess.set("session-1", Date.now())
@@ -7,7 +7,7 @@ describe("runtime-fallback error classifier", () => {
//#given //#given
const info = { const info = {
status: status:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
} }
//#when //#when
@@ -21,7 +21,7 @@ describe("runtime-fallback error classifier", () => {
//#given //#given
const info = { const info = {
status: status:
"All credentials for model claude-opus-4-6 are cooldown [retrying in 7m 56s attempt #1]", "All credentials for model claude-opus-4-7 are cooldown [retrying in 7m 56s attempt #1]",
} }
//#when //#when
@@ -49,7 +49,7 @@ describe("runtime-fallback error classifier", () => {
//#given //#given
const error = { const error = {
message: message:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
} }
//#when //#when
@@ -65,8 +65,8 @@ describe("runtime-fallback error classifier", () => {
name: "ProviderModelNotFoundError", name: "ProviderModelNotFoundError",
data: { data: {
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
message: "Model not found: anthropic/claude-opus-4-6.", message: "Model not found: anthropic/claude-opus-4-7.",
}, },
} }
@@ -15,7 +15,7 @@ describe("runtime-fallback fallback-models", () => {
const pluginConfig = { const pluginConfig = {
categories: { categories: {
quick: { quick: {
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"], fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
}, },
}, },
} as any } as any
@@ -24,7 +24,7 @@ describe("runtime-fallback fallback-models", () => {
const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig) const result = getFallbackModelsForSession(sessionID, undefined, pluginConfig)
//#then //#then
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-6"]) expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-7"])
}) })
test("uses agent-specific fallback_models when agent is resolved", () => { test("uses agent-specific fallback_models when agent is resolved", () => {
@@ -32,7 +32,7 @@ describe("runtime-fallback fallback-models", () => {
const pluginConfig = { const pluginConfig = {
agents: { agents: {
oracle: { oracle: {
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"], fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
}, },
}, },
} as any } as any
@@ -41,7 +41,7 @@ describe("runtime-fallback fallback-models", () => {
const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig) const result = getFallbackModelsForSession("ses_runtime_fallback_agent", "oracle", pluginConfig)
//#then //#then
expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-6"]) expect(result).toEqual(["openai/gpt-5.2", "anthropic/claude-opus-4-7"])
}) })
test("does not fall back to another agent chain when agent cannot be resolved", () => { test("does not fall back to another agent chain when agent cannot be resolved", () => {
@@ -52,7 +52,7 @@ describe("runtime-fallback fallback-models", () => {
fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"], fallback_models: ["quotio/gpt-5.2", "quotio/glm-5", "quotio/kimi-k2.5"],
}, },
oracle: { oracle: {
fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-6"], fallback_models: ["openai/gpt-5.2", "anthropic/claude-opus-4-7"],
}, },
}, },
} as any } as any
@@ -51,7 +51,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
@@ -63,7 +63,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
status: { status: {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]", message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
}, },
}, },
}, },
@@ -77,7 +77,7 @@ describe("createRuntimeFallbackHook dispose retry-key cleanup", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
await hook.event(retryEvent) await hook.event(retryEvent)
+73 -73
View File
@@ -329,7 +329,7 @@ describe("runtime-fallback", () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), { const hook = createRuntimeFallbackHook(createMockPluginInput(), {
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.7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
}) })
@@ -365,14 +365,14 @@ describe("runtime-fallback", () => {
type: "session.error", type: "session.error",
properties: { properties: {
sessionID, sessionID,
error: { name: "UnknownError", data: { message: "Model not found: anthropic/claude-opus-4.6." } }, error: { name: "UnknownError", data: { message: "Model not found: anthropic/claude-opus-4.7." } },
}, },
}, },
}) })
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.4" }) expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.7", 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
@@ -384,7 +384,7 @@ describe("runtime-fallback", () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), { const hook = createRuntimeFallbackHook(createMockPluginInput(), {
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.7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
}) })
@@ -421,8 +421,8 @@ describe("runtime-fallback", () => {
name: "ProviderModelNotFoundError", name: "ProviderModelNotFoundError",
data: { data: {
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4.6", modelID: "claude-opus-4.7",
message: "Model not found: anthropic/claude-opus-4.6.", message: "Model not found: anthropic/claude-opus-4.7.",
}, },
}, },
}, },
@@ -431,7 +431,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.4" }) expect(fallbackLogs[1]?.data).toMatchObject({ from: "anthropic/claude-opus-4.7", to: "openai/gpt-5.4" })
}) })
test("should bootstrap session.error fallback from session category model and preserve variant", async () => { test("should bootstrap session.error fallback from session category model and preserve variant", async () => {
@@ -500,7 +500,7 @@ describe("runtime-fallback", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "github-copilot/claude-opus-4.6" } }, properties: { info: { id: sessionID, model: "github-copilot/claude-opus-4.7" } },
}, },
}) })
@@ -511,7 +511,7 @@ describe("runtime-fallback", () => {
info: { info: {
sessionID, sessionID,
role: "assistant", role: "assistant",
model: "github-copilot/claude-opus-4.6", model: "github-copilot/claude-opus-4.7",
status: status:
"Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]", "Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]",
}, },
@@ -524,13 +524,13 @@ 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.4" }) expect(fallbackLog?.data).toMatchObject({ from: "github-copilot/claude-opus-4.7", 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 () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), { const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]), pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
}) })
const sessionID = "test-session-openai-auto-retry" const sessionID = "test-session-openai-auto-retry"
@@ -562,7 +562,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: "openai/gpt-5.3-codex", to: "anthropic/claude-opus-4-6" }) expect(fallbackLog?.data).toMatchObject({ from: "openai/gpt-5.3-codex", to: "anthropic/claude-opus-4-7" })
}) })
test("should trigger fallback on auto-retry signal in assistant text parts", async () => { test("should trigger fallback on auto-retry signal in assistant text parts", async () => {
@@ -577,7 +577,7 @@ describe("runtime-fallback", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
@@ -588,7 +588,7 @@ describe("runtime-fallback", () => {
info: { info: {
sessionID, sessionID,
role: "assistant", role: "assistant",
model: "quotio/claude-opus-4-6", model: "quotio/claude-opus-4-7",
}, },
parts: [ parts: [
{ {
@@ -605,7 +605,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: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" }) expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
}) })
test("should trigger fallback when auto-retry text parts are nested under info.parts", async () => { test("should trigger fallback when auto-retry text parts are nested under info.parts", async () => {
@@ -620,7 +620,7 @@ describe("runtime-fallback", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
@@ -631,7 +631,7 @@ describe("runtime-fallback", () => {
info: { info: {
sessionID, sessionID,
role: "assistant", role: "assistant",
model: "quotio/claude-opus-4-6", model: "quotio/claude-opus-4-7",
parts: [ parts: [
{ {
type: "text", type: "text",
@@ -648,7 +648,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: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" }) expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
}) })
test("should trigger fallback on session.status auto-retry signal", async () => { test("should trigger fallback on session.status auto-retry signal", async () => {
@@ -682,7 +682,7 @@ describe("runtime-fallback", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
@@ -695,7 +695,7 @@ describe("runtime-fallback", () => {
type: "retry", type: "retry",
next: 476, next: 476,
attempt: 1, attempt: 1,
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]", message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
}, },
}, },
}, },
@@ -706,7 +706,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: "quotio/claude-opus-4-6", to: "openai/gpt-5.2" }) expect(fallbackLog?.data).toMatchObject({ from: "quotio/claude-opus-4-7", to: "openai/gpt-5.2" })
expect(promptCalls.length).toBe(1) expect(promptCalls.length).toBe(1)
}) })
@@ -741,7 +741,7 @@ describe("runtime-fallback", () => {
await hook.event({ await hook.event({
event: { event: {
type: "session.created", type: "session.created",
properties: { info: { id: sessionID, model: "quotio/claude-opus-4-6" } }, properties: { info: { id: sessionID, model: "quotio/claude-opus-4-7" } },
}, },
}) })
@@ -754,7 +754,7 @@ describe("runtime-fallback", () => {
type: "retry", type: "retry",
next: 476, next: 476,
attempt: 1, attempt: 1,
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]", message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
}, },
}, },
}, },
@@ -769,7 +769,7 @@ describe("runtime-fallback", () => {
type: "retry", type: "retry",
next: 475, next: 475,
attempt: 1, attempt: 1,
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 55s attempt #1]", message: "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 55s attempt #1]",
}, },
}, },
}, },
@@ -781,7 +781,7 @@ describe("runtime-fallback", () => {
test("should NOT trigger fallback on auto-retry signal when timeout_seconds is 0", async () => { test("should NOT trigger fallback on auto-retry signal when timeout_seconds is 0", async () => {
const hook = createRuntimeFallbackHook(createMockPluginInput(), { const hook = createRuntimeFallbackHook(createMockPluginInput(), {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 0 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 0 }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]), pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
}) })
const sessionID = "test-session-auto-retry-timeout-disabled" const sessionID = "test-session-auto-retry-timeout-disabled"
@@ -1161,8 +1161,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false }), config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
} }
@@ -1212,7 +1212,7 @@ describe("runtime-fallback", () => {
"Google Generative AI API key is missing. Pass it using the 'apiKey' parameter or the GOOGLE_GENERATIVE_AI_API_KEY environment variable.", "Google Generative AI API key is missing. Pass it using the 'apiKey' parameter or the GOOGLE_GENERATIVE_AI_API_KEY environment variable.",
}, },
}, },
model: "github-copilot/claude-opus-4.6", model: "github-copilot/claude-opus-4.7",
}, },
}, },
}, },
@@ -1251,8 +1251,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false }), config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
} }
@@ -1294,7 +1294,7 @@ describe("runtime-fallback", () => {
info: { info: {
sessionID, sessionID,
role: "assistant", role: "assistant",
model: "github-copilot/claude-opus-4.6", model: "github-copilot/claude-opus-4.7",
status: status:
"Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]", "Too Many Requests: quota exceeded [retrying in ~2 weeks attempt #1]",
}, },
@@ -1303,8 +1303,8 @@ describe("runtime-fallback", () => {
}) })
expect(retriedModels.length).toBeGreaterThanOrEqual(2) expect(retriedModels.length).toBeGreaterThanOrEqual(2)
expect(retriedModels[0]).toBe("github-copilot/claude-opus-4.6") expect(retriedModels[0]).toBe("github-copilot/claude-opus-4.7")
expect(retriedModels[1]).toBe("anthropic/claude-opus-4-6") expect(retriedModels[1]).toBe("anthropic/claude-opus-4-7")
void sessionErrorPromise void sessionErrorPromise
}) })
@@ -1335,8 +1335,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
@@ -1372,8 +1372,8 @@ describe("runtime-fallback", () => {
await new Promise((resolve) => setTimeout(resolve, 50)) await new Promise((resolve) => setTimeout(resolve, 50))
expect(retriedModels).toContain("github-copilot/claude-opus-4.6") expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
expect(retriedModels).toContain("anthropic/claude-opus-4-6") expect(retriedModels).toContain("anthropic/claude-opus-4-7")
expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true) expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true)
const timeoutLog = logCalls.find((c) => c.msg.includes("Session fallback timeout reached")) const timeoutLog = logCalls.find((c) => c.msg.includes("Session fallback timeout reached"))
@@ -1401,8 +1401,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
@@ -1443,15 +1443,15 @@ describe("runtime-fallback", () => {
await hook["chat.message"]?.( await hook["chat.message"]?.(
{ {
sessionID, sessionID,
model: { providerID: "github-copilot", modelID: "claude-opus-4.6" }, model: { providerID: "github-copilot", modelID: "claude-opus-4.7" },
}, },
output output
) )
await new Promise((resolve) => setTimeout(resolve, 50)) await new Promise((resolve) => setTimeout(resolve, 50))
expect(retriedModels).toContain("github-copilot/claude-opus-4.6") expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
expect(retriedModels).toContain("anthropic/claude-opus-4-6") expect(retriedModels).toContain("anthropic/claude-opus-4-7")
}) })
test("should abort in-flight fallback request before advancing on timeout", async () => { test("should abort in-flight fallback request before advancing on timeout", async () => {
@@ -1486,8 +1486,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
@@ -1524,8 +1524,8 @@ describe("runtime-fallback", () => {
await new Promise((resolve) => setTimeout(resolve, 50)) await new Promise((resolve) => setTimeout(resolve, 50))
expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true) expect(abortCalls.some((call) => call.path?.id === sessionID)).toBe(true)
expect(retriedModels).toContain("github-copilot/claude-opus-4.6") expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
expect(retriedModels).toContain("anthropic/claude-opus-4-6") expect(retriedModels).toContain("anthropic/claude-opus-4-7")
void sessionErrorPromise void sessionErrorPromise
}) })
@@ -1551,8 +1551,8 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4", "openai/gpt-5.4",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
@@ -1586,7 +1586,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toContain("github-copilot/claude-opus-4.6") expect(retriedModels).toContain("github-copilot/claude-opus-4.7")
await hook.event({ await hook.event({
event: { event: {
@@ -1624,9 +1624,9 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -1659,7 +1659,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
await hook.event({ await hook.event({
event: { event: {
@@ -1695,7 +1695,7 @@ describe("runtime-fallback", () => {
await new Promise((resolve) => setTimeout(resolve, 50)) await new Promise((resolve) => setTimeout(resolve, 50))
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
}) })
test("should not clear fallback timeout on assistant non-error update with Copilot retry signal", async () => { test("should not clear fallback timeout on assistant non-error update with Copilot retry signal", async () => {
@@ -1719,9 +1719,9 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -1754,7 +1754,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
await hook.event({ await hook.event({
event: { event: {
@@ -1796,7 +1796,7 @@ describe("runtime-fallback", () => {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -1846,7 +1846,7 @@ describe("runtime-fallback", () => {
await new Promise((resolve) => setTimeout(resolve, 60)) await new Promise((resolve) => setTimeout(resolve, 60))
expect(retriedModels).toContain("anthropic/claude-opus-4-6") expect(retriedModels).toContain("anthropic/claude-opus-4-7")
}) })
test("should not clear fallback timeout on assistant non-error update without user-visible content", async () => { test("should not clear fallback timeout on assistant non-error update without user-visible content", async () => {
@@ -1870,9 +1870,9 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -1905,7 +1905,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
await hook.event({ await hook.event({
event: { event: {
@@ -1914,7 +1914,7 @@ describe("runtime-fallback", () => {
info: { info: {
sessionID, sessionID,
role: "assistant", role: "assistant",
model: "github-copilot/claude-opus-4.6", model: "github-copilot/claude-opus-4.7",
}, },
}, },
}, },
@@ -1946,9 +1946,9 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -1981,7 +1981,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
await hook.event({ await hook.event({
event: { event: {
@@ -2022,9 +2022,9 @@ describe("runtime-fallback", () => {
{ {
config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }), config: createMockConfig({ notify_on_fallback: false, timeout_seconds: 30 }),
pluginConfig: createMockPluginConfigWithCategoryFallback([ pluginConfig: createMockPluginConfigWithCategoryFallback([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]), ]),
session_timeout_ms: 20, session_timeout_ms: 20,
} }
@@ -2057,7 +2057,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toEqual(["github-copilot/claude-opus-4.6"]) expect(retriedModels).toEqual(["github-copilot/claude-opus-4.7"])
await hook.event({ await hook.event({
event: { event: {
@@ -2145,7 +2145,7 @@ describe("runtime-fallback", () => {
}), }),
{ {
config: createMockConfig({ notify_on_fallback: false }), config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-6"]), pluginConfig: createMockPluginConfigWithCategoryFallback(["anthropic/claude-opus-4-7"]),
} }
) )
@@ -2176,7 +2176,7 @@ describe("runtime-fallback", () => {
}, },
}) })
expect(retriedModels).toContain("anthropic/claude-opus-4-6") expect(retriedModels).toContain("anthropic/claude-opus-4-7")
}) })
test("does NOT trigger fallback for normal type:error-free messages", async () => { test("does NOT trigger fallback for normal type:error-free messages", async () => {
@@ -2452,7 +2452,7 @@ describe("runtime-fallback", () => {
}), }),
{ {
config: createMockConfig({ notify_on_fallback: false }), config: createMockConfig({ notify_on_fallback: false }),
pluginConfig: createMockPluginConfigWithAgentFallback("prometheus", ["github-copilot/claude-opus-4.6"]), pluginConfig: createMockPluginConfigWithAgentFallback("prometheus", ["github-copilot/claude-opus-4.7"]),
}, },
) )
const sessionID = "test-preserve-agent-on-retry" const sessionID = "test-preserve-agent-on-retry"
@@ -2462,7 +2462,7 @@ describe("runtime-fallback", () => {
type: "session.error", type: "session.error",
properties: { properties: {
sessionID, sessionID,
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
error: { statusCode: 503, message: "Service unavailable" }, error: { statusCode: 503, message: "Service unavailable" },
agent: "prometheus", agent: "prometheus",
}, },
@@ -2472,7 +2472,7 @@ describe("runtime-fallback", () => {
expect(promptCalls.length).toBe(1) expect(promptCalls.length).toBe(1)
const callBody = promptCalls[0]?.body as Record<string, unknown> const callBody = promptCalls[0]?.body as Record<string, unknown>
expect(callBody?.agent).toBe("prometheus") expect(callBody?.agent).toBe("prometheus")
expect(callBody?.model).toEqual({ providerID: "github-copilot", modelID: "claude-opus-4.6" }) expect(callBody?.model).toEqual({ providerID: "github-copilot", modelID: "claude-opus-4.7" })
}) })
}) })
@@ -92,7 +92,7 @@ describe("runtime-fallback provider matrix quota tests", () => {
//#given //#given
const error = { const error = {
name: "AI_APICallError", name: "AI_APICallError",
message: "All credentials for model claude-opus-4-6 are cooling down [retrying in ~2 weeks]", message: "All credentials for model claude-opus-4-7 are cooling down [retrying in ~2 weeks]",
provider: "anthropic", provider: "anthropic",
} }
@@ -74,12 +74,12 @@ describe("createSessionStatusHandler", () => {
const deps = createDeps() const deps = createDeps()
const abortCalls: string[] = [] const abortCalls: string[] = []
const retryCalls: Array<{ sessionID: string; model: string; source: string }> = [] const retryCalls: Array<{ sessionID: string; model: string; source: string }> = []
const state = createFallbackState("anthropic/claude-opus-4-6") const state = createFallbackState("anthropic/claude-opus-4-7")
state.currentModel = "openai/gpt-5.4" state.currentModel = "openai/gpt-5.4"
state.fallbackIndex = 0 state.fallbackIndex = 0
state.attemptCount = 1 state.attemptCount = 1
state.pendingFallbackModel = "openai/gpt-5.4" state.pendingFallbackModel = "openai/gpt-5.4"
state.failedModels.set("anthropic/claude-opus-4-6", Date.now()) state.failedModels.set("anthropic/claude-opus-4-7", Date.now())
deps.sessionStates.set(sessionID, state) deps.sessionStates.set(sessionID, state)
const handler = createSessionStatusHandler(deps, createHelpers(abortCalls, retryCalls), deps.sessionStatusRetryKeys) const handler = createSessionStatusHandler(deps, createHelpers(abortCalls, retryCalls), deps.sessionStatusRetryKeys)
+1 -1
View File
@@ -49,7 +49,7 @@ describe("createThinkModeHook", () => {
const input = createHookInput({ const input = createHookInput({
sessionID, sessionID,
providerID: "github-copilot", providerID: "github-copilot",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
}) })
const output = createHookOutput("Please think deeply about this") const output = createHookOutput("Please think deeply about this")
+17 -17
View File
@@ -23,26 +23,26 @@ describe("think-mode switcher", () => {
describe("getHighVariant with dots vs hyphens", () => { describe("getHighVariant with dots vs hyphens", () => {
it("should handle dots in Claude version numbers", () => { it("should handle dots in Claude version numbers", () => {
// given a Claude model ID with dot format // given a Claude model ID with dot format
const variant = getHighVariant("claude-opus-4.6") const variant = getHighVariant("claude-opus-4.7")
// then should return high variant with hyphen format // then should return high variant with hyphen format
expect(variant).toBe("claude-opus-4-6-high") expect(variant).toBe("claude-opus-4-7-high")
}) })
it("should handle hyphens in Claude version numbers", () => { it("should handle hyphens in Claude version numbers", () => {
// given a Claude model ID with hyphen format // given a Claude model ID with hyphen format
const variant = getHighVariant("claude-opus-4-6") const variant = getHighVariant("claude-opus-4-7")
// then should return high variant // then should return high variant
expect(variant).toBe("claude-opus-4-6-high") expect(variant).toBe("claude-opus-4-7-high")
}) })
it("should handle claude-opus-4-6 high variant", () => { it("should handle claude-opus-4-7 high variant", () => {
// given a Claude Opus 4.6 model ID // given a Claude Opus 4.7 model ID
const variant = getHighVariant("claude-opus-4-6") const variant = getHighVariant("claude-opus-4-7")
// then should return high variant // then should return high variant
expect(variant).toBe("claude-opus-4-6-high") expect(variant).toBe("claude-opus-4-7-high")
}) })
it("should handle dots in GPT version numbers", () => { it("should handle dots in GPT version numbers", () => {
@@ -73,7 +73,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-7-high")).toBeNull()
expect(getHighVariant("gpt-5-4-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()
}) })
@@ -89,7 +89,7 @@ describe("think-mode switcher", () => {
describe("isAlreadyHighVariant", () => { describe("isAlreadyHighVariant", () => {
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-7-high")).toBe(true)
expect(isAlreadyHighVariant("gpt-5-4-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)
}) })
@@ -101,8 +101,8 @@ describe("think-mode switcher", () => {
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-7")).toBe(false)
expect(isAlreadyHighVariant("claude-opus-4.6")).toBe(false) expect(isAlreadyHighVariant("claude-opus-4.7")).toBe(false)
expect(isAlreadyHighVariant("gpt-5.4")).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)
}) })
@@ -133,10 +133,10 @@ describe("think-mode switcher", () => {
it("should handle prefixes with dots in version numbers", () => { it("should handle prefixes with dots in version numbers", () => {
// given a model ID with prefix and dots // given a model ID with prefix and dots
const variant = getHighVariant("vertex_ai/claude-opus-4.6") const variant = getHighVariant("vertex_ai/claude-opus-4.7")
// then should normalize dots and preserve prefix // then should normalize dots and preserve prefix
expect(variant).toBe("vertex_ai/claude-opus-4-6-high") expect(variant).toBe("vertex_ai/claude-opus-4-7-high")
}) })
it("should handle multiple different prefixes", () => { it("should handle multiple different prefixes", () => {
@@ -167,7 +167,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-7-high")).toBeNull()
expect(getHighVariant("openai/gpt-5-4-high")).toBeNull() expect(getHighVariant("openai/gpt-5-4-high")).toBeNull()
}) })
}) })
@@ -175,14 +175,14 @@ describe("think-mode switcher", () => {
describe("isAlreadyHighVariant with prefixes", () => { describe("isAlreadyHighVariant with prefixes", () => {
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-7-high")).toBe(true)
expect(isAlreadyHighVariant("openai/gpt-5-4-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-7")).toBe(false)
expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false) expect(isAlreadyHighVariant("openai/gpt-5-4")).toBe(false)
}) })
+1 -1
View File
@@ -45,7 +45,7 @@ function extractModelPrefix(modelID: string): { prefix: string; base: string } {
const HIGH_VARIANT_MAP: Record<string, string> = { const HIGH_VARIANT_MAP: Record<string, string> = {
// Claude // Claude
"claude-sonnet-4-6": "claude-sonnet-4-6-high", "claude-sonnet-4-6": "claude-sonnet-4-6-high",
"claude-opus-4-6": "claude-opus-4-6-high", "claude-opus-4-7": "claude-opus-4-7-high",
// Gemini // Gemini
"gemini-3-1-pro": "gemini-3-1-pro-high", "gemini-3-1-pro": "gemini-3-1-pro-high",
"gemini-3-1-pro-low": "gemini-3-1-pro-high", "gemini-3-1-pro-low": "gemini-3-1-pro-high",
@@ -13,7 +13,7 @@ describe("experimental.session.compacting", () => {
//#then //#then
expect(hookIndex).toBeGreaterThanOrEqual(0) expect(hookIndex).toBeGreaterThanOrEqual(0)
expect(content.includes('modelID: "claude-opus-4-6"')).toBe(false) expect(content.includes('modelID: "claude-opus-4-7"')).toBe(false)
expect(hookSlice.includes("output.context.push")).toBe(true) expect(hookSlice.includes("output.context.push")).toBe(true)
expect(hookSlice.includes("providerID:")).toBe(false) expect(hookSlice.includes("providerID:")).toBe(false)
expect(hookSlice.includes("modelID:")).toBe(false) expect(hookSlice.includes("modelID:")).toBe(false)
@@ -81,7 +81,7 @@ describe("applyAgentConfig .agents skills", () => {
// when // when
await applyAgentConfig({ await applyAgentConfig({
config: { model: "anthropic/claude-opus-4-6", agent: {} }, config: { model: "anthropic/claude-opus-4-7", agent: {} },
pluginConfig: createPluginConfig(), pluginConfig: createPluginConfig(),
ctx: { directory }, ctx: { directory },
pluginComponents: createPluginComponents(), pluginComponents: createPluginComponents(),
@@ -111,7 +111,7 @@ describe("applyAgentConfig .agents skills", () => {
// when // when
await applyAgentConfig({ await applyAgentConfig({
config: { model: "anthropic/claude-opus-4-6", agent: {} }, config: { model: "anthropic/claude-opus-4-7", agent: {} },
pluginConfig: createPluginConfig(), pluginConfig: createPluginConfig(),
ctx: { directory: "/tmp/project" }, ctx: { directory: "/tmp/project" },
pluginComponents: createPluginComponents(), pluginComponents: createPluginComponents(),
@@ -31,7 +31,7 @@ function createPluginComponents(): PluginComponents {
function createBaseConfig(): Record<string, unknown> { function createBaseConfig(): Record<string, unknown> {
return { return {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
} }
+48 -48
View File
@@ -88,7 +88,7 @@ beforeEach(async () => {
spyOn(mcpModule, "createBuiltinMcps" as any).mockReturnValue({}) spyOn(mcpModule, "createBuiltinMcps" as any).mockReturnValue({})
spyOn(shared, "log" as any).mockImplementation(() => {}) spyOn(shared, "log" as any).mockImplementation(() => {})
spyOn(shared, "fetchAvailableModels" as any).mockResolvedValue(new Set(["anthropic/claude-opus-4-6"])) spyOn(shared, "fetchAvailableModels" as any).mockResolvedValue(new Set(["anthropic/claude-opus-4-7"]))
spyOn(shared, "readConnectedProvidersCache" as any).mockReturnValue(null) spyOn(shared, "readConnectedProvidersCache" as any).mockReturnValue(null)
spyOn(configDir, "getOpenCodeConfigPaths" as any).mockReturnValue({ spyOn(configDir, "getOpenCodeConfigPaths" as any).mockReturnValue({
@@ -98,7 +98,7 @@ beforeEach(async () => {
spyOn(permissionCompat, "migrateAgentConfig" as any).mockImplementation((config: Record<string, unknown>) => config) spyOn(permissionCompat, "migrateAgentConfig" as any).mockImplementation((config: Record<string, unknown>) => config)
spyOn(modelResolver, "resolveModelWithFallback" as any).mockReturnValue({ model: "anthropic/claude-opus-4-6" }) spyOn(modelResolver, "resolveModelWithFallback" as any).mockReturnValue({ model: "anthropic/claude-opus-4-7" })
;({ createConfigHandler } = await importFreshConfigHandlerModule()) ;({ createConfigHandler } = await importFreshConfigHandlerModule())
}) })
@@ -204,7 +204,7 @@ describe("MCP env allowlist initialization", () => {
mcp_env_allowlist: ["CUSTOM_API_KEY", "CUSTOM_AUTH_TOKEN"], mcp_env_allowlist: ["CUSTOM_API_KEY", "CUSTOM_AUTH_TOKEN"],
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -246,7 +246,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -292,7 +292,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -336,7 +336,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -385,7 +385,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: { agent: {
plan: { plan: {
name: "plan", name: "plan",
@@ -422,7 +422,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: { agent: {
plan: { plan: {
name: "plan", name: "plan",
@@ -459,7 +459,7 @@ describe("Plan agent demote behavior", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -495,7 +495,7 @@ describe("Agent permission defaults", () => {
}) })
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -523,7 +523,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// given // given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: " hephaestus ", default_agent: " hephaestus ",
agent: {}, agent: {},
} }
@@ -547,7 +547,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// given // given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: "HePhAeStUs", default_agent: "HePhAeStUs",
agent: {}, agent: {},
} }
@@ -571,7 +571,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// #given // #given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: "hephaestus", default_agent: "hephaestus",
agent: {}, agent: {},
} }
@@ -596,7 +596,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const displayName = getAgentListDisplayName("hephaestus") const displayName = getAgentListDisplayName("hephaestus")
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: displayName, default_agent: displayName,
agent: {}, agent: {},
} }
@@ -620,7 +620,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// #given // #given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -643,7 +643,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// given // given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: "hephaestus", default_agent: "hephaestus",
agent: {}, agent: {},
} }
@@ -667,7 +667,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// given // given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: " ", default_agent: " ",
agent: {}, agent: {},
} }
@@ -691,7 +691,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
// given // given
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: " Custom Agent ", default_agent: " Custom Agent ",
agent: {}, agent: {},
} }
@@ -719,7 +719,7 @@ describe("default_agent behavior with Sisyphus orchestration", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
default_agent: " HePhAeStUs ", default_agent: " HePhAeStUs ",
agent: {}, agent: {},
} }
@@ -861,7 +861,7 @@ describe("Prometheus direct override priority over category", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -902,7 +902,7 @@ describe("Prometheus direct override priority over category", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -944,7 +944,7 @@ describe("Prometheus direct override priority over category", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -980,7 +980,7 @@ describe("Prometheus direct override priority over category", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1007,9 +1007,9 @@ describe("Prometheus direct override priority over category", () => {
describe("Plan agent model inheritance from prometheus", () => { describe("Plan agent model inheritance from prometheus", () => {
test("plan agent inherits all model-related settings from resolved prometheus config", async () => { test("plan agent inherits all model-related settings from resolved prometheus config", async () => {
//#given - prometheus resolves to claude-opus-4-6 with model settings //#given - prometheus resolves to claude-opus-4-7 with model settings
spyOn(prometheusAgentConfigBuilder, "buildPrometheusAgentConfig").mockResolvedValue({ spyOn(prometheusAgentConfigBuilder, "buildPrometheusAgentConfig").mockResolvedValue({
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
mode: "primary", mode: "primary",
prompt: "prometheus prompt", prompt: "prometheus prompt",
@@ -1021,7 +1021,7 @@ describe("Plan agent model inheritance from prometheus", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: { agent: {
plan: { plan: {
name: "plan", name: "plan",
@@ -1047,7 +1047,7 @@ describe("Plan agent model inheritance from prometheus", () => {
const agents = config.agent as Record<string, { mode?: string; model?: string; variant?: string; prompt?: string }> const agents = config.agent as Record<string, { mode?: string; model?: string; variant?: string; prompt?: string }>
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("anthropic/claude-opus-4-6") expect(agents.plan.model).toBe("anthropic/claude-opus-4-7")
expect(agents.plan.variant).toBe("max") expect(agents.plan.variant).toBe("max")
expect(agents.plan.prompt).toBeUndefined() expect(agents.plan.prompt).toBeUndefined()
}) })
@@ -1078,7 +1078,7 @@ describe("Plan agent model inheritance from prometheus", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1110,7 +1110,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.4 //#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-7",
provenance: "provider-fallback", provenance: "provider-fallback",
variant: "max", variant: "max",
}) })
@@ -1128,7 +1128,7 @@ describe("Plan agent model inheritance from prometheus", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1153,7 +1153,7 @@ describe("Plan agent model inheritance from prometheus", () => {
test("plan agent does NOT inherit prompt, description, or color from prometheus", async () => { test("plan agent does NOT inherit prompt, description, or color from prometheus", async () => {
//#given //#given
spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({ spyOn(shared, "resolveModelPipeline" as any).mockReturnValue({
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
provenance: "provider-fallback", provenance: "provider-fallback",
variant: "max", variant: "max",
}) })
@@ -1164,7 +1164,7 @@ describe("Plan agent model inheritance from prometheus", () => {
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1181,7 +1181,7 @@ describe("Plan agent model inheritance from prometheus", () => {
//#then - plan has model settings but NOT prompt/description/color //#then - plan has model settings but NOT prompt/description/color
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("anthropic/claude-opus-4-6") expect(agents.plan.model).toBe("anthropic/claude-opus-4-7")
expect(agents.plan.prompt).toBeUndefined() expect(agents.plan.prompt).toBeUndefined()
expect(agents.plan.description).toBeUndefined() expect(agents.plan.description).toBeUndefined()
expect(agents.plan.color).toBeUndefined() expect(agents.plan.color).toBeUndefined()
@@ -1200,7 +1200,7 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
}, },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const mockClient = { const mockClient = {
@@ -1233,7 +1233,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash")) spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash"))
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
@@ -1263,7 +1263,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
experimental: { plugin_load_timeout_ms: 100 }, experimental: { plugin_load_timeout_ms: 100 },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
@@ -1289,7 +1289,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash")) spyOn(pluginLoader, "loadAllPluginComponents" as any).mockRejectedValue(new Error("crash"))
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
@@ -1326,7 +1326,7 @@ describe("config-handler plugin loading error boundary (#1559)", () => {
}) })
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule() const { createConfigHandler: createFreshConfigHandler } = await importFreshConfigHandlerModule()
@@ -1370,7 +1370,7 @@ describe("command agent routing coherence", () => {
}) })
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1420,7 +1420,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
experimental: { task_system: true }, experimental: { task_system: true },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1458,7 +1458,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
experimental: { task_system: false }, experimental: { task_system: false },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1497,7 +1497,7 @@ describe("per-agent todowrite/todoread deny when task_system enabled", () => {
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1538,7 +1538,7 @@ describe("disable_omo_env pass-through", () => {
experimental: { disable_omo_env: true }, experimental: { disable_omo_env: true },
}) })
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1575,7 +1575,7 @@ describe("disable_omo_env pass-through", () => {
const pluginConfig = createPluginConfig({}) const pluginConfig = createPluginConfig({})
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1621,7 +1621,7 @@ describe("Agent merge priority — project-local overrides global", () => {
const pluginConfig: OhMyOpenCodeConfig = {} const pluginConfig: OhMyOpenCodeConfig = {}
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1661,7 +1661,7 @@ describe("Agent merge priority — project-local overrides global", () => {
const pluginConfig: OhMyOpenCodeConfig = {} const pluginConfig: OhMyOpenCodeConfig = {}
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1701,7 +1701,7 @@ describe("Agent merge priority — project-local overrides global", () => {
const pluginConfig: OhMyOpenCodeConfig = {} const pluginConfig: OhMyOpenCodeConfig = {}
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -1749,7 +1749,7 @@ describe("Agent merge priority — project-local overrides global", () => {
const pluginConfig: OhMyOpenCodeConfig = {} const pluginConfig: OhMyOpenCodeConfig = {}
const config: Record<string, unknown> = { const config: Record<string, unknown> = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
agent: {}, agent: {},
} }
const handler = createConfigHandler({ const handler = createConfigHandler({
@@ -18,7 +18,7 @@ describe("buildPlanDemoteConfig", () => {
//#given //#given
const prometheusConfig = { const prometheusConfig = {
name: "prometheus", name: "prometheus",
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
mode: "primary", mode: "primary",
prompt: "You are Prometheus...", prompt: "You are Prometheus...",
@@ -39,7 +39,7 @@ describe("buildPlanDemoteConfig", () => {
//#then - picks model settings, NOT prompt/permission/description/color/name/mode //#then - picks model settings, NOT prompt/permission/description/color/name/mode
expect(result.mode).toBe("subagent") expect(result.mode).toBe("subagent")
expect(result.model).toBe("anthropic/claude-opus-4-6") expect(result.model).toBe("anthropic/claude-opus-4-7")
expect(result.variant).toBe("max") expect(result.variant).toBe("max")
expect(result.temperature).toBe(0.1) expect(result.temperature).toBe(0.1)
expect(result.top_p).toBe(0.95) expect(result.top_p).toBe(0.95)
@@ -58,7 +58,7 @@ describe("buildPlanDemoteConfig", () => {
test("plan override takes priority over prometheus for all model settings", () => { test("plan override takes priority over prometheus for all model settings", () => {
//#given //#given
const prometheusConfig = { const prometheusConfig = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
temperature: 0.1, temperature: 0.1,
reasoningEffort: "high", reasoningEffort: "high",
@@ -83,7 +83,7 @@ describe("buildPlanDemoteConfig", () => {
test("falls back to prometheus when plan override has partial settings", () => { test("falls back to prometheus when plan override has partial settings", () => {
//#given //#given
const prometheusConfig = { const prometheusConfig = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
temperature: 0.1, temperature: 0.1,
reasoningEffort: "high", reasoningEffort: "high",
@@ -105,14 +105,14 @@ describe("buildPlanDemoteConfig", () => {
test("skips undefined values from both sources", () => { test("skips undefined values from both sources", () => {
//#given //#given
const prometheusConfig = { const prometheusConfig = {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
} }
//#when //#when
const result = buildPlanDemoteConfig(prometheusConfig, undefined) const result = buildPlanDemoteConfig(prometheusConfig, undefined)
//#then //#then
expect(result).toEqual({ mode: "subagent", hidden: true, model: "anthropic/claude-opus-4-6" }) expect(result).toEqual({ mode: "subagent", hidden: true, model: "anthropic/claude-opus-4-7" })
expect(Object.keys(result)).toEqual(["mode", "hidden", "model"]) expect(Object.keys(result)).toEqual(["mode", "hidden", "model"])
}) })
}) })
@@ -24,7 +24,7 @@ describe("buildPrometheusAgentConfig", () => {
(category) => ({ model: `${category}/default-model` } as CategoryConfig) (category) => ({ model: `${category}/default-model` } as CategoryConfig)
); );
resolveModelPipelineSpy = spyOn(shared, "resolveModelPipeline").mockReturnValue({ resolveModelPipelineSpy = spyOn(shared, "resolveModelPipeline").mockReturnValue({
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
provenance: "provider-fallback", provenance: "provider-fallback",
}); });
;({ buildPrometheusAgentConfig } = await importFreshPrometheusAgentConfigBuilderModule()) ;({ buildPrometheusAgentConfig } = await importFreshPrometheusAgentConfigBuilderModule())
@@ -42,7 +42,7 @@ describe("buildPrometheusAgentConfig", () => {
describe("#when currentModel is NOT in Prometheus fallback chain", () => { describe("#when currentModel is NOT in Prometheus fallback chain", () => {
test("falls through to fallback chain instead of using currentModel as override", async () => { test("falls through to fallback chain instead of using currentModel as override", async () => {
// given - currentModel is a model NOT in Prometheus fallback chain // given - currentModel is a model NOT in Prometheus fallback chain
// Prometheus chain: claude-opus-4-6, gpt-5.4, glm-5, gemini-3.1-pro // Prometheus chain: claude-opus-4-7, gpt-5.4, glm-5, gemini-3.1-pro
const currentModel = "some-provider/gpt-5.3-codex"; const currentModel = "some-provider/gpt-5.3-codex";
// when // when
@@ -65,14 +65,14 @@ describe("buildPrometheusAgentConfig", () => {
systemDefaultModel: undefined, systemDefaultModel: undefined,
}), }),
}); });
expect(result.model).toBe("anthropic/claude-opus-4-6"); expect(result.model).toBe("anthropic/claude-opus-4-7");
}); });
}); });
describe("#when currentModel IS in Prometheus fallback chain", () => { describe("#when currentModel IS in Prometheus fallback chain", () => {
test("preserves currentModel as uiSelectedModel for claude-opus-4-6", async () => { test("preserves currentModel as uiSelectedModel for claude-opus-4-7", async () => {
// given - currentModel matches a Prometheus fallback chain entry // given - currentModel matches a Prometheus fallback chain entry
const currentModel = "anthropic/claude-opus-4-6"; const currentModel = "anthropic/claude-opus-4-7";
// when - should not throw and should produce a valid config // when - should not throw and should produce a valid config
const result = await buildPrometheusAgentConfig({ const result = await buildPrometheusAgentConfig({
@@ -128,7 +128,7 @@ describe("buildPrometheusAgentConfig", () => {
describe("#given explicit Prometheus model configured via plugin override", () => { describe("#given explicit Prometheus model configured via plugin override", () => {
test("explicit config wins over currentModel and fallback chain", async () => { test("explicit config wins over currentModel and fallback chain", async () => {
// given // given
const currentModel = "anthropic/claude-opus-4-6"; const currentModel = "anthropic/claude-opus-4-7";
const explicitModel = "custom-provider/custom-model"; const explicitModel = "custom-provider/custom-model";
// when // when
@@ -163,7 +163,7 @@ describe("buildPrometheusAgentConfig", () => {
describe("#given category with model configured", () => { describe("#given category with model configured", () => {
test("category model wins when no explicit override", async () => { test("category model wins when no explicit override", async () => {
// given // given
const currentModel = "anthropic/claude-opus-4-6"; const currentModel = "anthropic/claude-opus-4-7";
const categoryModel = "category-provider/category-model"; const categoryModel = "category-provider/category-model";
resolveCategoryConfigSpy.mockReturnValue({ resolveCategoryConfigSpy.mockReturnValue({
@@ -264,7 +264,7 @@ describe("buildPrometheusAgentConfig", () => {
}, },
}) })
); );
expect(result.model).toBe("anthropic/claude-opus-4-6"); expect(result.model).toBe("anthropic/claude-opus-4-7");
}); });
}); });
+3 -3
View File
@@ -709,7 +709,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
shouldOverride: false, shouldOverride: false,
pluginConfig: { pluginConfig: {
agents: { agents: {
sisyphus: { model: "anthropic/claude-opus-4-6" }, sisyphus: { model: "anthropic/claude-opus-4-7" },
}, },
}, },
}) })
@@ -733,7 +733,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
shouldOverride: false, shouldOverride: false,
pluginConfig: { pluginConfig: {
agents: { agents: {
prometheus: { model: "anthropic/claude-opus-4-6" }, prometheus: { model: "anthropic/claude-opus-4-7" },
}, },
}, },
}) })
@@ -753,7 +753,7 @@ describe("createChatMessageHandler - TUI variant passthrough", () => {
test("respects a mid-conversation model switch instead of reusing the previous stored model", async () => { test("respects a mid-conversation model switch instead of reusing the previous stored model", async () => {
//#given //#given
setMainSession("test-session") setMainSession("test-session")
setSessionModel("test-session", { providerID: "anthropic", modelID: "claude-opus-4-6" }) setSessionModel("test-session", { providerID: "anthropic", modelID: "claude-opus-4-7" })
const args = createMockHandlerArgs({ shouldOverride: false }) const args = createMockHandlerArgs({ shouldOverride: false })
const handler = createChatMessageHandler(args) const handler = createChatMessageHandler(args)
const nextModel = { providerID: "openai", modelID: "gpt-5.4" } const nextModel = { providerID: "openai", modelID: "gpt-5.4" }
+1 -1
View File
@@ -48,7 +48,7 @@ describe("createChatParamsHandler", () => {
const input = { const input = {
sessionID: "ses_chat_params", sessionID: "ses_chat_params",
agent: { name: "sisyphus" }, agent: { name: "sisyphus" },
model: { providerID: "opencode", modelID: "claude-opus-4-6" }, model: { providerID: "opencode", modelID: "claude-opus-4-7" },
provider: { id: "opencode" }, provider: { id: "opencode" },
message: {}, message: {},
} }
+1 -1
View File
@@ -73,7 +73,7 @@ describe("createEventHandler compaction agent filtering", () => {
role: "user", role: "user",
agent: "compaction", agent: "compaction",
time: { created: Date.now() }, time: { created: Date.now() },
model: { providerID: "anthropic", modelID: "claude-opus-4-6" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7" },
}, },
}, },
}, },
+21 -21
View File
@@ -85,12 +85,12 @@ describe("createEventHandler - model fallback", () => {
name: "APIError", name: "APIError",
data: { data: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
isRetryable: true, isRetryable: true,
}, },
}, },
parentID: "msg_user_1", parentID: "msg_user_1",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
providerID: "anthropic", providerID: "anthropic",
mode: "Sisyphus - Ultraworker", mode: "Sisyphus - Ultraworker",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
@@ -125,7 +125,7 @@ describe("createEventHandler - model fallback", () => {
data: { data: {
error: { error: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
}, },
}, },
}, },
@@ -182,7 +182,7 @@ describe("createEventHandler - model fallback", () => {
role: "user", role: "user",
time: { created: 1 }, time: { created: 1 },
content: [], content: [],
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
providerID: "anthropic", providerID: "anthropic",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
path: { cwd: "/tmp", root: "/tmp" }, path: { cwd: "/tmp", root: "/tmp" },
@@ -201,7 +201,7 @@ describe("createEventHandler - model fallback", () => {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
next: 1234, next: 1234,
}, },
}, },
@@ -213,7 +213,7 @@ describe("createEventHandler - model fallback", () => {
{ {
sessionID, sessionID,
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
}, },
output, output,
) )
@@ -244,7 +244,7 @@ describe("createEventHandler - model fallback", () => {
id: "msg_user_status_dedup", id: "msg_user_status_dedup",
sessionID, sessionID,
role: "user", role: "user",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
providerID: "anthropic", providerID: "anthropic",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
}, },
@@ -262,7 +262,7 @@ describe("createEventHandler - model fallback", () => {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
next: 300, next: 300,
}, },
}, },
@@ -277,7 +277,7 @@ describe("createEventHandler - model fallback", () => {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~4 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~4 days attempt #1]",
next: 299, next: 299,
}, },
}, },
@@ -312,7 +312,7 @@ describe("createEventHandler - model fallback", () => {
id: "msg_user_status_runtime_enabled", id: "msg_user_status_runtime_enabled",
sessionID, sessionID,
role: "user", role: "user",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
providerID: "quotio", providerID: "quotio",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
}, },
@@ -330,7 +330,7 @@ describe("createEventHandler - model fallback", () => {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]", "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
next: 476, next: 476,
}, },
}, },
@@ -393,7 +393,7 @@ describe("createEventHandler - model fallback", () => {
role: "user", role: "user",
time: { created: 1 }, time: { created: 1 },
content: [], content: [],
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
providerID: "quotio", providerID: "quotio",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
path: { cwd: "/tmp", root: "/tmp" }, path: { cwd: "/tmp", root: "/tmp" },
@@ -412,7 +412,7 @@ describe("createEventHandler - model fallback", () => {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
next: 300, next: 300,
}, },
}, },
@@ -424,7 +424,7 @@ describe("createEventHandler - model fallback", () => {
{ {
sessionID, sessionID,
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "quotio", modelID: "claude-opus-4-6" }, model: { providerID: "quotio", modelID: "claude-opus-4-7" },
}, },
output, output,
) )
@@ -520,13 +520,13 @@ describe("createEventHandler - model fallback", () => {
properties: { properties: {
sessionID, sessionID,
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
error: { error: {
name: "UnknownError", name: "UnknownError",
data: { data: {
error: { error: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
}, },
}, },
}, },
@@ -539,7 +539,7 @@ describe("createEventHandler - model fallback", () => {
{ {
sessionID, sessionID,
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
}, },
output, output,
) )
@@ -549,7 +549,7 @@ describe("createEventHandler - model fallback", () => {
//#when - first retry cycle //#when - first retry cycle
const first = await triggerRetryCycle() const first = await triggerRetryCycle()
//#then - first fallback entry applied (no-op skip: claude-opus-4-6 matches current model after normalization) //#then - first fallback entry applied (no-op skip: claude-opus-4-7 matches current model after normalization)
expect(first.message["model"]).toMatchObject({ expect(first.message["model"]).toMatchObject({
providerID: "opencode-go", providerID: "opencode-go",
modelID: "kimi-k2.5", modelID: "kimi-k2.5",
@@ -590,12 +590,12 @@ describe("createEventHandler - model fallback", () => {
name: "APIError", name: "APIError",
data: { data: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
isRetryable: true, isRetryable: true,
}, },
}, },
parentID: "msg_user_disabled_1", parentID: "msg_user_disabled_1",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
providerID: "anthropic", providerID: "anthropic",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
path: { cwd: "/tmp", root: "/tmp" }, path: { cwd: "/tmp", root: "/tmp" },
@@ -617,7 +617,7 @@ describe("createEventHandler - model fallback", () => {
data: { data: {
error: { error: {
message: message:
"Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-6-thinking\"}}", "Bad Gateway: {\"error\":{\"message\":\"unknown provider for model claude-opus-4-7-thinking\"}}",
}, },
}, },
}, },
+3 -3
View File
@@ -826,7 +826,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
const retryStatus = { const retryStatus = {
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: "All credentials for model claude-opus-4-6-thinking are cooling down [retrying in 7m 56s attempt #1]", message: "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in 7m 56s attempt #1]",
next: 476, next: 476,
} as const } as const
@@ -838,7 +838,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
id: "msg_user_retry_rearm", id: "msg_user_retry_rearm",
sessionID, sessionID,
role: "user", role: "user",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
providerID: "anthropic", providerID: "anthropic",
agent: "Sisyphus - Ultraworker", agent: "Sisyphus - Ultraworker",
}, },
@@ -862,7 +862,7 @@ describe("createEventHandler - retry dedupe lifecycle", () => {
{ {
sessionID, sessionID,
agent: "sisyphus", agent: "sisyphus",
model: { providerID: "anthropic", modelID: "claude-opus-4-6-thinking" }, model: { providerID: "anthropic", modelID: "claude-opus-4-7-thinking" },
}, },
firstOutput, firstOutput,
) )
+3 -3
View File
@@ -515,7 +515,7 @@ export function createEventHandler(args: {
sessionID, sessionID,
info?.providerID as string | undefined, info?.providerID as string | undefined,
); );
const rawModel = (info?.modelID as string | undefined) ?? "claude-opus-4-6"; const rawModel = (info?.modelID as string | undefined) ?? "claude-opus-4-7";
const currentModel = normalizeFallbackModelID(rawModel); const currentModel = normalizeFallbackModelID(rawModel);
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
@@ -578,7 +578,7 @@ export function createEventHandler(args: {
const parsed = extractProviderModelFromErrorMessage(retryMessage); const parsed = extractProviderModelFromErrorMessage(retryMessage);
const lastKnown = lastKnownModelBySession.get(sessionID); const lastKnown = lastKnownModelBySession.get(sessionID);
const currentProvider = resolveFallbackProviderID(sessionID, parsed.providerID); const currentProvider = resolveFallbackProviderID(sessionID, parsed.providerID);
let currentModel = parsed.modelID ?? lastKnown?.modelID ?? "claude-opus-4-6"; let currentModel = parsed.modelID ?? lastKnown?.modelID ?? "claude-opus-4-7";
currentModel = normalizeFallbackModelID(currentModel); currentModel = normalizeFallbackModelID(currentModel);
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
@@ -664,7 +664,7 @@ export function createEventHandler(args: {
sessionID, sessionID,
(props?.providerID as string | undefined) || parsed.providerID, (props?.providerID as string | undefined) || parsed.providerID,
); );
let currentModel = (props?.modelID as string) || parsed.modelID || "claude-opus-4-6"; let currentModel = (props?.modelID as string) || parsed.modelID || "claude-opus-4-7";
currentModel = normalizeFallbackModelID(currentModel); currentModel = normalizeFallbackModelID(currentModel);
applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig); applyUserConfiguredFallbackChain(sessionID, agentName, currentProvider, args.pluginConfig);
@@ -59,7 +59,7 @@ function createChatMessageHandlerHooks(
const PRIMARY_MODEL = { const PRIMARY_MODEL = {
providerID: PROVIDER_ID, providerID: PROVIDER_ID,
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
} }
const PRIMARY_MODEL_STRING = `${PRIMARY_MODEL.providerID}/${PRIMARY_MODEL.modelID}` const PRIMARY_MODEL_STRING = `${PRIMARY_MODEL.providerID}/${PRIMARY_MODEL.modelID}`
@@ -313,7 +313,7 @@ async function triggerSessionStatusRetry(
type: "retry", type: "retry",
attempt: 1, attempt: 1,
message: message:
"All credentials for model claude-opus-4-6 are cooling down [retrying in 7m 56s attempt #1]", "All credentials for model claude-opus-4-7 are cooling down [retrying in 7m 56s attempt #1]",
next: 476, next: 476,
}, },
}, },
@@ -112,13 +112,13 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_001", "msg_001",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
) )
await flushMicrotasks(5) await flushMicrotasks(5)
//#then //#then
const model = readMessageModel("msg_001") const model = readMessageModel("msg_001")
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
}) })
test("should update variant and thinking fields when variant provided", async () => { test("should update variant and thinking fields when variant provided", async () => {
@@ -128,7 +128,7 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_002", "msg_002",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
"max", "max",
) )
await flushMicrotasks(5) await flushMicrotasks(5)
@@ -144,7 +144,7 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_nonexistent", "msg_nonexistent",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
) )
await flushWithTimeout() await flushWithTimeout()
@@ -162,13 +162,13 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_003", "msg_003",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
) )
await flushMicrotasks(5) await flushMicrotasks(5)
//#then //#then
const model = readMessageModel("msg_003") const model = readMessageModel("msg_003")
expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) expect(model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
expect(readMessageField("msg_003", "variant")).toBeNull() expect(readMessageField("msg_003", "variant")).toBeNull()
expect(readMessageField("msg_003", "thinking")).toBeNull() expect(readMessageField("msg_003", "thinking")).toBeNull()
}) })
@@ -180,7 +180,7 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_004", "msg_004",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
) )
await flushMicrotasks(5) await flushMicrotasks(5)
@@ -200,7 +200,7 @@ describe("scheduleDeferredModelOverride", () => {
//#when //#when
scheduleDeferredModelOverride( scheduleDeferredModelOverride(
"msg_corrupt", "msg_corrupt",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
) )
await flushMicrotasks(5) await flushMicrotasks(5)
+29 -29
View File
@@ -79,19 +79,19 @@ describe("resolveUltraworkOverride", () => {
test("should resolve override when ultrawork keyword detected", () => { test("should resolve override when ultrawork keyword detected", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something") const output = createOutput("ultrawork do something")
//#when //#when
const result = resolveUltraworkOverride(config, "sisyphus", output) const result = resolveUltraworkOverride(config, "sisyphus", output)
//#then //#then
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
}) })
test("should return null when no keyword detected", () => { test("should return null when no keyword detected", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("just do something normal") const output = createOutput("just do something normal")
//#when //#when
@@ -103,7 +103,7 @@ describe("resolveUltraworkOverride", () => {
test("should return null when agent name is undefined", () => { test("should return null when agent name is undefined", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("ultrawork do something") const output = createOutput("ultrawork do something")
//#when //#when
@@ -115,14 +115,14 @@ describe("resolveUltraworkOverride", () => {
test("should use message.agent when input agent is undefined", () => { test("should use message.agent when input agent is undefined", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("ultrawork do something", "sisyphus") const output = createOutput("ultrawork do something", "sisyphus")
//#when //#when
const result = resolveUltraworkOverride(config, undefined, output) const result = resolveUltraworkOverride(config, undefined, output)
//#then //#then
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: undefined }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: undefined })
}) })
test("should return null when agents config is missing", () => { test("should return null when agents config is missing", () => {
@@ -189,19 +189,19 @@ describe("resolveUltraworkOverride", () => {
test("should resolve display name to config key", () => { test("should resolve display name to config key", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ulw do something") const output = createOutput("ulw do something")
//#when //#when
const result = resolveUltraworkOverride(config, "Sisyphus - Ultraworker", output) const result = resolveUltraworkOverride(config, "Sisyphus - Ultraworker", output)
//#then //#then
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
}) })
test("should handle multiple text parts by joining them", () => { test("should handle multiple text parts by joining them", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = { const output = {
message: {} as Record<string, unknown>, message: {} as Record<string, unknown>,
parts: [ parts: [
@@ -215,12 +215,12 @@ describe("resolveUltraworkOverride", () => {
const result = resolveUltraworkOverride(config, "sisyphus", output) const result = resolveUltraworkOverride(config, "sisyphus", output)
//#then //#then
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: undefined }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: undefined })
}) })
test("should use session agent when input and message agents are undefined", () => { test("should use session agent when input and message agents are undefined", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something") const output = createOutput("ultrawork do something")
const getSessionAgentSpy = spyOn(sessionStateModule, "getSessionAgent") const getSessionAgentSpy = spyOn(sessionStateModule, "getSessionAgent")
getSessionAgentSpy.mockReturnValue("sisyphus") getSessionAgentSpy.mockReturnValue("sisyphus")
@@ -230,7 +230,7 @@ describe("resolveUltraworkOverride", () => {
//#then //#then
expect(getSessionAgentSpy).toHaveBeenCalledWith("ses_test") expect(getSessionAgentSpy).toHaveBeenCalledWith("ses_test")
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6", variant: "max" }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7", variant: "max" })
getSessionAgentSpy.mockRestore() getSessionAgentSpy.mockRestore()
}) })
@@ -287,7 +287,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should schedule deferred DB override without variant when SDK unavailable", () => { test("should schedule deferred DB override without variant when SDK unavailable", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something", { messageId: "msg_123" }) const output = createOutput("ultrawork do something", { messageId: "msg_123" })
const tui = createMockTui() const tui = createMockTui()
@@ -297,7 +297,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
//#then - variant should NOT be applied without SDK validation //#then - variant should NOT be applied without SDK validation
expect(dbOverrideSpy).toHaveBeenCalledWith( expect(dbOverrideSpy).toHaveBeenCalledWith(
"msg_123", "msg_123",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
undefined, undefined,
) )
}) })
@@ -305,7 +305,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should NOT override variant when SDK unavailable even if config specifies variant", () => { test("should NOT override variant when SDK unavailable even if config specifies variant", () => {
//#given //#given
const config = createConfig("sisyphus", { const config = createConfig("sisyphus", {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "extended", variant: "extended",
}) })
const output = createOutput("ultrawork do something", { messageId: "msg_123" }) const output = createOutput("ultrawork do something", { messageId: "msg_123" })
@@ -319,7 +319,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
//#then - existing variant preserved, not overridden to "extended" //#then - existing variant preserved, not overridden to "extended"
expect(dbOverrideSpy).toHaveBeenCalledWith( expect(dbOverrideSpy).toHaveBeenCalledWith(
"msg_123", "msg_123",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
undefined, undefined,
) )
expect(output.message["variant"]).toBe("max") expect(output.message["variant"]).toBe("max")
@@ -329,7 +329,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should NOT mutate output.message.model when message ID present", () => { test("should NOT mutate output.message.model when message ID present", () => {
//#given //#given
const sonnetModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" } const sonnetModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" }
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("ultrawork do something", { const output = createOutput("ultrawork do something", {
existingModel: sonnetModel, existingModel: sonnetModel,
messageId: "msg_123", messageId: "msg_123",
@@ -345,7 +345,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should fall back to direct model mutation without variant when no message ID and no SDK", () => { test("should fall back to direct model mutation without variant when no message ID and no SDK", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something") const output = createOutput("ultrawork do something")
const tui = createMockTui() const tui = createMockTui()
@@ -353,7 +353,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
applyUltraworkModelOverrideOnMessage(config, "sisyphus", output, tui) applyUltraworkModelOverrideOnMessage(config, "sisyphus", output, tui)
//#then - model is set but variant is NOT applied without SDK validation //#then - model is set but variant is NOT applied without SDK validation
expect(output.message.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6" }) expect(output.message.model).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7" })
expect(output.message["variant"]).toBeUndefined() expect(output.message["variant"]).toBeUndefined()
expect(dbOverrideSpy).not.toHaveBeenCalled() expect(dbOverrideSpy).not.toHaveBeenCalled()
}) })
@@ -375,7 +375,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should not apply override when no keyword detected", () => { test("should not apply override when no keyword detected", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("just do something normal", { messageId: "msg_123" }) const output = createOutput("just do something normal", { messageId: "msg_123" })
const tui = createMockTui() const tui = createMockTui()
@@ -388,7 +388,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should log the model transition with deferred DB tag", () => { test("should log the model transition with deferred DB tag", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const existingModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" } const existingModel = { providerID: "anthropic", modelID: "claude-sonnet-4-6" }
const output = createOutput("ultrawork do something", { const output = createOutput("ultrawork do something", {
existingModel, existingModel,
@@ -408,7 +408,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should call showToast on override", () => { test("should call showToast on override", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7" })
const output = createOutput("ultrawork do something", { messageId: "msg_123" }) const output = createOutput("ultrawork do something", { messageId: "msg_123" })
let toastCalled = false let toastCalled = false
const tui = { const tui = {
@@ -426,7 +426,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should resolve display name to config key with deferred path", () => { test("should resolve display name to config key with deferred path", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ulw do something", { messageId: "msg_123" }) const output = createOutput("ulw do something", { messageId: "msg_123" })
const tui = createMockTui() const tui = createMockTui()
@@ -436,16 +436,16 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
//#then //#then
expect(dbOverrideSpy).toHaveBeenCalledWith( expect(dbOverrideSpy).toHaveBeenCalledWith(
"msg_123", "msg_123",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
undefined, undefined,
) )
}) })
test("should skip override trigger when current model already matches ultrawork model", () => { test("should skip override trigger when current model already matches ultrawork model", () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something", { const output = createOutput("ultrawork do something", {
existingModel: { providerID: "anthropic", modelID: "claude-opus-4-6" }, existingModel: { providerID: "anthropic", modelID: "claude-opus-4-7" },
messageId: "msg_123", messageId: "msg_123",
}) })
let toastCalled = false let toastCalled = false
@@ -465,13 +465,13 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
test("should apply validated variant when SDK confirms model supports it", async () => { test("should apply validated variant when SDK confirms model supports it", async () => {
//#given //#given
const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-6", variant: "max" }) const config = createConfig("sisyphus", { model: "anthropic/claude-opus-4-7", variant: "max" })
const output = createOutput("ultrawork do something", { messageId: "msg_123" }) const output = createOutput("ultrawork do something", { messageId: "msg_123" })
const tui = createMockTui() const tui = createMockTui()
const mockClient = { const mockClient = {
provider: { provider: {
list: async () => ({ list: async () => ({
data: { all: [{ id: "anthropic", models: { "claude-opus-4-6": { variants: { max: {} } } } }] }, data: { all: [{ id: "anthropic", models: { "claude-opus-4-7": { variants: { max: {} } } } }] },
}), }),
}, },
} }
@@ -482,7 +482,7 @@ describe("applyUltraworkModelOverrideOnMessage", () => {
//#then - SDK confirmed max exists, so variant is applied //#then - SDK confirmed max exists, so variant is applied
expect(dbOverrideSpy).toHaveBeenCalledWith( expect(dbOverrideSpy).toHaveBeenCalledWith(
"msg_123", "msg_123",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
"max", "max",
) )
}) })
@@ -23,7 +23,7 @@ describe("resolveValidUltraworkVariant", () => {
// given // given
const client = createClient({ const client = createClient({
anthropic: { anthropic: {
"claude-opus-4-6": { "claude-opus-4-7": {
variants: { variants: {
max: {}, max: {},
high: {}, high: {},
@@ -35,7 +35,7 @@ describe("resolveValidUltraworkVariant", () => {
// when // when
const result = await resolveValidUltraworkVariant( const result = await resolveValidUltraworkVariant(
client, client,
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
"max", "max",
) )
@@ -47,7 +47,7 @@ describe("resolveValidUltraworkVariant", () => {
// given // given
const client = createClient({ const client = createClient({
anthropic: { anthropic: {
"claude-opus-4-6": { "claude-opus-4-7": {
variants: { variants: {
high: {}, high: {},
}, },
@@ -58,7 +58,7 @@ describe("resolveValidUltraworkVariant", () => {
// when // when
const result = await resolveValidUltraworkVariant( const result = await resolveValidUltraworkVariant(
client, client,
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
"max", "max",
) )
@@ -87,7 +87,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
// given // given
const client = createClient({ const client = createClient({
anthropic: { anthropic: {
"claude-opus-4-6": { "claude-opus-4-7": {
variants: { variants: {
high: {}, high: {},
}, },
@@ -100,7 +100,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
agents: { agents: {
sisyphus: { sisyphus: {
ultrawork: { ultrawork: {
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
variant: "max", variant: "max",
}, },
}, },
@@ -130,7 +130,7 @@ describe("applyUltraworkModelOverrideOnMessage variant guard", () => {
expect(output.message["thinking"]).toBeUndefined() expect(output.message["thinking"]).toBeUndefined()
expect(dbOverrideSpy).toHaveBeenCalledWith( expect(dbOverrideSpy).toHaveBeenCalledWith(
"msg_123", "msg_123",
{ providerID: "anthropic", modelID: "claude-opus-4-6" }, { providerID: "anthropic", modelID: "claude-opus-4-7" },
undefined, undefined,
) )
dbOverrideSpy.mockRestore() dbOverrideSpy.mockRestore()
+15 -15
View File
@@ -8,9 +8,9 @@ describe("Agent Config Integration", () => {
test("migrates old format agent keys to lowercase", () => { test("migrates old format agent keys to lowercase", () => {
// given - config with old format keys // given - config with old format keys
const oldConfig = { const oldConfig = {
Sisyphus: { model: "anthropic/claude-opus-4-6" }, Sisyphus: { model: "anthropic/claude-opus-4-7" },
Atlas: { model: "anthropic/claude-opus-4-6" }, Atlas: { model: "anthropic/claude-opus-4-7" },
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" }, "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
"Metis - Plan Consultant": { model: "anthropic/claude-sonnet-4-6" }, "Metis - Plan Consultant": { model: "anthropic/claude-sonnet-4-6" },
"Momus - Plan Critic": { model: "anthropic/claude-sonnet-4-6" }, "Momus - Plan Critic": { model: "anthropic/claude-sonnet-4-6" },
} }
@@ -33,9 +33,9 @@ describe("Agent Config Integration", () => {
expect(result.migrated).not.toHaveProperty("Momus - Plan Critic") expect(result.migrated).not.toHaveProperty("Momus - Plan Critic")
// then - values are preserved // then - values are preserved
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-7" })
expect(result.migrated.atlas).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(result.migrated.atlas).toEqual({ model: "anthropic/claude-opus-4-7" })
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" })
// then - changed flag is true // then - changed flag is true
expect(result.changed).toBe(true) expect(result.changed).toBe(true)
@@ -44,7 +44,7 @@ describe("Agent Config Integration", () => {
test("preserves already lowercase keys", () => { test("preserves already lowercase keys", () => {
// 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-7" },
oracle: { model: "openai/gpt-5.4" }, oracle: { model: "openai/gpt-5.4" },
librarian: { model: "opencode/big-pickle" }, librarian: { model: "opencode/big-pickle" },
} }
@@ -62,9 +62,9 @@ describe("Agent Config Integration", () => {
test("handles mixed case config", () => { test("handles mixed case config", () => {
// 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-7" },
oracle: { model: "openai/gpt-5.4" }, oracle: { model: "openai/gpt-5.4" },
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" }, "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
librarian: { model: "opencode/big-pickle" }, librarian: { model: "opencode/big-pickle" },
} }
@@ -173,8 +173,8 @@ describe("Agent Config Integration", () => {
test("old config migrates and displays correctly", () => { test("old config migrates and displays correctly", () => {
// given - old format config // given - old format config
const oldConfig = { const oldConfig = {
Sisyphus: { model: "anthropic/claude-opus-4-6", temperature: 0.1 }, Sisyphus: { model: "anthropic/claude-opus-4-7", temperature: 0.1 },
"Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-6" }, "Prometheus - Plan Builder": { model: "anthropic/claude-opus-4-7" },
} }
// when - config is migrated // when - config is migrated
@@ -193,15 +193,15 @@ describe("Agent Config Integration", () => {
expect(prometheusDisplay).toBe("Prometheus - Plan Builder") expect(prometheusDisplay).toBe("Prometheus - Plan Builder")
// then - config values are preserved // then - config values are preserved
expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-6", temperature: 0.1 }) expect(result.migrated.sisyphus).toEqual({ model: "anthropic/claude-opus-4-7", temperature: 0.1 })
expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(result.migrated.prometheus).toEqual({ model: "anthropic/claude-opus-4-7" })
}) })
test("new config works without migration", () => { test("new config works without migration", () => {
// given - new format config (already lowercase) // given - new format config (already lowercase)
const newConfig = { const newConfig = {
sisyphus: { model: "anthropic/claude-opus-4-6" }, sisyphus: { model: "anthropic/claude-opus-4-7" },
atlas: { model: "anthropic/claude-opus-4-6" }, atlas: { model: "anthropic/claude-opus-4-7" },
} }
// when - migration is applied (should be no-op) // when - migration is applied (should be no-op)
+5 -5
View File
@@ -84,14 +84,14 @@ describe("applyAgentVariant", () => {
describe("resolveVariantForModel", () => { describe("resolveVariantForModel", () => {
test("returns agent override variant when configured", () => { test("returns agent override variant when configured", () => {
// given - use a model in sisyphus chain (claude-opus-4-6 has default variant "max") // given - use a model in sisyphus chain (claude-opus-4-7 has default variant "max")
// to verify override takes precedence over fallback chain // to verify override takes precedence over fallback chain
const config = { const config = {
agents: { agents: {
sisyphus: { variant: "high" }, sisyphus: { variant: "high" },
}, },
} as OhMyOpenCodeConfig } as OhMyOpenCodeConfig
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
// when // when
const variant = resolveVariantForModel(config, "sisyphus", model) const variant = resolveVariantForModel(config, "sisyphus", model)
@@ -103,7 +103,7 @@ describe("resolveVariantForModel", () => {
test("returns correct variant for anthropic provider", () => { test("returns correct variant for anthropic provider", () => {
// given // given
const config = {} as OhMyOpenCodeConfig const config = {} as OhMyOpenCodeConfig
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
// when // when
const variant = resolveVariantForModel(config, "sisyphus", model) const variant = resolveVariantForModel(config, "sisyphus", model)
@@ -151,7 +151,7 @@ describe("resolveVariantForModel", () => {
test("returns undefined for unknown agent", () => { test("returns undefined for unknown agent", () => {
// given // given
const config = {} as OhMyOpenCodeConfig const config = {} as OhMyOpenCodeConfig
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
// when // when
const variant = resolveVariantForModel(config, "nonexistent-agent", model) const variant = resolveVariantForModel(config, "nonexistent-agent", model)
@@ -203,7 +203,7 @@ describe("resolveVariantForModel", () => {
test("returns correct variant for oracle agent with anthropic", () => { test("returns correct variant for oracle agent with anthropic", () => {
// given // given
const config = {} as OhMyOpenCodeConfig const config = {} as OhMyOpenCodeConfig
const model = { providerID: "anthropic", modelID: "claude-opus-4-6" } const model = { providerID: "anthropic", modelID: "claude-opus-4-7" }
// when // when
const variant = resolveVariantForModel(config, "oracle", model) const variant = resolveVariantForModel(config, "oracle", model)
+2 -2
View File
@@ -61,7 +61,7 @@ describe("updateConnectedProvidersCache", () => {
name: "Anthropic", name: "Anthropic",
env: [], env: [],
models: { models: {
"claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, "claude-opus-4-7": { id: "claude-opus-4-7", name: "Claude Opus 4.7" },
"claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, "claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
}, },
}, },
@@ -84,7 +84,7 @@ describe("updateConnectedProvidersCache", () => {
{ id: "gpt-5.4", name: "GPT-5.4" }, { id: "gpt-5.4", name: "GPT-5.4" },
], ],
anthropic: [ anthropic: [
{ id: "claude-opus-4-6", name: "Claude Opus 4.6" }, { id: "claude-opus-4-7", name: "Claude Opus 4.7" },
{ id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
], ],
}) })
+6 -6
View File
@@ -28,15 +28,15 @@ describe("resolveActualContextLimit", () => {
resetContextLimitEnv() resetContextLimitEnv()
}) })
it("returns cached limit for Anthropic 4.6 models when 1M mode is disabled (GA support)", () => { it("returns cached limit for Anthropic 4.7 models when 1M mode is disabled (GA support)", () => {
// given // given
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY] delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
delete process.env[VERTEX_CONTEXT_ENV_KEY] delete process.env[VERTEX_CONTEXT_ENV_KEY]
const modelContextLimitsCache = new Map<string, number>() const modelContextLimitsCache = new Map<string, number>()
modelContextLimitsCache.set("anthropic/claude-opus-4-6", 1_000_000) modelContextLimitsCache.set("anthropic/claude-opus-4-7", 1_000_000)
// when // when
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-6", { const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4-7", {
anthropicContext1MEnabled: false, anthropicContext1MEnabled: false,
modelContextLimitsCache, modelContextLimitsCache,
}) })
@@ -107,15 +107,15 @@ describe("resolveActualContextLimit", () => {
expect(actualLimit).toBe(200000) expect(actualLimit).toBe(200000)
}) })
it("supports Anthropic 4.6 dot-version model IDs without explicit 1M mode", () => { it("supports Anthropic 4.7 dot-version model IDs without explicit 1M mode", () => {
// given // given
delete process.env[ANTHROPIC_CONTEXT_ENV_KEY] delete process.env[ANTHROPIC_CONTEXT_ENV_KEY]
delete process.env[VERTEX_CONTEXT_ENV_KEY] delete process.env[VERTEX_CONTEXT_ENV_KEY]
const modelContextLimitsCache = new Map<string, number>() const modelContextLimitsCache = new Map<string, number>()
modelContextLimitsCache.set("anthropic/claude-opus-4.6", 1_000_000) modelContextLimitsCache.set("anthropic/claude-opus-4.7", 1_000_000)
// when // when
const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.6", { const actualLimit = resolveActualContextLimit("anthropic", "claude-opus-4.7", {
anthropicContext1MEnabled: false, anthropicContext1MEnabled: false,
modelContextLimitsCache, modelContextLimitsCache,
}) })
+1 -1
View File
@@ -20,7 +20,7 @@ function getAnthropicActualLimit(modelCacheState?: ContextLimitModelCacheState):
} }
function supportsCachedAnthropicLimit(modelID: string): boolean { function supportsCachedAnthropicLimit(modelID: string): boolean {
return /^claude-(opus|sonnet)-4(?:-|\.)6(?:-high)?$/.test(modelID) return /^claude-(opus|sonnet)-4(?:-|\.)(?:6|7)(?:-high)?$/.test(modelID)
} }
export function resolveActualContextLimit( export function resolveActualContextLimit(
+2 -2
View File
@@ -71,7 +71,7 @@ describe("mergeCategories", () => {
it("user overrides merge with defaults", () => { it("user overrides merge with defaults", () => {
//#given //#given
const userCategories = { const userCategories = {
"ultrabrain": { model: "anthropic/claude-opus-4-6" }, "ultrabrain": { model: "anthropic/claude-opus-4-7" },
} }
//#when //#when
@@ -79,6 +79,6 @@ describe("mergeCategories", () => {
//#then //#then
expect(result["ultrabrain"]).toBeDefined() expect(result["ultrabrain"]).toBeDefined()
expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-6") expect(result["ultrabrain"].model).toBe("anthropic/claude-opus-4-7")
}) })
}) })
+27 -27
View File
@@ -19,7 +19,7 @@ describe("migrateAgentNames", () => {
test("migrates legacy OmO names to lowercase", () => { test("migrates legacy OmO names to lowercase", () => {
// given: Config with legacy OmO agent names // given: Config with legacy OmO agent names
const agents = { const agents = {
omo: { model: "anthropic/claude-opus-4-6" }, omo: { model: "anthropic/claude-opus-4-7" },
OmO: { temperature: 0.5 }, OmO: { temperature: 0.5 },
"OmO-Plan": { prompt: "custom prompt" }, "OmO-Plan": { prompt: "custom prompt" },
} }
@@ -88,7 +88,7 @@ describe("migrateAgentNames", () => {
test("migrates orchestrator-sisyphus to atlas", () => { test("migrates orchestrator-sisyphus to atlas", () => {
// given: Config with legacy orchestrator-sisyphus agent name // given: Config with legacy orchestrator-sisyphus agent name
const agents = { const agents = {
"orchestrator-sisyphus": { model: "anthropic/claude-opus-4-6" }, "orchestrator-sisyphus": { model: "anthropic/claude-opus-4-7" },
} }
// when: Migrate agent names // when: Migrate agent names
@@ -96,14 +96,14 @@ describe("migrateAgentNames", () => {
// then: orchestrator-sisyphus should be migrated to atlas // then: orchestrator-sisyphus should be migrated to atlas
expect(changed).toBe(true) expect(changed).toBe(true)
expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-7" })
expect(migrated["orchestrator-sisyphus"]).toBeUndefined() expect(migrated["orchestrator-sisyphus"]).toBeUndefined()
}) })
test("migrates lowercase atlas to atlas", () => { test("migrates lowercase atlas to atlas", () => {
// given: Config with lowercase atlas agent name // given: Config with lowercase atlas agent name
const agents = { const agents = {
atlas: { model: "anthropic/claude-opus-4-6" }, atlas: { model: "anthropic/claude-opus-4-7" },
} }
// when: Migrate agent names // when: Migrate agent names
@@ -111,7 +111,7 @@ describe("migrateAgentNames", () => {
// then: lowercase atlas should remain atlas (no change needed) // then: lowercase atlas should remain atlas (no change needed)
expect(changed).toBe(false) expect(changed).toBe(false)
expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-6" }) expect(migrated["atlas"]).toEqual({ model: "anthropic/claude-opus-4-7" })
}) })
test("migrates Sisyphus variants to lowercase", () => { test("migrates Sisyphus variants to lowercase", () => {
@@ -524,7 +524,7 @@ describe("migrateConfigFile", () => {
// then: Model version should be migrated // then: Model version should be migrated
expect(needsWrite).toBe(true) expect(needsWrite).toBe(true)
const categories = rawConfig.categories as Record<string, Record<string, unknown>> const categories = rawConfig.categories as Record<string, Record<string, unknown>>
expect(categories["my-category"].model).toBe("anthropic/claude-opus-4-6") expect(categories["my-category"].model).toBe("anthropic/claude-opus-4-7")
}) })
test("does not set needsWrite when no model versions need migration", () => { test("does not set needsWrite when no model versions need migration", () => {
@@ -534,7 +534,7 @@ describe("migrateConfigFile", () => {
sisyphus: { model: "openai/gpt-5.4-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-7" },
}, },
} }
@@ -572,10 +572,10 @@ describe("MODEL_VERSION_MAP", () => {
expect(MODEL_VERSION_MAP["openai/gpt-5.4-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-7", () => {
// given/when: Check MODEL_VERSION_MAP // given/when: Check MODEL_VERSION_MAP
// then: Should contain correct mapping // then: Should contain correct mapping
expect(MODEL_VERSION_MAP["anthropic/claude-opus-4-5"]).toBe("anthropic/claude-opus-4-6") expect(MODEL_VERSION_MAP["anthropic/claude-opus-4-5"]).toBe("anthropic/claude-opus-4-7")
}) })
test("maps openai/gpt-5.3-codex to openai/gpt-5.4 for deep category migration", () => { test("maps openai/gpt-5.3-codex to openai/gpt-5.4 for deep category migration", () => {
@@ -614,7 +614,7 @@ describe("migrateModelVersions", () => {
// then: Model should be updated // then: Model should be updated
expect(changed).toBe(true) expect(changed).toBe(true)
const prometheus = migrated["prometheus"] as Record<string, unknown> const prometheus = migrated["prometheus"] as Record<string, unknown>
expect(prometheus.model).toBe("anthropic/claude-opus-4-6") expect(prometheus.model).toBe("anthropic/claude-opus-4-7")
}) })
test("leaves unknown model strings untouched", () => { test("leaves unknown model strings untouched", () => {
@@ -674,7 +674,7 @@ 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.4-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-7")
expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.4") expect((migrated["oracle"] as Record<string, unknown>).model).toBe("openai/gpt-5.4")
}) })
@@ -736,9 +736,9 @@ 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-7"])
expect((migrated["sisyphus"] as Record<string, unknown>).model).toBe("openai/gpt-5.4-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-7")
}) })
test("backward compatible without appliedMigrations param", () => { test("backward compatible without appliedMigrations param", () => {
@@ -820,12 +820,12 @@ describe("migrateConfigFile _migrations tracking", () => {
// (legacy + new) is written to the sidecar file exactly once. // (legacy + new) is written to the sidecar file exactly once.
expect(result).toBe(true) expect(result).toBe(true)
expect(rawConfig._migrations).toBeUndefined() expect(rawConfig._migrations).toBeUndefined()
expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe("anthropic/claude-opus-4-6") expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe("anthropic/claude-opus-4-7")
const sidecar = JSON.parse(fs.readFileSync(`${configPath}.migrations.json`, "utf-8")) const sidecar = JSON.parse(fs.readFileSync(`${configPath}.migrations.json`, "utf-8"))
expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([ expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([
"model-version:openai/gpt-5.4-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-7",
])) ]))
// cleanup // cleanup
@@ -890,7 +890,7 @@ describe("migrateAgentConfigToCategory", () => {
{ model: "google/gemini-3-flash" }, { model: "google/gemini-3-flash" },
{ model: "openai/gpt-5.4" }, { 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-7" },
{ model: "anthropic/claude-sonnet-4-6" }, { model: "anthropic/claude-sonnet-4-6" },
] ]
@@ -970,7 +970,7 @@ describe("shouldDeleteAgentConfig", () => {
// given: Config with custom model override // given: Config with custom model override
const config = { const config = {
category: "visual-engineering", category: "visual-engineering",
model: "anthropic/claude-opus-4-6", model: "anthropic/claude-opus-4-7",
} }
// when: Check if config should be deleted // when: Check if config should be deleted
@@ -1245,9 +1245,9 @@ 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-7"])
expect((migrated.sisyphus as Record<string, unknown>).model).toBe("openai/gpt-5.4-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-7")
}) })
test("backward compatible: no appliedMigrations param", () => { test("backward compatible: no appliedMigrations param", () => {
@@ -1334,12 +1334,12 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
const needsWrite = migrateConfigFile(testConfigPath, rawConfig) const needsWrite = migrateConfigFile(testConfigPath, rawConfig)
expect(needsWrite).toBe(true) expect(needsWrite).toBe(true)
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-7")
expect(rawConfig._migrations).toBeUndefined() expect(rawConfig._migrations).toBeUndefined()
const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8"))
expect(sidecar.appliedMigrations).toEqual([ expect(sidecar.appliedMigrations).toEqual([
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
]) ])
}) })
@@ -1408,7 +1408,7 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
JSON.stringify({ JSON.stringify({
appliedMigrations: [ appliedMigrations: [
"model-version:openai/gpt-5.3-codex->openai/gpt-5.4", "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
], ],
}), }),
) )
@@ -1416,7 +1416,7 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
agents: { agents: {
oracle: { model: "anthropic/claude-opus-4-5" }, oracle: { model: "anthropic/claude-opus-4-5" },
}, },
_migrations: ["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6"], _migrations: ["model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7"],
} }
fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2)) fs.writeFileSync(testConfigPath, JSON.stringify(rawConfig, null, 2))
@@ -1430,7 +1430,7 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8"))
expect(sidecar.appliedMigrations).toEqual([ expect(sidecar.appliedMigrations).toEqual([
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
"model-version:openai/gpt-5.3-codex->openai/gpt-5.4", "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
]) ])
}) })
@@ -1460,13 +1460,13 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
// codex was reverted, must stay // codex was reverted, must stay
expect((rawConfig.agents as Record<string, Record<string, unknown>>).codex.model).toBe("openai/gpt-5.3-codex") expect((rawConfig.agents as Record<string, Record<string, unknown>>).codex.model).toBe("openai/gpt-5.3-codex")
// claude migrates // claude migrates
expect((rawConfig.agents as Record<string, Record<string, unknown>>).claude.model).toBe("anthropic/claude-opus-4-6") expect((rawConfig.agents as Record<string, Record<string, unknown>>).claude.model).toBe("anthropic/claude-opus-4-7")
expect(rawConfig._migrations).toBeUndefined() expect(rawConfig._migrations).toBeUndefined()
const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8")) const sidecar = JSON.parse(fs.readFileSync(sidecarPath(testConfigPath), "utf-8"))
expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([ expect(new Set(sidecar.appliedMigrations)).toEqual(new Set([
"model-version:openai/gpt-5.3-codex->openai/gpt-5.4", "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
])) ]))
}) })
@@ -1494,7 +1494,7 @@ describe("migrateConfigFile with migration tracking via sidecar (#3263)", () =>
expect(Array.isArray(migrations)).toBe(true) expect(Array.isArray(migrations)).toBe(true)
expect(migrations).toContain("model-version:openai/gpt-5.3-codex->openai/gpt-5.4") expect(migrations).toContain("model-version:openai/gpt-5.3-codex->openai/gpt-5.4")
expect(migrations.length).toBeGreaterThanOrEqual(1) expect(migrations.length).toBeGreaterThanOrEqual(1)
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-7")
// Sidecar should not exist because write failed // Sidecar should not exist because write failed
expect(fs.existsSync(sidecarPath(testConfigPath))).toBe(false) expect(fs.existsSync(sidecarPath(testConfigPath))).toBe(false)
+1
View File
@@ -17,6 +17,7 @@ export const MODEL_TO_CATEGORY_MAP: Record<string, string> = {
"openai/gpt-5.4": "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-opus-4-7": "unspecified-high",
"anthropic/claude-sonnet-4-6": "unspecified-low", "anthropic/claude-sonnet-4-6": "unspecified-low",
} }
@@ -8,7 +8,7 @@ import { migrateConfigFile } from "./config-migration"
import { getSidecarPath } from "./migrations-sidecar" import { getSidecarPath } from "./migrations-sidecar"
const createdDirectories: string[] = [] const createdDirectories: string[] = []
const MIGRATION_KEY = "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6" const MIGRATION_KEY = "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7"
function createWorkdir(): string { function createWorkdir(): string {
const workdir = mkdtempSync(join(tmpdir(), "omo-config-migration-")) const workdir = mkdtempSync(join(tmpdir(), "omo-config-migration-"))
@@ -46,13 +46,13 @@ describe("migrateConfigFile sidecar write ordering", () => {
expect(needsWrite).toBe(true) expect(needsWrite).toBe(true)
expect(rawConfig._migrations).toBeUndefined() expect(rawConfig._migrations).toBeUndefined()
expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe( expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
) )
const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown> const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>
expect(persistedConfig._migrations).toBeUndefined() expect(persistedConfig._migrations).toBeUndefined()
expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe( expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
) )
const sidecar = JSON.parse(readFileSync(getSidecarPath(configPath), "utf-8")) as { const sidecar = JSON.parse(readFileSync(getSidecarPath(configPath), "utf-8")) as {
@@ -87,7 +87,7 @@ describe("migrateConfigFile sidecar write ordering", () => {
expect(retriedNeedsWrite).toBe(true) expect(retriedNeedsWrite).toBe(true)
expect(retriedConfig._migrations).toBeUndefined() expect(retriedConfig._migrations).toBeUndefined()
expect((retriedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe( expect((retriedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
) )
expect(existsSync(getSidecarPath(configPath))).toBe(true) expect(existsSync(getSidecarPath(configPath))).toBe(true)
}) })
@@ -108,13 +108,13 @@ describe("migrateConfigFile sidecar write ordering", () => {
expect(needsWrite).toBe(true) expect(needsWrite).toBe(true)
expect(rawConfig._migrations).toEqual([MIGRATION_KEY]) expect(rawConfig._migrations).toEqual([MIGRATION_KEY])
expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe( expect((rawConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
) )
const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown> const persistedConfig = JSON.parse(readFileSync(configPath, "utf-8")) as Record<string, unknown>
expect(persistedConfig._migrations).toEqual([MIGRATION_KEY]) expect(persistedConfig._migrations).toEqual([MIGRATION_KEY])
expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe( expect((persistedConfig.agents as Record<string, Record<string, unknown>>).prometheus.model).toBe(
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
) )
expect(statSync(getSidecarPath(configPath)).isDirectory()).toBe(true) expect(statSync(getSidecarPath(configPath)).isDirectory()).toBe(true)
}) })
@@ -42,7 +42,7 @@ describe("migrations sidecar", () => {
JSON.stringify({ JSON.stringify({
appliedMigrations: [ appliedMigrations: [
"model-version:openai/gpt-5.3-codex->openai/gpt-5.4", "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
], ],
}), }),
) )
@@ -51,7 +51,7 @@ describe("migrations sidecar", () => {
expect(applied.size).toBe(2) expect(applied.size).toBe(2)
expect(applied.has("model-version:openai/gpt-5.3-codex->openai/gpt-5.4")).toBe(true) expect(applied.has("model-version:openai/gpt-5.3-codex->openai/gpt-5.4")).toBe(true)
expect(applied.has("model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6")).toBe(true) expect(applied.has("model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7")).toBe(true)
}) })
test("returns an empty set on malformed JSON instead of throwing", () => { test("returns an empty set on malformed JSON instead of throwing", () => {
@@ -134,7 +134,7 @@ describe("migrations sidecar", () => {
const configPath = join(workdir, "oh-my-openagent.jsonc") const configPath = join(workdir, "oh-my-openagent.jsonc")
const original = new Set([ const original = new Set([
"model-version:openai/gpt-5.3-codex->openai/gpt-5.4", "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
"model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6", "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7",
]) ])
writeAppliedMigrations(configPath, original) writeAppliedMigrations(configPath, original)
+1 -1
View File
@@ -22,7 +22,7 @@ import { writeFileAtomically } from "../write-file-atomically"
* { * {
* "appliedMigrations": [ * "appliedMigrations": [
* "model-version:openai/gpt-5.3-codex->openai/gpt-5.4", * "model-version:openai/gpt-5.3-codex->openai/gpt-5.4",
* "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-6" * "model-version:anthropic/claude-opus-4-5->anthropic/claude-opus-4-7"
* ] * ]
* } * }
*/ */
+2 -1
View File
@@ -6,7 +6,8 @@
* Keys are full "provider/model" strings. Only openai and anthropic entries needed. * Keys are full "provider/model" strings. Only openai and anthropic entries needed.
*/ */
export const MODEL_VERSION_MAP: Record<string, string> = { export const MODEL_VERSION_MAP: Record<string, string> = {
"anthropic/claude-opus-4-5": "anthropic/claude-opus-4-6", "anthropic/claude-opus-4-5": "anthropic/claude-opus-4-7",
"anthropic/claude-opus-4-6": "anthropic/claude-opus-4-7",
"anthropic/claude-sonnet-4-5": "anthropic/claude-sonnet-4-6", "anthropic/claude-sonnet-4-5": "anthropic/claude-sonnet-4-6",
"openai/gpt-5.3-codex": "openai/gpt-5.4", "openai/gpt-5.3-codex": "openai/gpt-5.4",
} }
+43 -43
View File
@@ -65,7 +65,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.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
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" } } },
}) })
@@ -76,7 +76,7 @@ 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.4")).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-7")).toBe(true)
expect(result.has("google/gemini-3.1-pro")).toBe(true) expect(result.has("google/gemini-3.1-pro")).toBe(true)
}) })
@@ -145,7 +145,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.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
}) })
const result1 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] }) const result1 = await fetchAvailableModels(undefined, { connectedProviders: ["openai"] })
@@ -192,7 +192,7 @@ describe("fuzzyMatchModel", () => {
const available = new Set([ const available = new Set([
"openai/gpt-5.4", "openai/gpt-5.4",
"openai/gpt-5.3-codex", "openai/gpt-5.3-codex",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]) ])
const result = fuzzyMatchModel("gpt-5.4", available) const result = fuzzyMatchModel("gpt-5.4", available)
expect(result).toBe("openai/gpt-5.4") expect(result).toBe("openai/gpt-5.4")
@@ -239,25 +239,25 @@ describe("fuzzyMatchModel", () => {
// given available models with claude variants // given available models with claude variants
// when searching for claude-opus // when searching for claude-opus
// then return matching claude-opus model // then return matching claude-opus model
it("should match claude-opus to claude-opus-4-6", () => { it("should match claude-opus to claude-opus-4-7", () => {
const available = new Set([ const available = new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"anthropic/claude-sonnet-4-6", "anthropic/claude-sonnet-4-6",
]) ])
const result = fuzzyMatchModel("claude-opus", available) const result = fuzzyMatchModel("claude-opus", available)
expect(result).toBe("anthropic/claude-opus-4-6") expect(result).toBe("anthropic/claude-opus-4-7")
}) })
// given github-copilot serves claude versions with dot notation // given github-copilot serves claude versions with dot notation
// when fallback chain uses hyphen notation in requested model // when fallback chain uses hyphen notation in requested model
// then normalize both forms and match github-copilot model // then normalize both forms and match github-copilot model
it("should match github-copilot claude-opus-4-6 to claude-opus-4.6", () => { it("should match github-copilot claude-opus-4-7 to claude-opus-4.7", () => {
const available = new Set([ const available = new Set([
"github-copilot/claude-opus-4.6", "github-copilot/claude-opus-4.7",
"opencode/big-pickle", "opencode/big-pickle",
]) ])
const result = fuzzyMatchModel("claude-opus-4-6", available, ["github-copilot"]) const result = fuzzyMatchModel("claude-opus-4-7", available, ["github-copilot"])
expect(result).toBe("github-copilot/claude-opus-4.6") expect(result).toBe("github-copilot/claude-opus-4.7")
}) })
// given claude models can evolve to newer version numbers // given claude models can evolve to newer version numbers
@@ -275,7 +275,7 @@ describe("fuzzyMatchModel", () => {
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.4", "openai/gpt-5.4",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"google/gemini-3", "google/gemini-3",
]) ])
const result = fuzzyMatchModel("gpt", available, ["openai"]) const result = fuzzyMatchModel("gpt", available, ["openai"])
@@ -288,7 +288,7 @@ describe("fuzzyMatchModel", () => {
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.4", "openai/gpt-5.4",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]) ])
const result = fuzzyMatchModel("claude", available, ["openai"]) const result = fuzzyMatchModel("claude", available, ["openai"])
expect(result).toBeNull() expect(result).toBeNull()
@@ -300,7 +300,7 @@ describe("fuzzyMatchModel", () => {
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.4", "openai/gpt-5.4",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]) ])
const result = fuzzyMatchModel("gemini", available) const result = fuzzyMatchModel("gemini", available)
expect(result).toBeNull() expect(result).toBeNull()
@@ -312,7 +312,7 @@ describe("fuzzyMatchModel", () => {
it("should match case-insensitively", () => { it("should match case-insensitively", () => {
const available = new Set([ const available = new Set([
"openai/gpt-5.4", "openai/gpt-5.4",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
]) ])
const result = fuzzyMatchModel("GPT-5.4", available) const result = fuzzyMatchModel("GPT-5.4", available)
expect(result).toBe("openai/gpt-5.4") expect(result).toBe("openai/gpt-5.4")
@@ -323,11 +323,11 @@ describe("fuzzyMatchModel", () => {
// then return exact match first // then return exact match first
it("should prioritize exact match over longer variants", () => { it("should prioritize exact match over longer variants", () => {
const available = new Set([ const available = new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"anthropic/claude-opus-4-6-extended", "anthropic/claude-opus-4-7-extended",
]) ])
const result = fuzzyMatchModel("claude-opus-4-6", available) const result = fuzzyMatchModel("claude-opus-4-7", available)
expect(result).toBe("anthropic/claude-opus-4-6") expect(result).toBe("anthropic/claude-opus-4-7")
}) })
// given available models with similar model IDs (e.g., glm-5 and big-pickle) // given available models with similar model IDs (e.g., glm-5 and big-pickle)
@@ -372,7 +372,7 @@ describe("fuzzyMatchModel", () => {
it("should search all specified providers", () => { it("should search all specified providers", () => {
const available = new Set([ const available = new Set([
"openai/gpt-5.4", "openai/gpt-5.4",
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"google/gemini-3", "google/gemini-3",
]) ])
const result = fuzzyMatchModel("gpt", available, ["openai", "google"]) const result = fuzzyMatchModel("gpt", available, ["openai", "google"])
@@ -520,7 +520,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
it("should filter models by connected providers", async () => { it("should filter models by connected providers", async () => {
writeModelsCache({ writeModelsCache({
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } }, google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
}) })
@@ -529,7 +529,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-7")).toBe(true)
expect(result.has("openai/gpt-5.4")).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)
}) })
@@ -540,7 +540,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
it("should filter models by multiple connected providers", async () => { it("should filter models by multiple connected providers", async () => {
writeModelsCache({ writeModelsCache({
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } }, google: { models: { "gemini-3.1-pro": { id: "gemini-3.1-pro" } } },
}) })
@@ -549,7 +549,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-7")).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.4")).toBe(false) expect(result.has("openai/gpt-5.4")).toBe(false)
}) })
@@ -560,7 +560,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
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.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
}) })
const result = await fetchAvailableModels(undefined, { const result = await fetchAvailableModels(undefined, {
@@ -576,7 +576,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
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.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
}) })
const result = await fetchAvailableModels() const result = await fetchAvailableModels()
@@ -605,7 +605,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
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.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
}) })
const result = await fetchAvailableModels(undefined, { const result = await fetchAvailableModels(undefined, {
@@ -613,7 +613,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-7")).toBe(true)
}) })
// given filtered fetch // given filtered fetch
@@ -622,7 +622,7 @@ describe("fetchAvailableModels with connected providers filtering", () => {
it("should not cache filtered results", async () => { it("should not cache filtered results", async () => {
writeModelsCache({ writeModelsCache({
openai: { models: { "gpt-5.4": { id: "gpt-5.4" } } }, 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-7": { id: "claude-opus-4-7" } } },
}) })
// First call with anthropic // First call with anthropic
@@ -706,13 +706,13 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
writeProviderModelsCache({ writeProviderModelsCache({
models: { models: {
opencode: ["big-pickle", "gpt-5-nano"], opencode: ["big-pickle", "gpt-5-nano"],
anthropic: ["claude-opus-4-6"] anthropic: ["claude-opus-4-7"]
}, },
connected: ["opencode", "anthropic"] connected: ["opencode", "anthropic"]
}) })
writeModelsCache({ writeModelsCache({
opencode: { models: { "big-pickle": {}, "gpt-5-nano": {}, "gpt-5.4": {} } }, 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-7": {}, "claude-sonnet-4-6": {} } }
}) })
const result = await fetchAvailableModels(undefined, { const result = await fetchAvailableModels(undefined, {
@@ -722,7 +722,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("anthropic/claude-opus-4-6")).toBe(true) expect(result.has("anthropic/claude-opus-4-7")).toBe(true)
expect(result.has("opencode/gpt-5.4")).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)
}) })
@@ -773,7 +773,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
writeProviderModelsCache({ writeProviderModelsCache({
models: { models: {
opencode: ["big-pickle"], opencode: ["big-pickle"],
anthropic: ["claude-opus-4-6"], anthropic: ["claude-opus-4-7"],
google: ["gemini-3.1-pro"] google: ["gemini-3.1-pro"]
}, },
connected: ["opencode", "anthropic", "google"] connected: ["opencode", "anthropic", "google"]
@@ -785,7 +785,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
expect(result.size).toBe(1) expect(result.size).toBe(1)
expect(result.has("opencode/big-pickle")).toBe(true) expect(result.has("opencode/big-pickle")).toBe(true)
expect(result.has("anthropic/claude-opus-4-6")).toBe(false) expect(result.has("anthropic/claude-opus-4-7")).toBe(false)
expect(result.has("google/gemini-3.1-pro")).toBe(false) expect(result.has("google/gemini-3.1-pro")).toBe(false)
}) })
@@ -812,7 +812,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
it("should handle mixed string[] and object[] formats across providers", async () => { it("should handle mixed string[] and object[] formats across providers", async () => {
writeProviderModelsCache({ writeProviderModelsCache({
models: { models: {
anthropic: ["claude-opus-4-6", "claude-sonnet-4-6"], anthropic: ["claude-opus-4-7", "claude-sonnet-4-6"],
ollama: [ ollama: [
{ id: "ministral-3:14b-32k-agent", provider: "ollama" }, { id: "ministral-3:14b-32k-agent", provider: "ollama" },
{ id: "qwen3-coder:32k-agent", provider: "ollama" } { id: "qwen3-coder:32k-agent", provider: "ollama" }
@@ -826,7 +826,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
}) })
expect(result.size).toBe(4) expect(result.size).toBe(4)
expect(result.has("anthropic/claude-opus-4-6")).toBe(true) expect(result.has("anthropic/claude-opus-4-7")).toBe(true)
expect(result.has("anthropic/claude-sonnet-4-6")).toBe(true) expect(result.has("anthropic/claude-sonnet-4-6")).toBe(true)
expect(result.has("ollama/ministral-3:14b-32k-agent")).toBe(true) expect(result.has("ollama/ministral-3:14b-32k-agent")).toBe(true)
expect(result.has("ollama/qwen3-coder:32k-agent")).toBe(true) expect(result.has("ollama/qwen3-coder:32k-agent")).toBe(true)
@@ -859,7 +859,7 @@ describe("fetchAvailableModels with provider-models cache (whitelist-filtered)",
describe("isModelAvailable", () => { describe("isModelAvailable", () => {
it("returns true when model exists via fuzzy match", () => { it("returns true when model exists via fuzzy match", () => {
// given // given
const available = new Set(["openai/gpt-5.3-codex", "anthropic/claude-opus-4-6"]) const available = new Set(["openai/gpt-5.3-codex", "anthropic/claude-opus-4-7"])
// when // when
const result = isModelAvailable("gpt-5.3-codex", available) const result = isModelAvailable("gpt-5.3-codex", available)
@@ -870,7 +870,7 @@ describe("isModelAvailable", () => {
it("returns false when model not found", () => { it("returns false when model not found", () => {
// given // given
const available = new Set(["anthropic/claude-opus-4-6"]) const available = new Set(["anthropic/claude-opus-4-7"])
// when // when
const result = isModelAvailable("gpt-5.3-codex", available) const result = isModelAvailable("gpt-5.3-codex", available)
@@ -924,7 +924,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.4", "anthropic/claude-opus-4-6"]) const available = new Set(["openai/gpt-5.4", "anthropic/claude-opus-4-7"])
// when // when
const result = fuzzyMatchModel("non-existent-model-family", available) const result = fuzzyMatchModel("non-existent-model-family", available)
@@ -936,7 +936,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.4" }] 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-7"])
writeConnectedProvidersCache(["openai"]) writeConnectedProvidersCache(["openai"])
// when // when
@@ -950,10 +950,10 @@ describe("fallback model availability", () => {
// given // given
const fallbackChain = [ const fallbackChain = [
{ providers: ["openai"], model: "gpt-5.4" }, { providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" }, { providers: ["anthropic"], model: "claude-opus-4-7" },
] ]
const availableModels = new Set([ const availableModels = new Set([
"anthropic/claude-opus-4-6", "anthropic/claude-opus-4-7",
"openai/gpt-5.4-preview", "openai/gpt-5.4-preview",
]) ])
@@ -968,7 +968,7 @@ describe("fallback model availability", () => {
// given // given
const fallbackChain = [ const fallbackChain = [
{ providers: ["openai"], model: "gpt-5.4" }, { providers: ["openai"], model: "gpt-5.4" },
{ providers: ["anthropic"], model: "claude-opus-4-6" }, { providers: ["anthropic"], model: "claude-opus-4-7" },
] ]
const availableModels = new Set(["google/gemini-3.1-pro"]) const availableModels = new Set(["google/gemini-3.1-pro"])
+1 -1
View File
@@ -21,7 +21,7 @@ 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.4", "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-7"])
* fuzzyMatchModel("gpt-5.4", available) // → "openai/gpt-5.4" * 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)
*/ */
+9 -9
View File
@@ -16,8 +16,8 @@ describe("getModelCapabilities", () => {
generatedAt: "2026-03-25T00:00:00.000Z", generatedAt: "2026-03-25T00:00:00.000Z",
sourceUrl: "https://models.dev/api.json", sourceUrl: "https://models.dev/api.json",
models: { models: {
"claude-opus-4-6": { "claude-opus-4-7": {
id: "claude-opus-4-6", id: "claude-opus-4-7",
family: "claude-opus", family: "claude-opus",
reasoning: true, reasoning: true,
temperature: true, temperature: true,
@@ -66,7 +66,7 @@ describe("getModelCapabilities", () => {
findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined) findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined)
const result = getModelCapabilities({ const result = getModelCapabilities({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6", modelID: "claude-opus-4-7",
runtimeModel: { runtimeModel: {
variants: { variants: {
low: {}, low: {},
@@ -78,7 +78,7 @@ describe("getModelCapabilities", () => {
}) })
expect(result).toMatchObject({ expect(result).toMatchObject({
canonicalModelID: "claude-opus-4-6", canonicalModelID: "claude-opus-4-7",
family: "claude-opus", family: "claude-opus",
variants: ["low", "medium", "high"], variants: ["low", "medium", "high"],
supportsThinking: true, supportsThinking: true,
@@ -173,12 +173,12 @@ describe("getModelCapabilities", () => {
findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined) findProviderModelMetadataSpy = spyOn(connectedProvidersCache, "findProviderModelMetadata").mockReturnValue(undefined)
const result = getModelCapabilities({ const result = getModelCapabilities({
providerID: "anthropic", providerID: "anthropic",
modelID: "claude-opus-4-6-thinking", modelID: "claude-opus-4-7-thinking",
bundledSnapshot, bundledSnapshot,
}) })
expect(result).toMatchObject({ expect(result).toMatchObject({
canonicalModelID: "claude-opus-4-6", canonicalModelID: "claude-opus-4-7",
family: "claude-opus", family: "claude-opus",
supportsThinking: true, supportsThinking: true,
supportsTemperature: true, supportsTemperature: true,
@@ -247,13 +247,13 @@ describe("getModelCapabilities", () => {
test("canonicalizes provider-prefixed Claude thinking aliases to bare snapshot IDs", () => { test("canonicalizes provider-prefixed Claude thinking aliases to bare snapshot IDs", () => {
const result = getModelCapabilities({ const result = getModelCapabilities({
providerID: "anthropic", providerID: "anthropic",
modelID: "anthropic/claude-opus-4-6-thinking", modelID: "anthropic/claude-opus-4-7-thinking",
bundledSnapshot, bundledSnapshot,
}) })
expect(result).toMatchObject({ expect(result).toMatchObject({
requestedModelID: "anthropic/claude-opus-4-6-thinking", requestedModelID: "anthropic/claude-opus-4-7-thinking",
canonicalModelID: "claude-opus-4-6", canonicalModelID: "claude-opus-4-7",
family: "claude-opus", family: "claude-opus",
supportsThinking: true, supportsThinking: true,
supportsTemperature: true, supportsTemperature: true,
+9 -9
View File
@@ -67,22 +67,22 @@ describe("model-capability-aliases", () => {
}) })
test("normalizes provider-prefixed Claude thinking aliases through a pattern rule", () => { test("normalizes provider-prefixed Claude thinking aliases through a pattern rule", () => {
const result = resolveModelIDAlias("anthropic/claude-opus-4-6-thinking") const result = resolveModelIDAlias("anthropic/claude-opus-4-7-thinking")
expect(result).toEqual({ expect(result).toEqual({
requestedModelID: "anthropic/claude-opus-4-6-thinking", requestedModelID: "anthropic/claude-opus-4-7-thinking",
canonicalModelID: "claude-opus-4-6", canonicalModelID: "claude-opus-4-7",
source: "pattern-alias", source: "pattern-alias",
ruleID: "claude-thinking-legacy-alias", ruleID: "claude-thinking-legacy-alias",
}) })
}) })
test("does not pattern-match nearby canonical Claude IDs incorrectly", () => { test("does not pattern-match nearby canonical Claude IDs incorrectly", () => {
const result = resolveModelIDAlias("claude-opus-4-6-think") const result = resolveModelIDAlias("claude-opus-4-7-think")
expect(result).toEqual({ expect(result).toEqual({
requestedModelID: "claude-opus-4-6-think", requestedModelID: "claude-opus-4-7-think",
canonicalModelID: "claude-opus-4-6-think", canonicalModelID: "claude-opus-4-7-think",
source: "canonical", source: "canonical",
}) })
}) })
@@ -98,11 +98,11 @@ describe("model-capability-aliases", () => {
}) })
test("normalizes legacy Claude thinking aliases through a pattern rule", () => { test("normalizes legacy Claude thinking aliases through a pattern rule", () => {
const result = resolveModelIDAlias("claude-opus-4-6-thinking") const result = resolveModelIDAlias("claude-opus-4-7-thinking")
expect(result).toEqual({ expect(result).toEqual({
requestedModelID: "claude-opus-4-6-thinking", requestedModelID: "claude-opus-4-7-thinking",
canonicalModelID: "claude-opus-4-6", canonicalModelID: "claude-opus-4-7",
source: "pattern-alias", source: "pattern-alias",
ruleID: "claude-thinking-legacy-alias", ruleID: "claude-thinking-legacy-alias",
}) })
+3 -3
View File
@@ -41,9 +41,9 @@ const EXACT_ALIAS_RULES_BY_MODEL: ReadonlyMap<string, ExactAliasRule> = new Map(
const PATTERN_ALIAS_RULES: ReadonlyArray<PatternAliasRule> = [ const PATTERN_ALIAS_RULES: ReadonlyArray<PatternAliasRule> = [
{ {
ruleID: "claude-thinking-legacy-alias", ruleID: "claude-thinking-legacy-alias",
description: "Normalizes the legacy Claude Opus 4.6 thinking suffix to the canonical snapshot ID.", description: "Normalizes legacy Claude Opus thinking suffixes (4-6, 4-7) to the canonical snapshot ID.",
match: (normalizedModelID) => /^claude-opus-4-6-thinking$/.test(normalizedModelID), match: (normalizedModelID) => /^claude-opus-4-(?:6|7)-thinking$/.test(normalizedModelID),
canonicalize: () => "claude-opus-4-6", canonicalize: () => "claude-opus-4-7",
}, },
{ {
ruleID: "gemini-3.1-pro-tier-alias", ruleID: "gemini-3.1-pro-tier-alias",
@@ -19,7 +19,7 @@ describe("model-capability-guardrails", () => {
expect(modelIDs).toEqual([...modelIDs].sort()) expect(modelIDs).toEqual([...modelIDs].sort())
expect(new Set(modelIDs).size).toBe(modelIDs.length) expect(new Set(modelIDs).size).toBe(modelIDs.length)
expect(modelIDs).toContain("claude-opus-4-6") expect(modelIDs).toContain("claude-opus-4-7")
expect(modelIDs).toContain("gpt-5.4") expect(modelIDs).toContain("gpt-5.4")
expect(modelIDs).toContain("kimi-k2.5") expect(modelIDs).toContain("kimi-k2.5")
}) })
+1 -1
View File
@@ -31,7 +31,7 @@ describe("model-error-classifier", () => {
//#given //#given
const error = { const error = {
message: message:
"All credentials for model claude-opus-4-6-thinking are cooling down [retrying in ~5 days attempt #1]", "All credentials for model claude-opus-4-7-thinking are cooling down [retrying in ~5 days attempt #1]",
} }
//#when //#when
+2 -2
View File
@@ -9,8 +9,8 @@ describe("normalizeModelFormat", () => {
}) })
it("handles provider with multiple slashes", () => { it("handles provider with multiple slashes", () => {
const result = normalizeModelFormat("anthropic/claude-opus-4-6/max") const result = normalizeModelFormat("anthropic/claude-opus-4-7/max")
expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-6/max" }) expect(result).toEqual({ providerID: "anthropic", modelID: "claude-opus-4-7/max" })
}) })
it("returns undefined for malformed string without separator", () => { it("returns undefined for malformed string without separator", () => {
+16 -16
View File
@@ -23,7 +23,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(primary.variant).toBe("high") expect(primary.variant).toBe("high")
}) })
test("sisyphus has claude-opus-4-6 as primary with k2p5, kimi-k2.5, gpt-5.4 medium fallbacks", () => { test("sisyphus has claude-opus-4-7 as primary with k2p5, kimi-k2.5, gpt-5.4 medium fallbacks", () => {
// #given - sisyphus agent requirement // #given - sisyphus agent requirement
const sisyphus = AGENT_MODEL_REQUIREMENTS["sisyphus"] const sisyphus = AGENT_MODEL_REQUIREMENTS["sisyphus"]
@@ -36,7 +36,7 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
const primary = sisyphus.fallbackChain[0] const primary = sisyphus.fallbackChain[0]
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-7")
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
const second = sisyphus.fallbackChain[1] const second = sisyphus.fallbackChain[1]
@@ -148,34 +148,34 @@ describe("AGENT_MODEL_REQUIREMENTS", () => {
expect(last.model).toBe("gpt-5-nano") expect(last.model).toBe("gpt-5-nano")
}) })
test("prometheus has claude-opus-4-6 as primary", () => { test("prometheus has claude-opus-4-7 as primary", () => {
// #given - prometheus agent requirement // #given - prometheus agent requirement
const prometheus = AGENT_MODEL_REQUIREMENTS["prometheus"] const prometheus = AGENT_MODEL_REQUIREMENTS["prometheus"]
// #when - accessing Prometheus requirement // #when - accessing Prometheus requirement
// #then - claude-opus-4-6 is first // #then - claude-opus-4-7 is first
expect(prometheus).toBeDefined() expect(prometheus).toBeDefined()
expect(prometheus.fallbackChain).toBeArray() expect(prometheus.fallbackChain).toBeArray()
expect(prometheus.fallbackChain.length).toBeGreaterThan(1) expect(prometheus.fallbackChain.length).toBeGreaterThan(1)
const primary = prometheus.fallbackChain[0] const primary = prometheus.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-7")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
}) })
test("metis has claude-opus-4-6 as primary", () => { test("metis has claude-opus-4-7 as primary", () => {
// #given - metis agent requirement // #given - metis agent requirement
const metis = AGENT_MODEL_REQUIREMENTS["metis"] const metis = AGENT_MODEL_REQUIREMENTS["metis"]
// #when - accessing Metis requirement // #when - accessing Metis requirement
// #then - claude-opus-4-6 is first // #then - claude-opus-4-7 is first
expect(metis).toBeDefined() expect(metis).toBeDefined()
expect(metis.fallbackChain).toBeArray() expect(metis.fallbackChain).toBeArray()
expect(metis.fallbackChain.length).toBeGreaterThan(1) expect(metis.fallbackChain.length).toBeGreaterThan(1)
const primary = metis.fallbackChain[0] const primary = metis.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-7")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
@@ -356,7 +356,7 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
expect(second.model).toBe("glm-5") expect(second.model).toBe("glm-5")
const third = visualEngineering.fallbackChain[2] const third = visualEngineering.fallbackChain[2]
expect(third.model).toBe("claude-opus-4-6") expect(third.model).toBe("claude-opus-4-7")
expect(third.variant).toBe("max") expect(third.variant).toBe("max")
const fourth = visualEngineering.fallbackChain[3] const fourth = visualEngineering.fallbackChain[3]
@@ -402,18 +402,18 @@ describe("CATEGORY_MODEL_REQUIREMENTS", () => {
expect(primary.providers[0]).toBe("anthropic") expect(primary.providers[0]).toBe("anthropic")
}) })
test("unspecified-high has claude-opus-4-6 as primary and gpt-5.4 as secondary", () => { test("unspecified-high has claude-opus-4-7 as primary and gpt-5.4 as secondary", () => {
// #given - unspecified-high category requirement // #given - unspecified-high category requirement
const unspecifiedHigh = CATEGORY_MODEL_REQUIREMENTS["unspecified-high"] const unspecifiedHigh = CATEGORY_MODEL_REQUIREMENTS["unspecified-high"]
// #when - accessing unspecified-high requirement // #when - accessing unspecified-high requirement
// #then - claude-opus-4-6 is first and gpt-5.4 is second // #then - claude-opus-4-7 is first and gpt-5.4 is second
expect(unspecifiedHigh).toBeDefined() expect(unspecifiedHigh).toBeDefined()
expect(unspecifiedHigh.fallbackChain).toBeArray() expect(unspecifiedHigh.fallbackChain).toBeArray()
expect(unspecifiedHigh.fallbackChain.length).toBeGreaterThan(1) expect(unspecifiedHigh.fallbackChain.length).toBeGreaterThan(1)
const primary = unspecifiedHigh.fallbackChain[0] const primary = unspecifiedHigh.fallbackChain[0]
expect(primary.model).toBe("claude-opus-4-6") expect(primary.model).toBe("claude-opus-4-7")
expect(primary.variant).toBe("max") expect(primary.variant).toBe("max")
expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"]) expect(primary.providers).toEqual(["anthropic", "github-copilot", "opencode", "vercel"])
@@ -505,14 +505,14 @@ describe("FallbackEntry type", () => {
// given - a valid FallbackEntry object // given - a valid FallbackEntry object
const entry: FallbackEntry = { const entry: FallbackEntry = {
providers: ["anthropic", "github-copilot", "opencode"], providers: ["anthropic", "github-copilot", "opencode"],
model: "claude-opus-4-6", model: "claude-opus-4-7",
variant: "high", variant: "high",
} }
// when - accessing properties // when - accessing properties
// then - all properties are accessible // then - all properties are accessible
expect(entry.providers).toEqual(["anthropic", "github-copilot", "opencode"]) expect(entry.providers).toEqual(["anthropic", "github-copilot", "opencode"])
expect(entry.model).toBe("claude-opus-4-6") expect(entry.model).toBe("claude-opus-4-7")
expect(entry.variant).toBe("high") expect(entry.variant).toBe("high")
}) })
@@ -534,7 +534,7 @@ describe("ModelRequirement type", () => {
// given - a valid ModelRequirement object // given - a valid ModelRequirement object
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-7", variant: "max" },
{ providers: ["openai", "github-copilot"], model: "gpt-5.4", variant: "high" }, { providers: ["openai", "github-copilot"], model: "gpt-5.4", variant: "high" },
], ],
} }
@@ -543,7 +543,7 @@ describe("ModelRequirement type", () => {
// then - fallbackChain is accessible with correct structure // then - fallbackChain is accessible with correct structure
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-7")
expect(requirement.fallbackChain[1].model).toBe("gpt-5.4") expect(requirement.fallbackChain[1].model).toBe("gpt-5.4")
}) })

Some files were not shown because too many files have changed in this diff Show More